Web Inspector: implement Flame Chart for CPU profiler.
authorloislo@chromium.org <loislo@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 4 Mar 2013 12:39:49 +0000 (12:39 +0000)
committerloislo@chromium.org <loislo@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 4 Mar 2013 12:39:49 +0000 (12:39 +0000)
https://bugs.webkit.org/show_bug.cgi?id=111162

Reviewed by Yury Semikhatsky.

It is an initial implementation. The next step is to provide
function names and other stats about the hovered item.

* WebCore.gypi:
* WebCore.vcproj/WebCore.vcproj:
* WebCore.vcxproj/WebCore.vcxproj:
* WebCore.vcxproj/WebCore.vcxproj.filters:
* inspector/compile-front-end.py:
* inspector/front-end/CPUProfileView.js:
(WebInspector.CPUProfileView.prototype._getCPUProfileCallback):
* inspector/front-end/FlameChart.js: Added.
(WebInspector.FlameChart):
(WebInspector.FlameChart.prototype._onMouseMove):
(WebInspector.FlameChart.prototype.findNodeCallback):
(WebInspector.FlameChart.prototype._coordinatesToNode):
(WebInspector.FlameChart.prototype.onResize):
(WebInspector.FlameChart.prototype._rootNodes):
(WebInspector.FlameChart.prototype.draw):
(WebInspector.FlameChart.prototype._drawNode):
(WebInspector.FlameChart.prototype._forEachNode):
(WebInspector.FlameChart.prototype._drawBar):
(WebInspector.FlameChart.prototype.update):
* inspector/front-end/Settings.js:
(WebInspector.ExperimentsSettings):
* inspector/front-end/WebKit.qrc:
* inspector/front-end/flameChart.css: Added.
(.flame-chart):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@144618 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/WebCore.gypi
Source/WebCore/WebCore.vcproj/WebCore.vcproj
Source/WebCore/WebCore.vcxproj/WebCore.vcxproj
Source/WebCore/WebCore.vcxproj/WebCore.vcxproj.filters
Source/WebCore/inspector/compile-front-end.py
Source/WebCore/inspector/front-end/CPUProfileView.js
Source/WebCore/inspector/front-end/FlameChart.js [new file with mode: 0644]
Source/WebCore/inspector/front-end/Settings.js
Source/WebCore/inspector/front-end/WebKit.qrc
Source/WebCore/inspector/front-end/flameChart.css [new file with mode: 0644]

index a6c6edb..a2a3442 100644 (file)
@@ -1,3 +1,38 @@
+2013-03-04  Ilya Tikhonovsky  <loislo@chromium.org>
+
+        Web Inspector: implement Flame Chart for CPU profiler.
+        https://bugs.webkit.org/show_bug.cgi?id=111162
+
+        Reviewed by Yury Semikhatsky.
+
+        It is an initial implementation. The next step is to provide
+        function names and other stats about the hovered item.
+
+        * WebCore.gypi:
+        * WebCore.vcproj/WebCore.vcproj:
+        * WebCore.vcxproj/WebCore.vcxproj:
+        * WebCore.vcxproj/WebCore.vcxproj.filters:
+        * inspector/compile-front-end.py:
+        * inspector/front-end/CPUProfileView.js:
+        (WebInspector.CPUProfileView.prototype._getCPUProfileCallback):
+        * inspector/front-end/FlameChart.js: Added.
+        (WebInspector.FlameChart):
+        (WebInspector.FlameChart.prototype._onMouseMove):
+        (WebInspector.FlameChart.prototype.findNodeCallback):
+        (WebInspector.FlameChart.prototype._coordinatesToNode):
+        (WebInspector.FlameChart.prototype.onResize):
+        (WebInspector.FlameChart.prototype._rootNodes):
+        (WebInspector.FlameChart.prototype.draw):
+        (WebInspector.FlameChart.prototype._drawNode):
+        (WebInspector.FlameChart.prototype._forEachNode):
+        (WebInspector.FlameChart.prototype._drawBar):
+        (WebInspector.FlameChart.prototype.update):
+        * inspector/front-end/Settings.js:
+        (WebInspector.ExperimentsSettings):
+        * inspector/front-end/WebKit.qrc:
+        * inspector/front-end/flameChart.css: Added.
+        (.flame-chart):
+
 2013-03-04  Marja Hölttä  <marja@chromium.org>
 
         [V8] Add a "context type" parameter to GetTemplate and ConfigureV8SomethingTemplate functions
index a71fe47..9139e7b 100644 (file)
             'inspector/front-end/dataGrid.css',
             'inspector/front-end/elementsPanel.css',
             'inspector/front-end/filteredItemSelectionDialog.css',
+            'inspector/front-end/flameChart.css',
             'inspector/front-end/heapProfiler.css',
             'inspector/front-end/helpScreen.css',
             'inspector/front-end/indexedDBViews.css',
             'inspector/front-end/BottomUpProfileDataGridTree.js',
             'inspector/front-end/CPUProfileView.js',
             'inspector/front-end/CSSSelectorProfileView.js',
+            'inspector/front-end/FlameChart.js',
             'inspector/front-end/HeapSnapshot.js',
             'inspector/front-end/HeapSnapshotDataGrids.js',
             'inspector/front-end/HeapSnapshotGridNodes.js',
index ec42643..fa18bd6 100755 (executable)
                                        >
                                </File>
                                <File
+                                       RelativePath="..\inspector\front-end\FlameChart.js"
+                                       >
+                               </File>
+                               <File
                                        RelativePath="..\inspector\front-end\FontView.js"
                                        >
                                </File>
index c2bf7ff..44acb91 100644 (file)
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup Label="ProjectConfigurations">
     <ProjectConfiguration Include="DebugSuffix|Win32">
     <None Include="..\inspector\front-end\FileUtils.js" />
     <None Include="..\inspector\front-end\filteredItemSelectionDialog.css" />
     <None Include="..\inspector\front-end\FilteredItemSelectionDialog.js" />
+    <None Include="..\inspector\front-end\FlameChart.js" />
     <None Include="..\inspector\front-end\FontView.js" />
     <None Include="..\inspector\front-end\GoToLineDialog.js" />
     <None Include="..\inspector\front-end\HandlerRegistry.js" />
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
\ No newline at end of file
+</Project>
index 2b374ad..049b7bc 100644 (file)
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
     <Filter Include="DerivedSources">
     <None Include="..\inspector\front-end\FilteredItemSelectionDialog.js">
       <Filter>inspector\front-end</Filter>
     </None>
+    <None Include="..\inspector\front-end\FlameChart.js">
+      <Filter>inspector\front-end</Filter>
+    </None>
     <None Include="..\inspector\front-end\FontView.js">
       <Filter>inspector\front-end</Filter>
     </None>
       <Filter>rendering</Filter>
     </CustomBuildStep>
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
index e99e377..cdc70c9 100755 (executable)
@@ -337,6 +337,7 @@ modules = [
             "BottomUpProfileDataGridTree.js",
             "CPUProfileView.js",
             "CSSSelectorProfileView.js",
+            "FlameChart.js",
             "HeapSnapshot.js",
             "HeapSnapshotDataGrids.js",
             "HeapSnapshotGridNodes.js",
index c26796e..2788a3d 100644 (file)
@@ -53,7 +53,17 @@ WebInspector.CPUProfileView = function(profile)
     this.dataGrid = new WebInspector.DataGrid(columns);
     this.dataGrid.addEventListener("sorting changed", this._sortProfile, this);
     this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
-    this.dataGrid.show(this.element);
+
+    if (WebInspector.experimentsSettings.cpuFlameChart.isEnabled()) {
+        this._splitView = new WebInspector.SplitView(false, "flameChartSplitLocation");
+        this._splitView.show(this.element);
+
+        this.dataGrid.show(this._splitView.firstElement());
+
+        this.flameChart = new WebInspector.FlameChart(this);
+        this.flameChart.show(this._splitView.secondElement());
+    } else
+        this.dataGrid.show(this.element);
 
     this.viewSelectComboBox = new WebInspector.StatusBarComboBox(this._changeView.bind(this));
 
@@ -109,6 +119,8 @@ WebInspector.CPUProfileView.prototype = {
         this._assignParentsInProfile();
         this._changeView();
         this._updatePercentButton();
+        if (this.flameChart)
+            this.flameChart.update();
     },
 
     get statusBarItems()
@@ -750,3 +762,5 @@ WebInspector.CPUProfileHeader.prototype = {
 
     __proto__: WebInspector.ProfileHeader.prototype
 }
+
+importScript("FlameChart.js");
diff --git a/Source/WebCore/inspector/front-end/FlameChart.js b/Source/WebCore/inspector/front-end/FlameChart.js
new file mode 100644 (file)
index 0000000..a957d96
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2013 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.View}
+ * @param {WebInspector.CPUProfileView} cpuProfileView
+ */
+WebInspector.FlameChart = function(cpuProfileView)
+{
+    WebInspector.View.call(this);
+    this.registerRequiredCSS("flameChart.css");
+
+    this.element.className = "flame-chart";
+    this._canvas = this.element.createChild("canvas");
+    this._cpuProfileView = cpuProfileView;
+    this._xScaleFactor = 0.0;
+    this._yScaleFactor = 10;
+    this._minWidth = 3;
+    this.element.onmousemove = this._onMouseMove.bind(this);
+}
+
+WebInspector.FlameChart.prototype = {
+    _onMouseMove: function(e)
+    {
+        var node = this._coordinatesToNode(e.offsetX, e.offsetY);
+        if (node !== this._highlightedNode) {
+            this._highlightedNode = node;
+            this.update();
+        }
+    },
+
+    /**
+     * @param {!number} x
+     * @param {!number} y
+     */
+    _coordinatesToNode: function(x, y)
+    {
+        var cursorOffset = x / this._xScaleFactor;
+        var cursorLevel = (this._canvas.height - y) / this._yScaleFactor;
+        this._highlightedNode = null;
+        var cursorNode;
+
+        function findNodeCallback(offset, level, node)
+        {
+            if (cursorLevel > level && cursorLevel < level + 1 && cursorOffset > offset && cursorOffset < offset + node.totalTime)
+                cursorNode = node;
+        }
+        this._forEachNode(findNodeCallback.bind(this));
+        return cursorNode;
+    },
+
+    onResize: function()
+    {
+        this.draw(this.element.clientWidth, this.element.clientHeight);
+    },
+
+    /**
+     * @return {Array.<!ProfilerAgent.CPUProfileNode> }
+     */
+    _rootNodes: function()
+    {
+        if (this._rootNodesArray)
+            return this._rootNodesArray.slice();
+
+        var profileHead = this._cpuProfileView.profileHead;
+        if (!profileHead)
+            return null;
+        var totalTime = 0;
+        var nodes = [];
+        for (var i = 0; i < profileHead.children.length; ++i) {
+            var node = profileHead.children[i];
+            if (node.functionName === "(program)" || node.functionName === "(idle)")
+                continue;
+            totalTime += node.totalTime;
+            nodes.push(node);
+        }
+
+        this._rootNodesArray = nodes;
+        this._totalTime = totalTime;
+        return nodes.slice();
+    },
+
+    /**
+     * @param {!number} height
+     * @param {!number} width
+     */
+    draw: function(width, height)
+    {
+        if (!this._rootNodes())
+            return;
+
+        var margin = 0;
+        this._canvas.height = height - margin;
+        this._canvas.width = width - margin;
+
+        this._xScaleFactor = width / this._totalTime;
+        this._colorIndex = 0;
+
+        this._context = this._canvas.getContext("2d");
+
+        this._forEachNode(this._drawNode.bind(this));
+    },
+
+    _drawNode: function(offset, level, node)
+    {
+        ++this._colorIndex;
+        var hue = (this._colorIndex * 2 + 11 * (this._colorIndex % 2)) % 360;
+        var lightness = this._highlightedNode === node ? 33 : 67;
+        var color = "hsl(" + hue + ", 100%, " + lightness + "%)";
+        this._drawBar(this._context, offset, level, node, color);
+    },
+
+    _forEachNode: function(callback)
+    {
+        var nodes = this._rootNodes();
+        var levelOffsets = /** @type {Array.<!number>} */ [0];
+        var levelExitIndexes = [0];
+
+        while (nodes.length) {
+            var level = levelOffsets.length - 1;
+            var node = nodes.pop();
+            if (node.totalTime * this._xScaleFactor > this._minWidth) {
+                var offset = levelOffsets[level];
+                callback(offset, level, node);
+                levelOffsets[level] += node.totalTime;
+                if (node.children.length) {
+                    levelExitIndexes.push(nodes.length);
+                    levelOffsets.push(offset + node.selfTime / 2);
+                    nodes = nodes.concat(node.children);
+                }
+            }
+            while (nodes.length === levelExitIndexes[levelExitIndexes.length - 1]) {
+                levelOffsets.pop();
+                levelExitIndexes.pop();
+            }
+        }
+    },
+
+    /**
+     * @param {Object} context
+     * @param {number} offset
+     * @param {number} level
+     * @param {!ProfilerAgent.CPUProfileNode} node
+     * @param {!WebInspector.Color} hslColor
+     */
+    _drawBar: function(context, offset, level, node, hslColor)
+    {
+        var width = node.totalTime * this._xScaleFactor;
+        var height = this._yScaleFactor;
+        var x = offset * this._xScaleFactor;
+        var y = this._canvas.height - level * this._yScaleFactor - height;
+        context.beginPath();
+        context.rect(x, y, width - 1, height - 1);
+        context.fillStyle = hslColor;
+        context.fill();
+    },
+
+    update: function()
+    {
+        this.draw(this.element.clientWidth, this.element.clientHeight);
+    },
+
+    __proto__: WebInspector.View.prototype
+};
+
+//@ sourceURL=http://localhost/inspector/front-end/FlameChart.js
index c694b89..4edfa9d 100644 (file)
@@ -217,6 +217,7 @@ WebInspector.ExperimentsSettings = function()
     this.showWhitespaceInEditor = this._createExperiment("showWhitespaceInEditor", "Show whitespace characters in editor");
     this.textEditorSmartBraces = this._createExperiment("textEditorSmartBraces", "Enable smart braces in text editor");
     this.separateProfilers = this._createExperiment("separateProfilers", "Separate profiler tools");
+    this.cpuFlameChart = this._createExperiment("cpuFlameChart", "Show Flame Chart in CPU Profiler");
 
     this._cleanUpSetting();
 }
index 35abe2b..0b6eca8 100644 (file)
@@ -83,6 +83,7 @@
     <file>FileSystemProjectDelegate.js</file>
     <file>FileUtils.js</file>
     <file>FilteredItemSelectionDialog.js</file>
+    <file>FlameChart.js</file>
     <file>FontView.js</file>
     <file>GoToLineDialog.js</file>
     <file>HAREntry.js</file>
     <file>dialog.css</file>
     <file>elementsPanel.css</file>
     <file>filteredItemSelectionDialog.css</file>
+    <file>flameChart.css</file>
     <file>heapProfiler.css</file>
     <file>helpScreen.css</file>
     <file>indexedDBViews.css</file>
diff --git a/Source/WebCore/inspector/front-end/flameChart.css b/Source/WebCore/inspector/front-end/flameChart.css
new file mode 100644 (file)
index 0000000..6776a64
--- /dev/null
@@ -0,0 +1,4 @@
+.flame-chart {
+    height: 100%;
+    overflow: hidden;
+}