Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / sdk / CPUProfileDataModel.js
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.
4
5
6 /**
7  * @constructor
8  * @param {!ProfilerAgent.CPUProfile} profile
9  */
10 WebInspector.CPUProfileDataModel = function(profile)
11 {
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();
18     if (this.samples) {
19         this._normalizeTimestamps();
20         this._buildIdToNodeMap();
21         this._fixMissingSamples();
22     }
23     this._calculateTimes(profile);
24 }
25
26 /**
27  * @param {string} name
28  * @return {string}
29  */
30 WebInspector.CPUProfileDataModel.beautifyFunctionName = function(name)
31 {
32     return name || WebInspector.UIString("(anonymous function)");
33 }
34
35 WebInspector.CPUProfileDataModel.prototype = {
36     /**
37      * @param {!ProfilerAgent.CPUProfile} profile
38      */
39     _calculateTimes: function(profile)
40     {
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]);
45             return result;
46         }
47         profile.totalHitCount = totalHitCount(profile.head);
48
49         var duration = this.profileEndTime - this.profileStartTime;
50         var samplingInterval = duration / profile.totalHitCount;
51         this.samplingInterval = samplingInterval;
52
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;
59             return totalHitCount;
60         }
61         calculateTimesForNode(profile.head);
62     },
63
64     _assignParentsInProfile: function()
65     {
66         var head = this.profileHead;
67         head.parent = null;
68         head.depth = -1;
69         this.maxDepth = 0;
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;
81                 child.depth = depth;
82                 if (child.children.length)
83                     nodesToTraverse.push(child);
84             }
85         }
86     },
87
88     _normalizeTimestamps: function()
89     {
90         var timestamps = this.timestamps;
91         if (!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;
100             return;
101         }
102
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();
111     },
112
113     _buildIdToNodeMap: function()
114     {
115         /** @type {!Object.<number, !ProfilerAgent.CPUProfileNode>} */
116         this._idToNode = {};
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]);
124         }
125
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)")
130                 this.gcNode = node;
131             else if (node.functionName === "(program)")
132                 this.programNode = node;
133             else if (node.functionName === "(idle)")
134                 this.idleNode = node;
135         }
136     },
137
138     _fixMissingSamples: function()
139     {
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)
149             return;
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;
161             }
162             prevNodeId = nodeId;
163             nodeId = nextNodeId;
164         }
165
166         /**
167          * @param {!ProfilerAgent.CPUProfileNode} node
168          * @return {!ProfilerAgent.CPUProfileNode}
169          */
170         function bottomNode(node)
171         {
172             while (node.parent)
173                 node = node.parent;
174             return node;
175         }
176
177         /**
178          * @param {number} nodeId
179          * @return {boolean}
180          */
181         function isSystemNode(nodeId)
182         {
183             return nodeId === programNodeId || nodeId === gcNodeId || nodeId === idleNodeId;
184         }
185     },
186
187     /**
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
192      */
193     forEachFrame: function(openFrameCallback, closeFrameCallback, startTime, stopTime)
194     {
195         if (!this.profileHead)
196             return;
197
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);
206         var stackTop = 0;
207         var stackNodes = [];
208         var prevId = this.profileHead.id;
209         var prevHeight = this.profileHead.depth;
210         var sampleTime = timestamps[samplesCount];
211         var gcParentNode = null;
212
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;
219
220         for (var sampleIndex = startIndex; sampleIndex < samplesCount; sampleIndex++) {
221             sampleTime = timestamps[sampleIndex];
222             if (sampleTime >= stopTime)
223                 break;
224             var id = samples[sampleIndex];
225             if (id === prevId)
226                 continue;
227             var node = idToNode[id];
228             var prevNode = idToNode[prevId];
229
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;
236                 prevId = id;
237                 continue;
238             }
239             if (prevNode === gcNode) {
240                 // end of GC frame
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]);
245                 --stackTop;
246                 prevNode = gcParentNode;
247                 prevId = prevNode.id;
248                 gcParentNode = null;
249             }
250
251             while (node.depth > prevNode.depth) {
252                 stackNodes.push(node);
253                 node = node.parent;
254             }
255
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]);
262                 --stackTop;
263                 if (node.depth === prevNode.depth) {
264                     stackNodes.push(node);
265                     node = node.parent;
266                 }
267                 prevNode = prevNode.parent;
268             }
269
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;
276             }
277
278             prevId = id;
279         }
280
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]);
286             --stackTop;
287         }
288
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]);
294             --stackTop;
295         }
296     },
297
298     /**
299      * @param {number} index
300      * @return {!ProfilerAgent.CPUProfileNode}
301      */
302     nodeByIndex: function(index)
303     {
304         return this._idToNode[this.samples[index]];
305     }
306
307 }