2 * Copyright (C) 2014 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
34 * @implements {WebInspector.FlameChartDataProvider}
35 * @param {!WebInspector.CPUProfileDataModel} cpuProfile
36 * @param {!WeakReference.<!WebInspector.Target>} weakTarget
38 WebInspector.CPUFlameChartDataProvider = function(cpuProfile, weakTarget)
40 WebInspector.FlameChartDataProvider.call(this);
41 this._cpuProfile = cpuProfile;
42 this._weakTarget = weakTarget;
43 this._colorGenerator = WebInspector.CPUFlameChartDataProvider.colorGenerator();
46 WebInspector.CPUFlameChartDataProvider.prototype = {
58 textBaseline: function()
66 textPadding: function()
72 * @param {number} startTime
73 * @param {number} endTime
74 * @return {?Array.<number>}
76 dividerOffsets: function(startTime, endTime)
84 minimumBoundary: function()
86 return this._cpuProfile.profileStartTime;
94 return this._cpuProfile.profileHead.totalTime;
100 maxStackDepth: function()
102 return this._maxStackDepth;
106 * @return {?WebInspector.FlameChart.TimelineData}
108 timelineData: function()
110 return this._timelineData || this._calculateTimelineData();
114 * @param {number} index
117 markerColor: function(index)
119 throw new Error("Unreachable.");
123 * @param {number} index
126 markerTitle: function(index)
128 throw new Error("Unreachable.");
132 * @return {?WebInspector.FlameChart.TimelineData}
134 _calculateTimelineData: function()
138 * @param {number} depth
139 * @param {number} duration
140 * @param {number} startTime
141 * @param {number} selfTime
142 * @param {!ProfilerAgent.CPUProfileNode} node
144 function ChartEntry(depth, duration, startTime, selfTime, node)
147 this.duration = duration;
148 this.startTime = startTime;
149 this.selfTime = selfTime;
153 /** @type {!Array.<?ChartEntry>} */
155 /** @type {!Array.<number>} */
159 function onOpenFrame()
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.
166 function onCloseFrame(depth, node, startTime, totalTime, selfTime)
168 var index = stack.pop();
169 entries[index] = new ChartEntry(depth, totalTime, startTime, selfTime, node);
170 maxDepth = Math.max(maxDepth, depth);
172 this._cpuProfile.forEachFrame(onOpenFrame, onCloseFrame);
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();
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;
191 this._maxStackDepth = maxDepth;
193 this._timelineData = new WebInspector.FlameChart.TimelineData(entryLevels, entryTotalTimes, entryStartTimes);
195 /** @type {!Array.<!ProfilerAgent.CPUProfileNode>} */
196 this._entryNodes = entryNodes;
197 this._entrySelfTimes = entrySelfTimes;
199 return this._timelineData;
206 _millisecondsToString: function(ms)
211 return WebInspector.UIString("%.1f\u2009ms", ms);
212 return Number.secondsToString(ms / 1000, true);
216 * @param {number} entryIndex
217 * @return {?Array.<!{title: string, text: string}>}
219 prepareHighlightedEntryInfo: function(entryIndex)
221 var timelineData = this._timelineData;
222 var node = this._entryNodes[entryIndex];
227 function pushEntryInfoRow(title, text)
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);
253 * @param {number} entryIndex
256 canJumpToEntry: function(entryIndex)
258 return this._entryNodes[entryIndex].scriptId !== "0";
262 * @param {number} entryIndex
265 entryTitle: function(entryIndex)
267 var node = this._entryNodes[entryIndex];
268 return WebInspector.CPUProfileDataModel.beautifyFunctionName(node.functionName);
272 * @param {number} entryIndex
275 entryFont: function(entryIndex)
278 this._font = (this.barHeight() - 4) + "px " + WebInspector.fontFamily();
279 this._boldFont = "bold " + this._font;
281 var node = this._entryNodes[entryIndex];
282 var reason = node.deoptReason;
283 return (reason && reason !== "no reason") ? this._boldFont : this._font;
287 * @param {number} entryIndex
290 entryColor: function(entryIndex)
292 var node = this._entryNodes[entryIndex];
293 return this._colorGenerator.colorForID(node.functionName + ":" + node.url);
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
307 decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, timeToPosition)
313 * @param {number} entryIndex
316 forceDecoration: function(entryIndex)
322 * @param {number} entryIndex
323 * @return {!{startTime: number, endTime: number}}
325 highlightTimeRange: function(entryIndex)
327 var startTime = this._timelineData.entryStartTimes[entryIndex];
329 startTime: startTime,
330 endTime: startTime + this._timelineData.entryTotalTimes[entryIndex]
337 paddingLeft: function()
343 * @param {number} entryIndex
346 textColor: function(entryIndex)
354 * @return {!WebInspector.FlameChart.ColorGenerator}
356 WebInspector.CPUFlameChartDataProvider.colorGenerator = function()
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;
368 return WebInspector.CPUFlameChartDataProvider._colorGenerator;
374 * @extends {WebInspector.VBox}
375 * @param {!WebInspector.FlameChartDataProvider} dataProvider
377 WebInspector.CPUProfileFlameChart = function(dataProvider)
379 WebInspector.VBox.call(this);
380 this.registerRequiredCSS("flameChart.css");
381 this.element.id = "cpu-flame-chart";
383 this._overviewPane = new WebInspector.CPUProfileFlameChart.OverviewPane(dataProvider);
384 this._overviewPane.show(this.element);
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);
392 WebInspector.CPUProfileFlameChart.prototype = {
394 * @param {!WebInspector.Event} event
396 _onWindowChanged: function(event)
398 var windowLeft = event.data.windowTimeLeft;
399 var windowRight = event.data.windowTimeRight;
400 this._mainPane.setWindowTimes(windowLeft, windowRight);
404 * @param {!number} timeLeft
405 * @param {!number} timeRight
407 selectRange: function(timeLeft, timeRight)
409 this._overviewPane._selectRange(timeLeft, timeRight);
413 * @param {!WebInspector.Event} event
415 _onEntrySelected: function(event)
417 this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, event.data);
422 this._overviewPane.update();
423 this._mainPane.update();
426 __proto__: WebInspector.VBox.prototype
431 * @implements {WebInspector.TimelineGrid.Calculator}
433 WebInspector.CPUProfileFlameChart.OverviewCalculator = function()
437 WebInspector.CPUProfileFlameChart.OverviewCalculator.prototype = {
441 paddingLeft: function()
447 * @param {!WebInspector.CPUProfileFlameChart.OverviewPane} overviewPane
449 _updateBoundaries: function(overviewPane)
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;
458 * @param {number} time
461 computePosition: function(time)
463 return (time - this._minimumBoundaries) * this._xScaleFactor;
467 * @param {number} value
468 * @param {number=} precision
471 formatTime: function(value, precision)
473 return Number.secondsToString((value - this._minimumBoundaries) / 1000);
479 maximumBoundary: function()
481 return this._maximumBoundaries;
487 minimumBoundary: function()
489 return this._minimumBoundaries;
497 return this._minimumBoundaries;
503 boundarySpan: function()
505 return this._maximumBoundaries - this._minimumBoundaries;
511 * @extends {WebInspector.VBox}
512 * @implements {WebInspector.FlameChartDelegate}
513 * @param {!WebInspector.FlameChartDataProvider} dataProvider
515 WebInspector.CPUProfileFlameChart.OverviewPane = function(dataProvider)
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);
529 WebInspector.CPUProfileFlameChart.OverviewPane.prototype = {
531 * @param {number} windowStartTime
532 * @param {number} windowEndTime
534 requestWindowTimes: function(windowStartTime, windowEndTime)
536 this._selectRange(windowStartTime, windowEndTime);
540 * @param {!number} timeLeft
541 * @param {!number} timeRight
543 _selectRange: function(timeLeft, timeRight)
545 var startTime = this._dataProvider.minimumBoundary();
546 var totalTime = this._dataProvider.totalTime();
547 this._overviewGrid.setWindow((timeLeft - startTime) / totalTime, (timeRight - startTime) / totalTime);
551 * @param {!WebInspector.Event} event
553 _onWindowChanged: function(event)
555 var startTime = this._dataProvider.minimumBoundary();
556 var totalTime = this._dataProvider.totalTime();
558 windowTimeLeft: startTime + this._overviewGrid.windowLeft() * totalTime,
559 windowTimeRight: startTime + this._overviewGrid.windowRight() * totalTime
561 this.dispatchEventToListeners(WebInspector.OverviewGrid.Events.WindowChanged, data);
565 * @return {?WebInspector.FlameChart.TimelineData}
567 _timelineData: function()
569 return this._dataProvider.timelineData();
574 this._scheduleUpdate();
577 _scheduleUpdate: function()
579 if (this._updateTimerId)
581 this._updateTimerId = requestAnimationFrame(this.update.bind(this));
586 this._updateTimerId = 0;
587 var timelineData = this._timelineData();
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();
596 _drawOverviewCanvas: function()
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;
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));
613 for (var x = 0; x < canvasWidth; ++x) {
614 value = Math.round(canvasHeight - drawData[x] * yScaleFactor - offsetFromBottom);
615 context.lineTo(x, value);
617 context.lineTo(canvasWidth + lineWidth, value);
618 context.lineTo(canvasWidth + lineWidth, canvasHeight + lineWidth);
625 * @param {number} width
626 * @return {!Uint8Array}
628 _calculateDrawData: function(width)
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();
638 var drawData = new Uint8Array(width);
639 var scaleFactor = width / dataProvider.totalTime();
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);
651 * @param {!number} width
652 * @param {!number} height
654 _resetCanvas: function(width, height)
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";
663 __proto__: WebInspector.VBox.prototype