089a32d71bd8e485a3f2fa006e4f8c0d545232ca
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / profiler / CPUProfileFlameChart.js
1 /**
2  * Copyright (C) 2014 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31
32 /**
33  * @constructor
34  * @implements {WebInspector.FlameChartDataProvider}
35  * @param {!WebInspector.CPUProfileDataModel} cpuProfile
36  * @param {!WeakReference.<!WebInspector.Target>} weakTarget
37  */
38 WebInspector.CPUFlameChartDataProvider = function(cpuProfile, weakTarget)
39 {
40     WebInspector.FlameChartDataProvider.call(this);
41     this._cpuProfile = cpuProfile;
42     this._weakTarget = weakTarget;
43     this._colorGenerator = WebInspector.CPUFlameChartDataProvider.colorGenerator();
44 }
45
46 WebInspector.CPUFlameChartDataProvider.prototype = {
47     /**
48      * @return {number}
49      */
50     barHeight: function()
51     {
52         return 15;
53     },
54
55     /**
56      * @return {number}
57      */
58     textBaseline: function()
59     {
60         return 4;
61     },
62
63     /**
64      * @return {number}
65      */
66     textPadding: function()
67     {
68         return 2;
69     },
70
71     /**
72      * @param {number} startTime
73      * @param {number} endTime
74      * @return {?Array.<number>}
75      */
76     dividerOffsets: function(startTime, endTime)
77     {
78         return null;
79     },
80
81     /**
82      * @return {number}
83      */
84     minimumBoundary: function()
85     {
86         return this._cpuProfile.profileStartTime;
87     },
88
89     /**
90      * @return {number}
91      */
92     totalTime: function()
93     {
94         return this._cpuProfile.profileHead.totalTime;
95     },
96
97     /**
98      * @return {number}
99      */
100     maxStackDepth: function()
101     {
102         return this._maxStackDepth;
103     },
104
105     /**
106      * @return {?WebInspector.FlameChart.TimelineData}
107      */
108     timelineData: function()
109     {
110         return this._timelineData || this._calculateTimelineData();
111     },
112
113     /**
114      * @param {number} index
115      * @return {string}
116      */
117     markerColor: function(index)
118     {
119         throw new Error("Unreachable.");
120     },
121
122     /**
123      * @param {number} index
124      * @return {string}
125      */
126     markerTitle: function(index)
127     {
128         throw new Error("Unreachable.");
129     },
130
131     /**
132      * @return {?WebInspector.FlameChart.TimelineData}
133      */
134     _calculateTimelineData: function()
135     {
136         /**
137          * @constructor
138          * @param {number} depth
139          * @param {number} duration
140          * @param {number} startTime
141          * @param {number} selfTime
142          * @param {!ProfilerAgent.CPUProfileNode} node
143          */
144         function ChartEntry(depth, duration, startTime, selfTime, node)
145         {
146             this.depth = depth;
147             this.duration = duration;
148             this.startTime = startTime;
149             this.selfTime = selfTime;
150             this.node = node;
151         }
152
153         /** @type {!Array.<?ChartEntry>} */
154         var entries = [];
155         /** @type {!Array.<number>} */
156         var stack = [];
157         var maxDepth = 5;
158
159         function onOpenFrame()
160         {
161             stack.push(entries.length);
162             // Reserve space for the entry, as they have to be ordered by startTime.
163             // The entry itself will be put there in onCloseFrame.
164             entries.push(null);
165         }
166         function onCloseFrame(depth, node, startTime, totalTime, selfTime)
167         {
168             var index = stack.pop();
169             entries[index] = new ChartEntry(depth, totalTime, startTime, selfTime, node);
170             maxDepth = Math.max(maxDepth, depth);
171         }
172         this._cpuProfile.forEachFrame(onOpenFrame, onCloseFrame);
173
174         /** @type {!Array.<!ProfilerAgent.CPUProfileNode>} */
175         var entryNodes = new Array(entries.length);
176         var entryLevels = new Uint8Array(entries.length);
177         var entryTotalTimes = new Float32Array(entries.length);
178         var entrySelfTimes = new Float32Array(entries.length);
179         var entryStartTimes = new Float64Array(entries.length);
180         var minimumBoundary = this.minimumBoundary();
181
182         for (var i = 0; i < entries.length; ++i) {
183             var entry = entries[i];
184             entryNodes[i] = entry.node;
185             entryLevels[i] = entry.depth;
186             entryTotalTimes[i] = entry.duration;
187             entryStartTimes[i] = entry.startTime;
188             entrySelfTimes[i] = entry.selfTime;
189         }
190
191         this._maxStackDepth = maxDepth;
192
193         this._timelineData = new WebInspector.FlameChart.TimelineData(entryLevels, entryTotalTimes, entryStartTimes);
194
195         /** @type {!Array.<!ProfilerAgent.CPUProfileNode>} */
196         this._entryNodes = entryNodes;
197         this._entrySelfTimes = entrySelfTimes;
198
199         return this._timelineData;
200     },
201
202     /**
203      * @param {number} ms
204      * @return {string}
205      */
206     _millisecondsToString: function(ms)
207     {
208         if (ms === 0)
209             return "0";
210         if (ms < 1000)
211             return WebInspector.UIString("%.1f\u2009ms", ms);
212         return Number.secondsToString(ms / 1000, true);
213     },
214
215     /**
216      * @param {number} entryIndex
217      * @return {?Array.<!{title: string, text: string}>}
218      */
219     prepareHighlightedEntryInfo: function(entryIndex)
220     {
221         var timelineData = this._timelineData;
222         var node = this._entryNodes[entryIndex];
223         if (!node)
224             return null;
225
226         var entryInfo = [];
227         function pushEntryInfoRow(title, text)
228         {
229             var row = {};
230             row.title = title;
231             row.text = text;
232             entryInfo.push(row);
233         }
234
235         var name = WebInspector.CPUProfileDataModel.beautifyFunctionName(node.functionName);
236         pushEntryInfoRow(WebInspector.UIString("Name"), name);
237         var selfTime = this._millisecondsToString(this._entrySelfTimes[entryIndex]);
238         var totalTime = this._millisecondsToString(timelineData.entryTotalTimes[entryIndex]);
239         pushEntryInfoRow(WebInspector.UIString("Self time"), selfTime);
240         pushEntryInfoRow(WebInspector.UIString("Total time"), totalTime);
241         var target = this._weakTarget.get();
242         var text = target ? WebInspector.Linkifier.liveLocationText(target, node.scriptId, node.lineNumber, node.columnNumber) : node.url;
243         pushEntryInfoRow(WebInspector.UIString("URL"), text);
244         pushEntryInfoRow(WebInspector.UIString("Aggregated self time"), Number.secondsToString(node.selfTime / 1000, true));
245         pushEntryInfoRow(WebInspector.UIString("Aggregated total time"), Number.secondsToString(node.totalTime / 1000, true));
246         if (node.deoptReason && node.deoptReason !== "no reason")
247             pushEntryInfoRow(WebInspector.UIString("Not optimized"), node.deoptReason);
248
249         return entryInfo;
250     },
251
252     /**
253      * @param {number} entryIndex
254      * @return {boolean}
255      */
256     canJumpToEntry: function(entryIndex)
257     {
258         return this._entryNodes[entryIndex].scriptId !== "0";
259     },
260
261     /**
262      * @param {number} entryIndex
263      * @return {?string}
264      */
265     entryTitle: function(entryIndex)
266     {
267         var node = this._entryNodes[entryIndex];
268         return WebInspector.CPUProfileDataModel.beautifyFunctionName(node.functionName);
269     },
270
271     /**
272      * @param {number} entryIndex
273      * @return {?string}
274      */
275     entryFont: function(entryIndex)
276     {
277         if (!this._font) {
278             this._font = (this.barHeight() - 4) + "px " + WebInspector.fontFamily();
279             this._boldFont = "bold " + this._font;
280         }
281         var node = this._entryNodes[entryIndex];
282         var reason = node.deoptReason;
283         return (reason && reason !== "no reason") ? this._boldFont : this._font;
284     },
285
286     /**
287      * @param {number} entryIndex
288      * @return {string}
289      */
290     entryColor: function(entryIndex)
291     {
292         var node = this._entryNodes[entryIndex];
293         return this._colorGenerator.colorForID(node.functionName + ":" + node.url);
294     },
295
296     /**
297      * @param {number} entryIndex
298      * @param {!CanvasRenderingContext2D} context
299      * @param {?string} text
300      * @param {number} barX
301      * @param {number} barY
302      * @param {number} barWidth
303      * @param {number} barHeight
304      * @param {function(number):number} timeToPosition
305      * @return {boolean}
306      */
307     decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, timeToPosition)
308     {
309         return false;
310     },
311
312     /**
313      * @param {number} entryIndex
314      * @return {boolean}
315      */
316     forceDecoration: function(entryIndex)
317     {
318         return false;
319     },
320
321     /**
322      * @param {number} entryIndex
323      * @return {!{startTime: number, endTime: number}}
324      */
325     highlightTimeRange: function(entryIndex)
326     {
327         var startTime = this._timelineData.entryStartTimes[entryIndex];
328         return {
329             startTime: startTime,
330             endTime: startTime + this._timelineData.entryTotalTimes[entryIndex]
331         };
332     },
333
334     /**
335      * @return {number}
336      */
337     paddingLeft: function()
338     {
339         return 15;
340     },
341
342     /**
343      * @param {number} entryIndex
344      * @return {string}
345      */
346     textColor: function(entryIndex)
347     {
348         return "#333";
349     }
350 }
351
352
353 /**
354  * @return {!WebInspector.FlameChart.ColorGenerator}
355  */
356 WebInspector.CPUFlameChartDataProvider.colorGenerator = function()
357 {
358     if (!WebInspector.CPUFlameChartDataProvider._colorGenerator) {
359         var colorGenerator = new WebInspector.FlameChart.ColorGenerator(
360             { min: 180, max: 310, count: 7 },
361             { min: 50, max: 80, count: 5 },
362             { min: 80, max: 90, count: 3 });
363         colorGenerator.setColorForID("(idle):", "hsl(0, 0%, 94%)");
364         colorGenerator.setColorForID("(program):", "hsl(0, 0%, 80%)");
365         colorGenerator.setColorForID("(garbage collector):", "hsl(0, 0%, 80%)");
366         WebInspector.CPUFlameChartDataProvider._colorGenerator = colorGenerator;
367     }
368     return WebInspector.CPUFlameChartDataProvider._colorGenerator;
369 }
370
371
372 /**
373  * @constructor
374  * @extends {WebInspector.VBox}
375  * @param {!WebInspector.FlameChartDataProvider} dataProvider
376  */
377 WebInspector.CPUProfileFlameChart = function(dataProvider)
378 {
379     WebInspector.VBox.call(this);
380     this.registerRequiredCSS("flameChart.css");
381     this.element.id = "cpu-flame-chart";
382
383     this._overviewPane = new WebInspector.CPUProfileFlameChart.OverviewPane(dataProvider);
384     this._overviewPane.show(this.element);
385
386     this._mainPane = new WebInspector.FlameChart(dataProvider, this._overviewPane, true);
387     this._mainPane.show(this.element);
388     this._mainPane.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
389     this._overviewPane.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
390 }
391
392 WebInspector.CPUProfileFlameChart.prototype = {
393     /**
394      * @param {!WebInspector.Event} event
395      */
396     _onWindowChanged: function(event)
397     {
398         var windowLeft = event.data.windowTimeLeft;
399         var windowRight = event.data.windowTimeRight;
400         this._mainPane.setWindowTimes(windowLeft, windowRight);
401     },
402
403     /**
404      * @param {!number} timeLeft
405      * @param {!number} timeRight
406      */
407     selectRange: function(timeLeft, timeRight)
408     {
409         this._overviewPane._selectRange(timeLeft, timeRight);
410     },
411
412     /**
413      * @param {!WebInspector.Event} event
414      */
415     _onEntrySelected: function(event)
416     {
417         this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, event.data);
418     },
419
420     update: function()
421     {
422         this._overviewPane.update();
423         this._mainPane.update();
424     },
425
426     __proto__: WebInspector.VBox.prototype
427 };
428
429 /**
430  * @constructor
431  * @implements {WebInspector.TimelineGrid.Calculator}
432  */
433 WebInspector.CPUProfileFlameChart.OverviewCalculator = function()
434 {
435 }
436
437 WebInspector.CPUProfileFlameChart.OverviewCalculator.prototype = {
438     /**
439      * @return {number}
440      */
441     paddingLeft: function()
442     {
443         return 0;
444     },
445
446     /**
447      * @param {!WebInspector.CPUProfileFlameChart.OverviewPane} overviewPane
448      */
449     _updateBoundaries: function(overviewPane)
450     {
451         this._minimumBoundaries = overviewPane._dataProvider.minimumBoundary();
452         var totalTime = overviewPane._dataProvider.totalTime();
453         this._maximumBoundaries = this._minimumBoundaries + totalTime;
454         this._xScaleFactor = overviewPane._overviewContainer.clientWidth / totalTime;
455     },
456
457     /**
458      * @param {number} time
459      * @return {number}
460      */
461     computePosition: function(time)
462     {
463         return (time - this._minimumBoundaries) * this._xScaleFactor;
464     },
465
466     /**
467      * @param {number} value
468      * @param {number=} precision
469      * @return {string}
470      */
471     formatTime: function(value, precision)
472     {
473         return Number.secondsToString((value - this._minimumBoundaries) / 1000);
474     },
475
476     /**
477      * @return {number}
478      */
479     maximumBoundary: function()
480     {
481         return this._maximumBoundaries;
482     },
483
484     /**
485      * @return {number}
486      */
487     minimumBoundary: function()
488     {
489         return this._minimumBoundaries;
490     },
491
492     /**
493      * @return {number}
494      */
495     zeroTime: function()
496     {
497         return this._minimumBoundaries;
498     },
499
500     /**
501      * @return {number}
502      */
503     boundarySpan: function()
504     {
505         return this._maximumBoundaries - this._minimumBoundaries;
506     }
507 }
508
509 /**
510  * @constructor
511  * @extends {WebInspector.VBox}
512  * @implements {WebInspector.FlameChartDelegate}
513  * @param {!WebInspector.FlameChartDataProvider} dataProvider
514  */
515 WebInspector.CPUProfileFlameChart.OverviewPane = function(dataProvider)
516 {
517     WebInspector.VBox.call(this);
518     this.element.classList.add("flame-chart-overview-pane");
519     this._overviewContainer = this.element.createChild("div", "overview-container");
520     this._overviewGrid = new WebInspector.OverviewGrid("flame-chart");
521     this._overviewGrid.element.classList.add("fill");
522     this._overviewCanvas = this._overviewContainer.createChild("canvas", "flame-chart-overview-canvas");
523     this._overviewContainer.appendChild(this._overviewGrid.element);
524     this._overviewCalculator = new WebInspector.CPUProfileFlameChart.OverviewCalculator();
525     this._dataProvider = dataProvider;
526     this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
527 }
528
529 WebInspector.CPUProfileFlameChart.OverviewPane.prototype = {
530     /**
531      * @param {number} windowStartTime
532      * @param {number} windowEndTime
533      */
534     requestWindowTimes: function(windowStartTime, windowEndTime)
535     {
536         this._selectRange(windowStartTime, windowEndTime);
537     },
538
539     /**
540      * @param {!number} timeLeft
541      * @param {!number} timeRight
542      */
543     _selectRange: function(timeLeft, timeRight)
544     {
545         var startTime = this._dataProvider.minimumBoundary();
546         var totalTime = this._dataProvider.totalTime();
547         this._overviewGrid.setWindow((timeLeft - startTime) / totalTime, (timeRight - startTime) / totalTime);
548     },
549
550     /**
551      * @param {!WebInspector.Event} event
552      */
553     _onWindowChanged: function(event)
554     {
555         var startTime = this._dataProvider.minimumBoundary();
556         var totalTime = this._dataProvider.totalTime();
557         var data = {
558             windowTimeLeft: startTime + this._overviewGrid.windowLeft() * totalTime,
559             windowTimeRight: startTime + this._overviewGrid.windowRight() * totalTime
560         };
561         this.dispatchEventToListeners(WebInspector.OverviewGrid.Events.WindowChanged, data);
562     },
563
564     /**
565      * @return {?WebInspector.FlameChart.TimelineData}
566      */
567     _timelineData: function()
568     {
569         return this._dataProvider.timelineData();
570     },
571
572     onResize: function()
573     {
574         this._scheduleUpdate();
575     },
576
577     _scheduleUpdate: function()
578     {
579         if (this._updateTimerId)
580             return;
581         this._updateTimerId = requestAnimationFrame(this.update.bind(this));
582     },
583
584     update: function()
585     {
586         this._updateTimerId = 0;
587         var timelineData = this._timelineData();
588         if (!timelineData)
589             return;
590         this._resetCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - WebInspector.FlameChart.DividersBarHeight);
591         this._overviewCalculator._updateBoundaries(this);
592         this._overviewGrid.updateDividers(this._overviewCalculator);
593         this._drawOverviewCanvas();
594     },
595
596     _drawOverviewCanvas: function()
597     {
598         var canvasWidth = this._overviewCanvas.width;
599         var canvasHeight = this._overviewCanvas.height;
600         var drawData = this._calculateDrawData(canvasWidth);
601         var context = this._overviewCanvas.getContext("2d");
602         var ratio = window.devicePixelRatio;
603         var offsetFromBottom = ratio;
604         var lineWidth = 1;
605         var yScaleFactor = canvasHeight / (this._dataProvider.maxStackDepth() * 1.1);
606         context.lineWidth = lineWidth;
607         context.translate(0.5, 0.5);
608         context.strokeStyle = "rgba(20,0,0,0.4)";
609         context.fillStyle = "rgba(214,225,254,0.8)";
610         context.moveTo(-lineWidth, canvasHeight + lineWidth);
611         context.lineTo(-lineWidth, Math.round(canvasHeight - drawData[0] * yScaleFactor - offsetFromBottom));
612         var value;
613         for (var x = 0; x < canvasWidth; ++x) {
614             value = Math.round(canvasHeight - drawData[x] * yScaleFactor - offsetFromBottom);
615             context.lineTo(x, value);
616         }
617         context.lineTo(canvasWidth + lineWidth, value);
618         context.lineTo(canvasWidth + lineWidth, canvasHeight + lineWidth);
619         context.fill();
620         context.stroke();
621         context.closePath();
622     },
623
624     /**
625      * @param {number} width
626      * @return {!Uint8Array}
627      */
628     _calculateDrawData: function(width)
629     {
630         var dataProvider = this._dataProvider;
631         var timelineData = this._timelineData();
632         var entryStartTimes = timelineData.entryStartTimes;
633         var entryTotalTimes = timelineData.entryTotalTimes;
634         var entryLevels = timelineData.entryLevels;
635         var length = entryStartTimes.length;
636         var minimumBoundary = this._dataProvider.minimumBoundary();
637
638         var drawData = new Uint8Array(width);
639         var scaleFactor = width / dataProvider.totalTime();
640
641         for (var entryIndex = 0; entryIndex < length; ++entryIndex) {
642             var start = Math.floor((entryStartTimes[entryIndex] - minimumBoundary) * scaleFactor);
643             var finish = Math.floor((entryStartTimes[entryIndex] - minimumBoundary + entryTotalTimes[entryIndex]) * scaleFactor);
644             for (var x = start; x <= finish; ++x)
645                 drawData[x] = Math.max(drawData[x], entryLevels[entryIndex] + 1);
646         }
647         return drawData;
648     },
649
650     /**
651      * @param {!number} width
652      * @param {!number} height
653      */
654     _resetCanvas: function(width, height)
655     {
656         var ratio = window.devicePixelRatio;
657         this._overviewCanvas.width = width * ratio;
658         this._overviewCanvas.height = height * ratio;
659         this._overviewCanvas.style.width = width + "px";
660         this._overviewCanvas.style.height = height + "px";
661     },
662
663     __proto__: WebInspector.VBox.prototype
664 }