Index: Source/devtools/front_end/CPUProfileFlameChart.js |
diff --git a/Source/devtools/front_end/CPUProfileFlameChart.js b/Source/devtools/front_end/CPUProfileFlameChart.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..eac4cc5e9dbfa389b5f2e2431e2246b440a1f2c1 |
--- /dev/null |
+++ b/Source/devtools/front_end/CPUProfileFlameChart.js |
@@ -0,0 +1,362 @@ |
+/** |
+ * Copyright (C) 2014 Google Inc. All rights reserved. |
+ * |
+ * Redistribution and use in source and binary forms, with or without |
+ * modification, are permitted provided that the following conditions are |
+ * met: |
+ * |
+ * * Redistributions of source code must retain the above copyright |
+ * notice, this list of conditions and the following disclaimer. |
+ * * Redistributions in binary form must reproduce the above |
+ * copyright notice, this list of conditions and the following disclaimer |
+ * in the documentation and/or other materials provided with the |
+ * distribution. |
+ * * Neither the name of Google Inc. nor the names of its |
+ * contributors may be used to endorse or promote products derived from |
+ * this software without specific prior written permission. |
+ * |
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
+ */ |
+ |
+/** |
+ * @constructor |
+ * @extends {WebInspector.VBox} |
+ * @param {!WebInspector.FlameChartDataProvider} dataProvider |
+ */ |
+WebInspector.CPUProfileFlameChart = function(dataProvider) |
+{ |
+ WebInspector.VBox.call(this); |
+ this.registerRequiredCSS("flameChart.css"); |
+ this.element.id = "cpu-flame-chart"; |
+ |
+ this._overviewPane = new WebInspector.CPUProfileFlameChart.OverviewPane(dataProvider); |
+ this._overviewPane.show(this.element); |
+ |
+ this._mainPane = new WebInspector.FlameChart(dataProvider, this._overviewPane, true, false); |
+ this._mainPane.show(this.element); |
+ this._mainPane.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this); |
+ this._overviewPane._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this); |
+} |
+ |
+WebInspector.CPUProfileFlameChart.prototype = { |
+ /** |
+ * @param {!WebInspector.Event} event |
+ */ |
+ _onWindowChanged: function(event) |
+ { |
+ this._mainPane.changeWindow(this._overviewPane._overviewGrid.windowLeft(), this._overviewPane._overviewGrid.windowRight()); |
+ }, |
+ |
+ /** |
+ * @param {!number} timeLeft |
+ * @param {!number} timeRight |
+ */ |
+ selectRange: function(timeLeft, timeRight) |
+ { |
+ this._overviewPane._selectRange(timeLeft, timeRight); |
+ }, |
+ |
+ /** |
+ * @param {!WebInspector.Event} event |
+ */ |
+ _onEntrySelected: function(event) |
+ { |
+ this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, event.data); |
+ }, |
+ |
+ update: function() |
+ { |
+ this._overviewPane.update(); |
+ this._mainPane.update(); |
+ }, |
+ |
+ __proto__: WebInspector.VBox.prototype |
+}; |
+ |
+/** |
+ * @constructor |
+ * @implements {WebInspector.TimelineGrid.Calculator} |
+ */ |
+WebInspector.CPUProfileFlameChart.OverviewCalculator = function() |
+{ |
+} |
+ |
+WebInspector.CPUProfileFlameChart.OverviewCalculator.prototype = { |
+ /** |
+ * @return {number} |
+ */ |
+ paddingLeft: function() |
+ { |
+ return 0; |
+ }, |
+ |
+ /** |
+ * @param {!WebInspector.CPUProfileFlameChart.OverviewPane} overviewPane |
+ */ |
+ _updateBoundaries: function(overviewPane) |
+ { |
+ this._minimumBoundaries = 0; |
+ var totalTime = overviewPane._dataProvider.totalTime(); |
+ this._maximumBoundaries = totalTime; |
+ this._xScaleFactor = overviewPane._overviewCanvas.width / totalTime; |
+ }, |
+ |
+ /** |
+ * @param {number} time |
+ * @return {number} |
+ */ |
+ computePosition: function(time) |
+ { |
+ return (time - this._minimumBoundaries) * this._xScaleFactor; |
+ }, |
+ |
+ /** |
+ * @param {number} value |
+ * @param {number=} precision |
+ * @return {string} |
+ */ |
+ formatTime: function(value, precision) |
+ { |
+ return Number.secondsToString((value + this._minimumBoundaries) / 1000); |
+ }, |
+ |
+ /** |
+ * @return {number} |
+ */ |
+ maximumBoundary: function() |
+ { |
+ return this._maximumBoundaries; |
+ }, |
+ |
+ /** |
+ * @return {number} |
+ */ |
+ minimumBoundary: function() |
+ { |
+ return this._minimumBoundaries; |
+ }, |
+ |
+ /** |
+ * @return {number} |
+ */ |
+ zeroTime: function() |
+ { |
+ return this._minimumBoundaries; |
+ }, |
+ |
+ /** |
+ * @return {number} |
+ */ |
+ boundarySpan: function() |
+ { |
+ return this._maximumBoundaries - this._minimumBoundaries; |
+ } |
+} |
+ |
+/** |
+ * @constructor |
+ */ |
+WebInspector.CPUProfileFlameChart.ColorGenerator = function() |
+{ |
+ this._colors = {}; |
+ this._currentColorIndex = 0; |
+} |
+ |
+WebInspector.CPUProfileFlameChart.ColorGenerator.prototype = { |
+ /** |
+ * @param {string} id |
+ * @param {string|!CanvasGradient} color |
+ */ |
+ setColorForID: function(id, color) |
+ { |
+ this._colors[id] = color; |
+ }, |
+ |
+ /** |
+ * @param {!string} id |
+ * @param {number=} sat |
+ * @return {!string} |
+ */ |
+ colorForID: function(id, sat) |
+ { |
+ if (typeof sat !== "number") |
+ sat = 100; |
+ var color = this._colors[id]; |
+ if (!color) { |
+ color = this._createColor(this._currentColorIndex++, sat); |
+ this._colors[id] = color; |
+ } |
+ return color; |
+ }, |
+ |
+ /** |
+ * @param {!number} index |
+ * @param {!number} sat |
+ */ |
+ _createColor: function(index, sat) |
+ { |
+ var hue = (index * 7 + 12 * (index % 2)) % 360; |
+ return "hsla(" + hue + ", " + sat + "%, 66%, 0.7)"; |
+ } |
+} |
+ |
+/** |
+ * @constructor |
+ * @extends {WebInspector.VBox} |
+ * @implements {WebInspector.FlameChartDelegate} |
+ * @param {!WebInspector.FlameChartDataProvider} dataProvider |
+ */ |
+WebInspector.CPUProfileFlameChart.OverviewPane = function(dataProvider) |
+{ |
+ WebInspector.VBox.call(this); |
+ this.element.classList.add("flame-chart-overview-pane"); |
+ this._overviewContainer = this.element.createChild("div", "overview-container"); |
+ this._overviewGrid = new WebInspector.OverviewGrid("flame-chart"); |
+ this._overviewGrid.element.classList.add("fill"); |
+ this._overviewCanvas = this._overviewContainer.createChild("canvas", "flame-chart-overview-canvas"); |
+ this._overviewContainer.appendChild(this._overviewGrid.element); |
+ this._overviewCalculator = new WebInspector.CPUProfileFlameChart.OverviewCalculator(); |
+ this._dataProvider = dataProvider; |
+} |
+ |
+WebInspector.CPUProfileFlameChart.OverviewPane.prototype = { |
+ /** |
+ * @param {number} windowStartTime |
+ * @param {number} windowEndTime |
+ */ |
+ requestWindowTimes: function(windowStartTime, windowEndTime) |
+ { |
+ this._overviewGrid.setWindow(windowStartTime / this._dataProvider.totalTime(), windowEndTime / this._dataProvider.totalTime()); |
+ }, |
+ |
+ /** |
+ * @param {!number} timeLeft |
+ * @param {!number} timeRight |
+ */ |
+ _selectRange: function(timeLeft, timeRight) |
+ { |
+ this._overviewGrid.setWindow(timeLeft / this._dataProvider.totalTime(), timeRight / this._dataProvider.totalTime()); |
+ }, |
+ |
+ /** |
+ * @return {?WebInspector.FlameChart.TimelineData} |
+ */ |
+ _timelineData: function() |
+ { |
+ return this._dataProvider.timelineData(); |
+ }, |
+ |
+ onResize: function() |
+ { |
+ this._scheduleUpdate(); |
+ }, |
+ |
+ _scheduleUpdate: function() |
+ { |
+ if (this._updateTimerId) |
+ return; |
+ this._updateTimerId = requestAnimationFrame(this.update.bind(this)); |
+ }, |
+ |
+ update: function() |
+ { |
+ this._updateTimerId = 0; |
+ var timelineData = this._timelineData(); |
+ if (!timelineData) |
+ return; |
+ this._resetCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - WebInspector.FlameChart.DividersBarHeight); |
+ this._overviewCalculator._updateBoundaries(this); |
+ this._overviewGrid.updateDividers(this._overviewCalculator); |
+ WebInspector.CPUProfileFlameChart.OverviewPane.drawOverviewCanvas( |
+ this._dataProvider, |
+ timelineData, |
+ this._overviewCanvas.getContext("2d"), |
+ this._overviewContainer.clientWidth, |
+ this._overviewContainer.clientHeight - WebInspector.FlameChart.DividersBarHeight |
+ ); |
+ }, |
+ |
+ /** |
+ * @param {!number} width |
+ * @param {!number} height |
+ */ |
+ _resetCanvas: function(width, height) |
+ { |
+ var ratio = window.devicePixelRatio; |
+ this._overviewCanvas.width = width * ratio; |
+ this._overviewCanvas.height = height * ratio; |
+ }, |
+ |
+ __proto__: WebInspector.VBox.prototype |
+} |
+ |
+/** |
+ * @param {!WebInspector.FlameChartDataProvider} dataProvider |
+ * @param {!WebInspector.FlameChart.TimelineData} timelineData |
+ * @param {!number} width |
+ */ |
+WebInspector.CPUProfileFlameChart.OverviewPane.calculateDrawData = function(dataProvider, timelineData, width) |
+{ |
+ var entryOffsets = timelineData.entryOffsets; |
+ var entryTotalTimes = timelineData.entryTotalTimes; |
+ var entryLevels = timelineData.entryLevels; |
+ var length = entryOffsets.length; |
+ |
+ var drawData = new Uint8Array(width); |
+ var scaleFactor = width / dataProvider.totalTime(); |
+ |
+ for (var entryIndex = 0; entryIndex < length; ++entryIndex) { |
+ var start = Math.floor(entryOffsets[entryIndex] * scaleFactor); |
+ var finish = Math.floor((entryOffsets[entryIndex] + entryTotalTimes[entryIndex]) * scaleFactor); |
+ for (var x = start; x <= finish; ++x) |
+ drawData[x] = Math.max(drawData[x], entryLevels[entryIndex] + 1); |
+ } |
+ return drawData; |
+} |
+ |
+/** |
+ * @param {!WebInspector.FlameChartDataProvider} dataProvider |
+ * @param {!WebInspector.FlameChart.TimelineData} timelineData |
+ * @param {!Object} context |
+ * @param {!number} width |
+ * @param {!number} height |
+ */ |
+WebInspector.CPUProfileFlameChart.OverviewPane.drawOverviewCanvas = function(dataProvider, timelineData, context, width, height) |
+{ |
+ var drawData = WebInspector.CPUProfileFlameChart.OverviewPane.calculateDrawData(dataProvider, timelineData, width); |
+ if (!drawData) |
+ return; |
+ |
+ var ratio = window.devicePixelRatio; |
+ var canvasWidth = width * ratio; |
+ var canvasHeight = height * ratio; |
+ |
+ var yScaleFactor = canvasHeight / (dataProvider.maxStackDepth() * 1.1); |
+ context.lineWidth = 1; |
+ context.translate(0.5, 0.5); |
+ context.strokeStyle = "rgba(20,0,0,0.4)"; |
+ context.fillStyle = "rgba(214,225,254,0.8)"; |
+ context.moveTo(-1, canvasHeight - 1); |
+ if (drawData) |
+ context.lineTo(-1, Math.round(height - drawData[0] * yScaleFactor - 1)); |
+ var value; |
+ for (var x = 0; x < width; ++x) { |
+ value = Math.round(canvasHeight - drawData[x] * yScaleFactor - 1); |
+ context.lineTo(x * ratio, value); |
+ } |
+ context.lineTo(canvasWidth + 1, value); |
+ context.lineTo(canvasWidth + 1, canvasHeight - 1); |
+ context.fill(); |
+ context.stroke(); |
+ context.closePath(); |
+} |