Upstream version 9.37.197.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / sdk / CPUProfileModel.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 WebInspector.CPUProfileDataModel.prototype = {
27     /**
28      * @param {!ProfilerAgent.CPUProfile} profile
29      */
30     _calculateTimes: function(profile)
31     {
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]);
36             return result;
37         }
38         profile.totalHitCount = totalHitCount(profile.head);
39
40         var duration = this.profileEndTime - this.profileStartTime;
41         var samplingInterval = duration / profile.totalHitCount;
42         this.samplingInterval = samplingInterval;
43
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;
50             return totalHitCount;
51         }
52         calculateTimesForNode(profile.head);
53     },
54
55     _assignParentsInProfile: function()
56     {
57         var head = this.profileHead;
58         head.parent = null;
59         head.depth = -1;
60         this.maxDepth = 0;
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;
72                 child.depth = depth;
73                 if (child.children.length)
74                     nodesToTraverse.push(child);
75             }
76         }
77     },
78
79     _normalizeTimestamps: function()
80     {
81         var timestamps = this.timestamps;
82         if (!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;
91             return;
92         }
93
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();
102     },
103
104     _buildIdToNodeMap: function()
105     {
106         /** @type {!Object.<number, !ProfilerAgent.CPUProfileNode>} */
107         this._idToNode = {};
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]);
115         }
116
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)")
121                 this.gcNode = node;
122             else if (node.functionName === "(program)")
123                 this.programNode = node;
124             else if (node.functionName === "(idle)")
125                 this.idleNode = node;
126         }
127     },
128
129     _fixMissingSamples: function()
130     {
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)
140             return;
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;
152             }
153             prevNodeId = nodeId;
154             nodeId = nextNodeId;
155         }
156
157         /**
158          * @param {!ProfilerAgent.CPUProfileNode} node
159          * @return {!ProfilerAgent.CPUProfileNode}
160          */
161         function bottomNode(node)
162         {
163             while (node.parent)
164                 node = node.parent;
165             return node;
166         }
167
168         /**
169          * @param {number} nodeId
170          * @return {boolean}
171          */
172         function isSystemNode(nodeId)
173         {
174             return nodeId === programNodeId || nodeId === gcNodeId || nodeId === idleNodeId;
175         }
176     },
177
178     /**
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
183      */
184     forEachFrame: function(openFrameCallback, closeFrameCallback, startTime, stopTime)
185     {
186         if (!this.profileHead)
187             return;
188
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);
197         var stackTop = 0;
198         var stackNodes = [];
199         var prevId = this.profileHead.id;
200         var prevHeight = this.profileHead.depth;
201         var sampleTime = timestamps[samplesCount];
202         var gcParentNode = null;
203
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;
210
211         for (var sampleIndex = startIndex; sampleIndex < samplesCount; sampleIndex++) {
212             sampleTime = timestamps[sampleIndex];
213             if (sampleTime >= stopTime)
214                 break;
215             var id = samples[sampleIndex];
216             if (id === prevId)
217                 continue;
218             var node = idToNode[id];
219             var prevNode = idToNode[prevId];
220
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;
227                 prevId = id;
228                 continue;
229             }
230             if (prevNode === gcNode) {
231                 // end of GC frame
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]);
236                 --stackTop;
237                 prevNode = gcParentNode;
238                 prevId = prevNode.id;
239                 gcParentNode = null;
240             }
241
242             while (node.depth > prevNode.depth) {
243                 stackNodes.push(node);
244                 node = node.parent;
245             }
246
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]);
253                 --stackTop;
254                 if (node.depth === prevNode.depth) {
255                     stackNodes.push(node);
256                     node = node.parent;
257                 }
258                 prevNode = prevNode.parent;
259             }
260
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;
267             }
268
269             prevId = id;
270         }
271
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]);
277             --stackTop;
278         }
279
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]);
285             --stackTop;
286         }
287     }
288 }