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);
26 WebInspector.CPUProfileDataModel.prototype = {
28 * @param {!ProfilerAgent.CPUProfile} profile
30 _calculateTimes: function(profile)
32 function totalHitCount(node) {
33 var result = node.hitCount;
34 for (var i = 0; i < node.children.length; i++)
35 result += totalHitCount(node.children[i]);
38 profile.totalHitCount = totalHitCount(profile.head);
40 var duration = this.profileEndTime - this.profileStartTime;
41 var samplingInterval = duration / profile.totalHitCount;
42 this.samplingInterval = samplingInterval;
44 function calculateTimesForNode(node) {
45 node.selfTime = node.hitCount * samplingInterval;
46 var totalHitCount = node.hitCount;
47 for (var i = 0; i < node.children.length; i++)
48 totalHitCount += calculateTimesForNode(node.children[i]);
49 node.totalTime = totalHitCount * samplingInterval;
52 calculateTimesForNode(profile.head);
55 _assignParentsInProfile: function()
57 var head = this.profileHead;
61 var nodesToTraverse = [ head ];
62 while (nodesToTraverse.length) {
63 var parent = nodesToTraverse.pop();
64 var depth = parent.depth + 1;
65 if (depth > this.maxDepth)
66 this.maxDepth = depth;
67 var children = parent.children;
68 var length = children.length;
69 for (var i = 0; i < length; ++i) {
70 var child = children[i];
71 child.parent = parent;
73 if (child.children.length)
74 nodesToTraverse.push(child);
79 _normalizeTimestamps: function()
81 var timestamps = this.timestamps;
83 // Support loading old CPU profiles that are missing timestamps.
84 // Derive timestamps from profile start and stop times.
85 var profileStartTime = this.profileStartTime;
86 var interval = (this.profileEndTime - profileStartTime) / this.samples.length;
87 timestamps = new Float64Array(this.samples.length + 1);
88 for (var i = 0; i < timestamps.length; ++i)
89 timestamps[i] = profileStartTime + i * interval;
90 this.timestamps = timestamps;
94 // Convert samples from usec to msec
95 for (var i = 0; i < timestamps.length; ++i)
96 timestamps[i] /= 1000;
97 var averageSample = (timestamps.peekLast() - timestamps[0]) / (timestamps.length - 1);
98 // Add an extra timestamp used to calculate the last sample duration.
99 this.timestamps.push(timestamps.peekLast() + averageSample);
100 this.profileStartTime = timestamps[0];
101 this.profileEndTime = timestamps.peekLast();
104 _buildIdToNodeMap: function()
106 /** @type {!Object.<number, !ProfilerAgent.CPUProfileNode>} */
108 var idToNode = this._idToNode;
109 var stack = [this.profileHead];
110 while (stack.length) {
111 var node = stack.pop();
112 idToNode[node.id] = node;
113 for (var i = 0; i < node.children.length; i++)
114 stack.push(node.children[i]);
117 var topLevelNodes = this.profileHead.children;
118 for (var i = 0; i < topLevelNodes.length && !(this.gcNode && this.programNode && this.idleNode); i++) {
119 var node = topLevelNodes[i];
120 if (node.functionName === "(garbage collector)")
122 else if (node.functionName === "(program)")
123 this.programNode = node;
124 else if (node.functionName === "(idle)")
125 this.idleNode = node;
129 _fixMissingSamples: function()
131 // Sometimes sampler is not able to parse the JS stack and returns
132 // a (program) sample instead. The issue leads to call frames belong
133 // to the same function invocation being split apart.
134 // Here's a workaround for that. When there's a single (program) sample
135 // between two call stacks sharing the same bottom node, it is replaced
136 // with the preceeding sample.
137 var samples = this.samples;
138 var samplesCount = samples.length;
139 if (!this.programNode || samplesCount < 3)
141 var idToNode = this._idToNode;
142 var programNodeId = this.programNode.id;
143 var gcNodeId = this.gcNode ? this.gcNode.id : -1;
144 var idleNodeId = this.idleNode ? this.idleNode.id : -1;
145 var prevNodeId = samples[0];
146 var nodeId = samples[1];
147 for (var sampleIndex = 1; sampleIndex < samplesCount - 1; sampleIndex++) {
148 var nextNodeId = samples[sampleIndex + 1];
149 if (nodeId === programNodeId && !isSystemNode(prevNodeId) && !isSystemNode(nextNodeId)
150 && bottomNode(idToNode[prevNodeId]) === bottomNode(idToNode[nextNodeId])) {
151 samples[sampleIndex] = prevNodeId;
158 * @param {!ProfilerAgent.CPUProfileNode} node
159 * @return {!ProfilerAgent.CPUProfileNode}
161 function bottomNode(node)
169 * @param {number} nodeId
172 function isSystemNode(nodeId)
174 return nodeId === programNodeId || nodeId === gcNodeId || nodeId === idleNodeId;
179 * @param {function(number, !ProfilerAgent.CPUProfileNode, number)} openFrameCallback
180 * @param {function(number, !ProfilerAgent.CPUProfileNode, number, number, number)} closeFrameCallback
181 * @param {number=} startTime
182 * @param {number=} stopTime
184 forEachFrame: function(openFrameCallback, closeFrameCallback, startTime, stopTime)
186 if (!this.profileHead)
189 startTime = startTime || 0;
190 stopTime = stopTime || Infinity;
191 var samples = this.samples;
192 var timestamps = this.timestamps;
193 var idToNode = this._idToNode;
194 var gcNode = this.gcNode;
195 var samplesCount = samples.length;
196 var startIndex = timestamps.lowerBound(startTime);
199 var prevId = this.profileHead.id;
200 var prevHeight = this.profileHead.depth;
201 var sampleTime = timestamps[samplesCount];
202 var gcParentNode = null;
204 if (!this._stackStartTimes)
205 this._stackStartTimes = new Float64Array(this.maxDepth + 2);
206 var stackStartTimes = this._stackStartTimes;
207 if (!this._stackChildrenDuration)
208 this._stackChildrenDuration = new Float64Array(this.maxDepth + 2);
209 var stackChildrenDuration = this._stackChildrenDuration;
211 for (var sampleIndex = startIndex; sampleIndex < samplesCount; sampleIndex++) {
212 sampleTime = timestamps[sampleIndex];
213 if (sampleTime >= stopTime)
215 var id = samples[sampleIndex];
218 var node = idToNode[id];
219 var prevNode = idToNode[prevId];
221 if (node === gcNode) {
222 // GC samples have no stack, so we just put GC node on top of the last recorded sample.
223 gcParentNode = prevNode;
224 openFrameCallback(gcParentNode.depth + 1, gcNode, sampleTime);
225 stackStartTimes[++stackTop] = sampleTime;
226 stackChildrenDuration[stackTop] = 0;
230 if (prevNode === gcNode) {
232 var start = stackStartTimes[stackTop];
233 var duration = sampleTime - start;
234 stackChildrenDuration[stackTop - 1] += duration;
235 closeFrameCallback(gcParentNode.depth + 1, gcNode, start, duration, duration - stackChildrenDuration[stackTop]);
237 prevNode = gcParentNode;
238 prevId = prevNode.id;
242 while (node.depth > prevNode.depth) {
243 stackNodes.push(node);
247 // Go down to the LCA and close current intervals.
248 while (prevNode !== node) {
249 var start = stackStartTimes[stackTop];
250 var duration = sampleTime - start;
251 stackChildrenDuration[stackTop - 1] += duration;
252 closeFrameCallback(prevNode.depth, prevNode, start, duration, duration - stackChildrenDuration[stackTop]);
254 if (node.depth === prevNode.depth) {
255 stackNodes.push(node);
258 prevNode = prevNode.parent;
261 // Go up the nodes stack and open new intervals.
262 while (stackNodes.length) {
263 node = stackNodes.pop();
264 openFrameCallback(node.depth, node, sampleTime);
265 stackStartTimes[++stackTop] = sampleTime;
266 stackChildrenDuration[stackTop] = 0;
272 if (idToNode[prevId] === gcNode) {
273 var start = stackStartTimes[stackTop];
274 var duration = sampleTime - start;
275 stackChildrenDuration[stackTop - 1] += duration;
276 closeFrameCallback(gcParentNode.depth + 1, node, start, duration, duration - stackChildrenDuration[stackTop]);
280 for (var node = idToNode[prevId]; node.parent; node = node.parent) {
281 var start = stackStartTimes[stackTop];
282 var duration = sampleTime - start;
283 stackChildrenDuration[stackTop - 1] += duration;
284 closeFrameCallback(node.depth, node, start, duration, duration - stackChildrenDuration[stackTop]);