UNPKG

strong-arc

Version:

A visual suite for the StrongLoop API Platform

308 lines (280 loc) 11.5 kB
// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @constructor * @param {!ProfilerAgent.CPUProfile} profile */ WebInspector.CPUProfileDataModel = function(profile) { this.profileHead = profile.head; this.samples = profile.samples; this.timestamps = profile.timestamps; this.profileStartTime = profile.startTime * 1000; this.profileEndTime = profile.endTime * 1000; this._assignParentsInProfile(); if (this.samples) { this._normalizeTimestamps(); this._buildIdToNodeMap(); this._fixMissingSamples(); } this._calculateTimes(profile); } /** * @param {string} name * @return {string} */ WebInspector.CPUProfileDataModel.beautifyFunctionName = function(name) { return name || WebInspector.UIString("(anonymous function)"); } WebInspector.CPUProfileDataModel.prototype = { /** * @param {!ProfilerAgent.CPUProfile} profile */ _calculateTimes: function(profile) { function totalHitCount(node) { var result = node.hitCount; for (var i = 0; i < node.children.length; i++) result += totalHitCount(node.children[i]); return result; } profile.totalHitCount = totalHitCount(profile.head); var duration = this.profileEndTime - this.profileStartTime; var samplingInterval = duration / profile.totalHitCount; this.samplingInterval = samplingInterval; function calculateTimesForNode(node) { node.selfTime = node.hitCount * samplingInterval; var totalHitCount = node.hitCount; for (var i = 0; i < node.children.length; i++) totalHitCount += calculateTimesForNode(node.children[i]); node.totalTime = totalHitCount * samplingInterval; return totalHitCount; } calculateTimesForNode(profile.head); }, _assignParentsInProfile: function() { var head = this.profileHead; head.parent = null; head.depth = -1; this.maxDepth = 0; var nodesToTraverse = [ head ]; while (nodesToTraverse.length) { var parent = nodesToTraverse.pop(); var depth = parent.depth + 1; if (depth > this.maxDepth) this.maxDepth = depth; var children = parent.children; var length = children.length; for (var i = 0; i < length; ++i) { var child = children[i]; child.parent = parent; child.depth = depth; if (child.children.length) nodesToTraverse.push(child); } } }, _normalizeTimestamps: function() { var timestamps = this.timestamps; if (!timestamps) { // Support loading old CPU profiles that are missing timestamps. // Derive timestamps from profile start and stop times. var profileStartTime = this.profileStartTime; var interval = (this.profileEndTime - profileStartTime) / this.samples.length; timestamps = new Float64Array(this.samples.length + 1); for (var i = 0; i < timestamps.length; ++i) timestamps[i] = profileStartTime + i * interval; this.timestamps = timestamps; return; } // Convert samples from usec to msec for (var i = 0; i < timestamps.length; ++i) timestamps[i] /= 1000; var averageSample = (timestamps.peekLast() - timestamps[0]) / (timestamps.length - 1); // Add an extra timestamp used to calculate the last sample duration. this.timestamps.push(timestamps.peekLast() + averageSample); this.profileStartTime = timestamps[0]; this.profileEndTime = timestamps.peekLast(); }, _buildIdToNodeMap: function() { /** @type {!Object.<number, !ProfilerAgent.CPUProfileNode>} */ this._idToNode = {}; var idToNode = this._idToNode; var stack = [this.profileHead]; while (stack.length) { var node = stack.pop(); idToNode[node.id] = node; for (var i = 0; i < node.children.length; i++) stack.push(node.children[i]); } var topLevelNodes = this.profileHead.children; for (var i = 0; i < topLevelNodes.length && !(this.gcNode && this.programNode && this.idleNode); i++) { var node = topLevelNodes[i]; if (node.functionName === "(garbage collector)") this.gcNode = node; else if (node.functionName === "(program)") this.programNode = node; else if (node.functionName === "(idle)") this.idleNode = node; } }, _fixMissingSamples: function() { // Sometimes sampler is not able to parse the JS stack and returns // a (program) sample instead. The issue leads to call frames belong // to the same function invocation being split apart. // Here's a workaround for that. When there's a single (program) sample // between two call stacks sharing the same bottom node, it is replaced // with the preceeding sample. var samples = this.samples; var samplesCount = samples.length; if (!this.programNode || samplesCount < 3) return; var idToNode = this._idToNode; var programNodeId = this.programNode.id; var gcNodeId = this.gcNode ? this.gcNode.id : -1; var idleNodeId = this.idleNode ? this.idleNode.id : -1; var prevNodeId = samples[0]; var nodeId = samples[1]; for (var sampleIndex = 1; sampleIndex < samplesCount - 1; sampleIndex++) { var nextNodeId = samples[sampleIndex + 1]; if (nodeId === programNodeId && !isSystemNode(prevNodeId) && !isSystemNode(nextNodeId) && bottomNode(idToNode[prevNodeId]) === bottomNode(idToNode[nextNodeId])) { samples[sampleIndex] = prevNodeId; } prevNodeId = nodeId; nodeId = nextNodeId; } /** * @param {!ProfilerAgent.CPUProfileNode} node * @return {!ProfilerAgent.CPUProfileNode} */ function bottomNode(node) { while (node.parent) node = node.parent; return node; } /** * @param {number} nodeId * @return {boolean} */ function isSystemNode(nodeId) { return nodeId === programNodeId || nodeId === gcNodeId || nodeId === idleNodeId; } }, /** * @param {function(number, !ProfilerAgent.CPUProfileNode, number)} openFrameCallback * @param {function(number, !ProfilerAgent.CPUProfileNode, number, number, number)} closeFrameCallback * @param {number=} startTime * @param {number=} stopTime */ forEachFrame: function(openFrameCallback, closeFrameCallback, startTime, stopTime) { if (!this.profileHead) return; startTime = startTime || 0; stopTime = stopTime || Infinity; var samples = this.samples; var timestamps = this.timestamps; var idToNode = this._idToNode; var gcNode = this.gcNode; var samplesCount = samples.length; var startIndex = timestamps.lowerBound(startTime); var stackTop = 0; var stackNodes = []; var prevId = this.profileHead.id; var prevHeight = this.profileHead.depth; var sampleTime = timestamps[samplesCount]; var gcParentNode = null; if (!this._stackStartTimes) this._stackStartTimes = new Float64Array(this.maxDepth + 2); var stackStartTimes = this._stackStartTimes; if (!this._stackChildrenDuration) this._stackChildrenDuration = new Float64Array(this.maxDepth + 2); var stackChildrenDuration = this._stackChildrenDuration; for (var sampleIndex = startIndex; sampleIndex < samplesCount; sampleIndex++) { sampleTime = timestamps[sampleIndex]; if (sampleTime >= stopTime) break; var id = samples[sampleIndex]; if (id === prevId) continue; var node = idToNode[id]; var prevNode = idToNode[prevId]; if (node === gcNode) { // GC samples have no stack, so we just put GC node on top of the last recorded sample. gcParentNode = prevNode; openFrameCallback(gcParentNode.depth + 1, gcNode, sampleTime); stackStartTimes[++stackTop] = sampleTime; stackChildrenDuration[stackTop] = 0; prevId = id; continue; } if (prevNode === gcNode) { // end of GC frame var start = stackStartTimes[stackTop]; var duration = sampleTime - start; stackChildrenDuration[stackTop - 1] += duration; closeFrameCallback(gcParentNode.depth + 1, gcNode, start, duration, duration - stackChildrenDuration[stackTop]); --stackTop; prevNode = gcParentNode; prevId = prevNode.id; gcParentNode = null; } while (node.depth > prevNode.depth) { stackNodes.push(node); node = node.parent; } // Go down to the LCA and close current intervals. while (prevNode !== node) { var start = stackStartTimes[stackTop]; var duration = sampleTime - start; stackChildrenDuration[stackTop - 1] += duration; closeFrameCallback(prevNode.depth, prevNode, start, duration, duration - stackChildrenDuration[stackTop]); --stackTop; if (node.depth === prevNode.depth) { stackNodes.push(node); node = node.parent; } prevNode = prevNode.parent; } // Go up the nodes stack and open new intervals. while (stackNodes.length) { node = stackNodes.pop(); openFrameCallback(node.depth, node, sampleTime); stackStartTimes[++stackTop] = sampleTime; stackChildrenDuration[stackTop] = 0; } prevId = id; } if (idToNode[prevId] === gcNode) { var start = stackStartTimes[stackTop]; var duration = sampleTime - start; stackChildrenDuration[stackTop - 1] += duration; closeFrameCallback(gcParentNode.depth + 1, node, start, duration, duration - stackChildrenDuration[stackTop]); --stackTop; } for (var node = idToNode[prevId]; node.parent; node = node.parent) { var start = stackStartTimes[stackTop]; var duration = sampleTime - start; stackChildrenDuration[stackTop - 1] += duration; closeFrameCallback(node.depth, node, start, duration, duration - stackChildrenDuration[stackTop]); --stackTop; } }, /** * @param {number} index * @return {!ProfilerAgent.CPUProfileNode} */ nodeByIndex: function(index) { return this._idToNode[this.samples[index]]; } }