1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
8 * @param {!ProfilerAgent.CPUProfile} profile
10 WebInspector.CPUProfileDataModel = function(profile)
12 this.profileHead = profile.head;
13 this.samples = profile.samples;
14 this.timestamps = profile.timestamps;
15 this.profileStartTime = profile.startTime * 1000;
16 this.profileEndTime = profile.endTime * 1000;
17 this._assignParentsInProfile();
19 this._normalizeTimestamps();
20 this._buildIdToNodeMap();
21 this._fixMissingSamples();
23 this._calculateTimes(profile);
27 * @param {string} name
30 WebInspector.CPUProfileDataModel.beautifyFunctionName = function(name)
32 return name || WebInspector.UIString("(anonymous function)");
35 WebInspector.CPUProfileDataModel.prototype = {
37 * @param {!ProfilerAgent.CPUProfile} profile
39 _calculateTimes: function(profile)
41 function totalHitCount(node) {
42 var result = node.hitCount;
43 for (var i = 0; i < node.children.length; i++)
44 result += totalHitCount(node.children[i]);
47 profile.totalHitCount = totalHitCount(profile.head);
49 var duration = this.profileEndTime - this.profileStartTime;
50 var samplingInterval = duration / profile.totalHitCount;
51 this.samplingInterval = samplingInterval;
53 function calculateTimesForNode(node) {
54 node.selfTime = node.hitCount * samplingInterval;
55 var totalHitCount = node.hitCount;
56 for (var i = 0; i < node.children.length; i++)
57 totalHitCount += calculateTimesForNode(node.children[i]);
58 node.totalTime = totalHitCount * samplingInterval;
61 calculateTimesForNode(profile.head);
64 _assignParentsInProfile: function()
66 var head = this.profileHead;
70 var nodesToTraverse = [ head ];
71 while (nodesToTraverse.length) {
72 var parent = nodesToTraverse.pop();
73 var depth = parent.depth + 1;
74 if (depth > this.maxDepth)
75 this.maxDepth = depth;
76 var children = parent.children;
77 var length = children.length;
78 for (var i = 0; i < length; ++i) {
79 var child = children[i];
80 child.parent = parent;
82 if (child.children.length)
83 nodesToTraverse.push(child);
88 _normalizeTimestamps: function()
90 var timestamps = this.timestamps;
92 // Support loading old CPU profiles that are missing timestamps.
93 // Derive timestamps from profile start and stop times.
94 var profileStartTime = this.profileStartTime;
95 var interval = (this.profileEndTime - profileStartTime) / this.samples.length;
96 timestamps = new Float64Array(this.samples.length + 1);
97 for (var i = 0; i < timestamps.length; ++i)
98 timestamps[i] = profileStartTime + i * interval;
99 this.timestamps = timestamps;
103 // Convert samples from usec to msec
104 for (var i = 0; i < timestamps.length; ++i)
105 timestamps[i] /= 1000;
106 var averageSample = (timestamps.peekLast() - timestamps[0]) / (timestamps.length - 1);
107 // Add an extra timestamp used to calculate the last sample duration.
108 this.timestamps.push(timestamps.peekLast() + averageSample);
109 this.profileStartTime = timestamps[0];
110 this.profileEndTime = timestamps.peekLast();
113 _buildIdToNodeMap: function()
115 /** @type {!Object.<number, !ProfilerAgent.CPUProfileNode>} */
117 var idToNode = this._idToNode;
118 var stack = [this.profileHead];
119 while (stack.length) {
120 var node = stack.pop();
121 idToNode[node.id] = node;
122 for (var i = 0; i < node.children.length; i++)
123 stack.push(node.children[i]);
126 var topLevelNodes = this.profileHead.children;
127 for (var i = 0; i < topLevelNodes.length && !(this.gcNode && this.programNode && this.idleNode); i++) {
128 var node = topLevelNodes[i];
129 if (node.functionName === "(garbage collector)")
131 else if (node.functionName === "(program)")
132 this.programNode = node;
133 else if (node.functionName === "(idle)")
134 this.idleNode = node;
138 _fixMissingSamples: function()
140 // Sometimes sampler is not able to parse the JS stack and returns
141 // a (program) sample instead. The issue leads to call frames belong
142 // to the same function invocation being split apart.
143 // Here's a workaround for that. When there's a single (program) sample
144 // between two call stacks sharing the same bottom node, it is replaced
145 // with the preceeding sample.
146 var samples = this.samples;
147 var samplesCount = samples.length;
148 if (!this.programNode || samplesCount < 3)
150 var idToNode = this._idToNode;
151 var programNodeId = this.programNode.id;
152 var gcNodeId = this.gcNode ? this.gcNode.id : -1;
153 var idleNodeId = this.idleNode ? this.idleNode.id : -1;
154 var prevNodeId = samples[0];
155 var nodeId = samples[1];
156 for (var sampleIndex = 1; sampleIndex < samplesCount - 1; sampleIndex++) {
157 var nextNodeId = samples[sampleIndex + 1];
158 if (nodeId === programNodeId && !isSystemNode(prevNodeId) && !isSystemNode(nextNodeId)
159 && bottomNode(idToNode[prevNodeId]) === bottomNode(idToNode[nextNodeId])) {
160 samples[sampleIndex] = prevNodeId;
167 * @param {!ProfilerAgent.CPUProfileNode} node
168 * @return {!ProfilerAgent.CPUProfileNode}
170 function bottomNode(node)
178 * @param {number} nodeId
181 function isSystemNode(nodeId)
183 return nodeId === programNodeId || nodeId === gcNodeId || nodeId === idleNodeId;
188 * @param {function(number, !ProfilerAgent.CPUProfileNode, number)} openFrameCallback
189 * @param {function(number, !ProfilerAgent.CPUProfileNode, number, number, number)} closeFrameCallback
190 * @param {number=} startTime
191 * @param {number=} stopTime
193 forEachFrame: function(openFrameCallback, closeFrameCallback, startTime, stopTime)
195 if (!this.profileHead)
198 startTime = startTime || 0;
199 stopTime = stopTime || Infinity;
200 var samples = this.samples;
201 var timestamps = this.timestamps;
202 var idToNode = this._idToNode;
203 var gcNode = this.gcNode;
204 var samplesCount = samples.length;
205 var startIndex = timestamps.lowerBound(startTime);
208 var prevId = this.profileHead.id;
209 var prevHeight = this.profileHead.depth;
210 var sampleTime = timestamps[samplesCount];
211 var gcParentNode = null;
213 if (!this._stackStartTimes)
214 this._stackStartTimes = new Float64Array(this.maxDepth + 2);
215 var stackStartTimes = this._stackStartTimes;
216 if (!this._stackChildrenDuration)
217 this._stackChildrenDuration = new Float64Array(this.maxDepth + 2);
218 var stackChildrenDuration = this._stackChildrenDuration;
220 for (var sampleIndex = startIndex; sampleIndex < samplesCount; sampleIndex++) {
221 sampleTime = timestamps[sampleIndex];
222 if (sampleTime >= stopTime)
224 var id = samples[sampleIndex];
227 var node = idToNode[id];
228 var prevNode = idToNode[prevId];
230 if (node === gcNode) {
231 // GC samples have no stack, so we just put GC node on top of the last recorded sample.
232 gcParentNode = prevNode;
233 openFrameCallback(gcParentNode.depth + 1, gcNode, sampleTime);
234 stackStartTimes[++stackTop] = sampleTime;
235 stackChildrenDuration[stackTop] = 0;
239 if (prevNode === gcNode) {
241 var start = stackStartTimes[stackTop];
242 var duration = sampleTime - start;
243 stackChildrenDuration[stackTop - 1] += duration;
244 closeFrameCallback(gcParentNode.depth + 1, gcNode, start, duration, duration - stackChildrenDuration[stackTop]);
246 prevNode = gcParentNode;
247 prevId = prevNode.id;
251 while (node.depth > prevNode.depth) {
252 stackNodes.push(node);
256 // Go down to the LCA and close current intervals.
257 while (prevNode !== node) {
258 var start = stackStartTimes[stackTop];
259 var duration = sampleTime - start;
260 stackChildrenDuration[stackTop - 1] += duration;
261 closeFrameCallback(prevNode.depth, prevNode, start, duration, duration - stackChildrenDuration[stackTop]);
263 if (node.depth === prevNode.depth) {
264 stackNodes.push(node);
267 prevNode = prevNode.parent;
270 // Go up the nodes stack and open new intervals.
271 while (stackNodes.length) {
272 node = stackNodes.pop();
273 openFrameCallback(node.depth, node, sampleTime);
274 stackStartTimes[++stackTop] = sampleTime;
275 stackChildrenDuration[stackTop] = 0;
281 if (idToNode[prevId] === gcNode) {
282 var start = stackStartTimes[stackTop];
283 var duration = sampleTime - start;
284 stackChildrenDuration[stackTop - 1] += duration;
285 closeFrameCallback(gcParentNode.depth + 1, node, start, duration, duration - stackChildrenDuration[stackTop]);
289 for (var node = idToNode[prevId]; node.parent; node = node.parent) {
290 var start = stackStartTimes[stackTop];
291 var duration = sampleTime - start;
292 stackChildrenDuration[stackTop - 1] += duration;
293 closeFrameCallback(node.depth, node, start, duration, duration - stackChildrenDuration[stackTop]);
299 * @param {number} index
300 * @return {!ProfilerAgent.CPUProfileNode}
302 nodeByIndex: function(index)
304 return this._idToNode[this.samples[index]];