Title: [146587] trunk/Source/WebCore
Revision
146587
Author
[email protected]
Date
2013-03-22 04:00:50 -0700 (Fri, 22 Mar 2013)

Log Message

Web Inspector: use individual samples to construct CPU profile flame chart
https://bugs.webkit.org/show_bug.cgi?id=112569

Reviewed by Pavel Feldman.

Added an optional array of samples to the CPU profile returned over the
protocol. Each sample is id of the profile node corresponding to the top frame
of the sample's stack trace. The array of samples if present is used to draw
the flame chart on the CPU profile panel, otherwise flame chart is drawn based
on the aggregated profile data.

* bindings/js/ScriptProfile.cpp:
(WebCore::ScriptProfile::buildInspectorObjectForSamples):
* bindings/js/ScriptProfile.h:
(ScriptProfile):
* bindings/v8/ScriptProfile.cpp:
(WebCore::buildInspectorObjectFor):
(WebCore):
(WebCore::ScriptProfile::buildInspectorObjectForSamples):
* bindings/v8/ScriptProfile.h:
(ScriptProfile):
* bindings/v8/ScriptProfiler.cpp:
(WebCore::ScriptProfiler::start):
* inspector/Inspector.json:
* inspector/InspectorProfilerAgent.cpp:
(WebCore::InspectorProfilerAgent::getCPUProfile):
* inspector/front-end/CPUProfileView.js:
(WebInspector.CPUProfileView.prototype._getCPUProfileCallback):
(WebInspector.CPUProfileView.prototype._buildIdToNodeMap):
* inspector/front-end/FlameChart.js:
(WebInspector.FlameChart.prototype._calculateTimelineData):

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (146586 => 146587)


--- trunk/Source/WebCore/ChangeLog	2013-03-22 10:34:33 UTC (rev 146586)
+++ trunk/Source/WebCore/ChangeLog	2013-03-22 11:00:50 UTC (rev 146587)
@@ -1,3 +1,37 @@
+2013-03-18  Yury Semikhatsky  <[email protected]>
+
+        Web Inspector: use individual samples to construct CPU profile flame chart
+        https://bugs.webkit.org/show_bug.cgi?id=112569
+
+        Reviewed by Pavel Feldman.
+
+        Added an optional array of samples to the CPU profile returned over the
+        protocol. Each sample is id of the profile node corresponding to the top frame
+        of the sample's stack trace. The array of samples if present is used to draw
+        the flame chart on the CPU profile panel, otherwise flame chart is drawn based
+        on the aggregated profile data.
+
+        * bindings/js/ScriptProfile.cpp:
+        (WebCore::ScriptProfile::buildInspectorObjectForSamples):
+        * bindings/js/ScriptProfile.h:
+        (ScriptProfile):
+        * bindings/v8/ScriptProfile.cpp:
+        (WebCore::buildInspectorObjectFor):
+        (WebCore):
+        (WebCore::ScriptProfile::buildInspectorObjectForSamples):
+        * bindings/v8/ScriptProfile.h:
+        (ScriptProfile):
+        * bindings/v8/ScriptProfiler.cpp:
+        (WebCore::ScriptProfiler::start):
+        * inspector/Inspector.json:
+        * inspector/InspectorProfilerAgent.cpp:
+        (WebCore::InspectorProfilerAgent::getCPUProfile):
+        * inspector/front-end/CPUProfileView.js:
+        (WebInspector.CPUProfileView.prototype._getCPUProfileCallback):
+        (WebInspector.CPUProfileView.prototype._buildIdToNodeMap):
+        * inspector/front-end/FlameChart.js:
+        (WebInspector.FlameChart.prototype._calculateTimelineData):
+
 2013-03-22  Steve Block  <[email protected]>
 
         Use generated bindings for the Coordinates type used by Geolocation

Modified: trunk/Source/WebCore/inspector/Inspector.json (146586 => 146587)


--- trunk/Source/WebCore/inspector/Inspector.json	2013-03-22 10:34:33 UTC (rev 146586)
+++ trunk/Source/WebCore/inspector/Inspector.json	2013-03-22 11:00:50 UTC (rev 146587)
@@ -3106,7 +3106,8 @@
                     { "name": "numberOfCalls", "type": "integer", "description": "Number of calls." },
                     { "name": "visible", "type": "boolean", "description": "Visibility." },
                     { "name": "callUID", "type": "number", "description": "Call UID." },
-                    { "name": "children", "type": "array", "items": { "$ref": "CPUProfileNode" }, "description": "Child nodes." }
+                    { "name": "children", "type": "array", "items": { "$ref": "CPUProfileNode" }, "description": "Child nodes." },
+                    { "name": "id", "optional": true, "type": "integer", "description": "Unique id of the node." }
                 ]
             },
             {
@@ -3115,7 +3116,8 @@
                 "description": "Profile.",
                 "properties": [
                     { "name": "head", "$ref": "CPUProfileNode", "optional": true },
-                    { "name": "idleTime", "type": "number", "optional": true }
+                    { "name": "idleTime", "type": "number", "optional": true },
+                    { "name": "samples", "optional": true, "type": "array", "items": { "type": "integer" }, "description": "Ids of samples top nodes." }
                 ]
             },
             {

Modified: trunk/Source/WebCore/inspector/front-end/CPUProfileView.js (146586 => 146587)


--- trunk/Source/WebCore/inspector/front-end/CPUProfileView.js	2013-03-22 10:34:33 UTC (rev 146586)
+++ trunk/Source/WebCore/inspector/front-end/CPUProfileView.js	2013-03-22 11:00:50 UTC (rev 146587)
@@ -122,11 +122,14 @@
             return;
         }
         this.profileHead = profile.head;
+        this.samples = profile.samples;
 
         if (profile.idleTime)
             this._injectIdleTimeNode(profile);
 
         this._assignParentsInProfile();
+        if (this.samples)
+            this._buildIdToNodeMap();
         this._changeView();
         this._updatePercentButton();
         if (this.flameChart)
@@ -552,6 +555,18 @@
         }
     },
 
+    _buildIdToNodeMap: function()
+    {
+        var idToNode = this._idToNode = {};
+        var stack = [this.profileHead];
+        while (stack.length) {
+            var node = stack.pop();
+            idToNode[node.id] = node;
+            for (var i = 0; i < node.children.length; i++)
+                stack.push(node.children[i]);
+        }
+    },
+
     /**
      * @param {ProfilerAgent.CPUProfile} profile
      */

Modified: trunk/Source/WebCore/inspector/front-end/FlameChart.js (146586 => 146587)


--- trunk/Source/WebCore/inspector/front-end/FlameChart.js	2013-03-22 10:34:33 UTC (rev 146586)
+++ trunk/Source/WebCore/inspector/front-end/FlameChart.js	2013-03-22 11:00:50 UTC (rev 146587)
@@ -214,37 +214,22 @@
         this._isDragging = false;
     },
 
-    _nodeCount: function()
+    _calculateTimelineData: function()
     {
-        var nodes = this._cpuProfileView.profileHead.children.slice();
+        if (this._cpuProfileView.samples)
+            return this._calculateTimelineDataForSamples();
 
-        var nodeCount = 0;
-        while (nodes.length) {
-            var node = nodes.pop();
-            ++nodeCount;
-            nodes = nodes.concat(node.children);
-        }
-        return nodeCount;
-    },
-
-    _calculateTimelineData: function()
-    {
         if (this._timelineData)
             return this._timelineData;
 
         if (!this._cpuProfileView.profileHead)
             return null;
 
-        var nodeCount = this._nodeCount();
         var functionColorPairs = { };
         var currentColorIndex = 0;
 
-        var startTimes = new Float32Array(nodeCount);
-        var durations = new Float32Array(nodeCount);
-        var depths = new Uint8Array(nodeCount);
-        var nodes = new Array(nodeCount);
-        var colorPairs = new Array(nodeCount);
         var index = 0;
+        var entries = [];
 
         function appendReversedArray(toArray, fromArray)
         {
@@ -271,12 +256,15 @@
                 functionColorPairs[functionUID] = colorPair = {highlighted: "hsl(" + hue + ", 100%, 33%)", normal: "hsl(" + hue + ", 100%, 66%)"};
             }
 
-            colorPairs[index] = colorPair;
-            depths[index] = level;
-            durations[index] = node.totalTime;
-            startTimes[index] = offset;
-            nodes[index] = node;
+            entries.push({
+                colorPair: colorPair,
+                depth: level,
+                duration: node.totalTime,
+                startTime: offset,
+                node: node
+            });
 
+
             ++index;
 
             levelOffsets[level] += node.totalTime;
@@ -293,18 +281,88 @@
         }
 
         this._timelineData = {
-            nodeCount: nodeCount,
+            entries: entries,
             totalTime: this._cpuProfileView.profileHead.totalTime,
-            startTimes: startTimes,
-            durations: durations,
-            depths: depths,
-            colorPairs: colorPairs,
-            nodes: nodes
         }
 
         return this._timelineData;
     },
 
+    _calculateTimelineDataForSamples: function()
+    {
+        if (this._timelineData)
+            return this._timelineData;
+
+        if (!this._cpuProfileView.profileHead)
+            return null;
+
+        var samples = this._cpuProfileView.samples;
+        var idToNode = this._cpuProfileView._idToNode;
+        var samplesCount = samples.length;
+        var functionColorPairs = { };
+        var currentColorIndex = 0;
+
+        var index = 0;
+        var entries = [];
+
+        var openIntervals = [];
+        var stackTrace = [];
+        for (var sampleIndex = 0; sampleIndex < samplesCount; sampleIndex++) {
+            var node = idToNode[samples[sampleIndex]];
+            stackTrace.length = 0;
+            while (node) {
+                stackTrace.push(node);
+                node = node.parent;
+            }
+            stackTrace.pop(); // Remove (root) node
+
+            var depth = 0;
+            node = stackTrace.pop();
+            while (node && depth < openIntervals.length && node === openIntervals[depth].node) {
+                var intervalIndex = openIntervals[depth].index;
+                entries[intervalIndex].duration += 1;
+                node = stackTrace.pop();
+                ++depth;
+            }
+            if (depth < openIntervals.length)
+                openIntervals.length = depth;
+            if (!node)
+                continue;
+
+            while (node) {
+                var functionUID = node.functionName + ":" + node.url + ":" + node.lineNumber;
+                var colorPair = functionColorPairs[functionUID];
+                if (!colorPair) {
+                    ++currentColorIndex;
+                    var hue = (currentColorIndex * 5 + 11 * (currentColorIndex % 2)) % 360;
+                    functionColorPairs[functionUID] = colorPair = {highlighted: "hsl(" + hue + ", 100%, 33%)", normal: "hsl(" + hue + ", 100%, 66%)"};
+                }
+
+                entries.push({
+                    colorPair: colorPair,
+                    depth: depth,
+                    duration: 1,
+                    startTime: sampleIndex,
+                    node: node
+                });
+
+                openIntervals.push({node: node, index: index});
+
+                ++index;
+
+                node = stackTrace.pop();
+                ++depth;
+            }
+        }
+
+        this._timelineData = {
+            entries: entries,
+            totalTime: samplesCount,
+        };
+
+        return this._timelineData;
+    },
+
     _getPopoverAnchor: function()
     {
         if (this._highlightedNodeIndex === -1 || this._isDragging)
@@ -316,7 +374,7 @@
     {
         if (this._isDragging)
             return;
-        var node = this._timelineData.nodes[this._highlightedNodeIndex];
+        var node = this._timelineData.entries[this._highlightedNodeIndex].node;
         if (!node)
             return;
         var contentHelper = new WebInspector.PopoverContentHelper(node.functionName);
@@ -342,7 +400,7 @@
     {
         if (this._highlightedNodeIndex === -1)
             return;
-        var node = this._timelineData.nodes[this._highlightedNodeIndex];
+        var node = this._timelineData.entries[this._highlightedNodeIndex].node;
         this.dispatchEventToListeners(WebInspector.FlameChart.Events.SelectedNode, node);
     },
 
@@ -359,19 +417,20 @@
         if (nodeIndex === -1)
             return;
 
-        var timelineData = this._timelineData;
+        var timelineEntries = this._timelineData.entries;
 
-        var anchorLeft = Math.floor(timelineData.startTimes[nodeIndex] * this._timeToPixel - this._pixelWindowLeft);
+        var anchorLeft = Math.floor(timelineEntries[nodeIndex].startTime * this._timeToPixel - this._pixelWindowLeft);
         anchorLeft = Number.constrain(anchorLeft, 0, this._canvas.width);
 
-        var anchorWidth = Math.floor(timelineData.durations[nodeIndex] * this._timeToPixel);
+        var anchorWidth = Math.floor(timelineEntries[nodeIndex].duration * this._timeToPixel);
+
         anchorWidth = Number.constrain(anchorWidth, 0, this._canvas.width - anchorLeft);
 
         var style = this._anchorElement.style;
         style.width = anchorWidth + "px";
         style.height = this._barHeight + "px";
         style.left = anchorLeft + "px";
-        style.top = Math.floor(this._canvas.height - (timelineData.depths[nodeIndex] + 1) * this._barHeight) + "px";
+        style.top = Math.floor(this._canvas.height - (timelineEntries[nodeIndex].depth + 1) * this._barHeight) + "px";
     },
 
     _onMouseWheel: function(e)
@@ -390,14 +449,15 @@
         var timelineData = this._timelineData;
         if (!timelineData)
             return -1;
+        var timelineEntries = timelineData.entries;
         var cursorTime = Math.floor((x + this._pixelWindowLeft) * this._pixelToTime);
         var cursorLevel = Math.floor((this._canvas.height - y) / this._barHeight);
 
-        for (var i = 0; i < timelineData.nodeCount; ++i) {
-            if (cursorTime < timelineData.startTimes[i])
+        for (var i = 0; i < timelineEntries.length; ++i) {
+            if (cursorTime < timelineEntries[i].startTime)
                 return -1;
-            if (cursorTime < (timelineData.startTimes[i] + timelineData.durations[i])
-                && cursorLevel === timelineData.depths[i])
+            if (cursorTime < (timelineEntries[i].startTime + timelineEntries[i].duration)
+                && cursorLevel === timelineEntries[i].depth)
                 return i;
         }
         return -1;
@@ -418,18 +478,17 @@
         if (!this._timelineData)
             return;
 
-        var nodeCount = this._timelineData.nodeCount;
-        var depths = this._timelineData.depths;
-        var startTimes = this._timelineData.startTimes;
-        var durations = this._timelineData.durations;
+        var timelineEntries = this._timelineData.entries;
+
         var drawData = new Uint8Array(width);
         var scaleFactor = width / this._totalTime;
 
-        for (var nodeIndex = 0; nodeIndex < nodeCount; ++nodeIndex) {
-            var start = Math.floor(startTimes[nodeIndex] * scaleFactor);
-            var finish = Math.floor((startTimes[nodeIndex] + durations[nodeIndex]) * scaleFactor);
+        for (var nodeIndex = 0; nodeIndex < timelineEntries.length; ++nodeIndex) {
+            var entry = timelineEntries[nodeIndex];
+            var start = Math.floor(entry.startTime * scaleFactor);
+            var finish = Math.floor((entry.startTime + entry.duration) * scaleFactor);
             for (var x = start; x < finish; ++x)
-                drawData[x] = Math.max(drawData[x], depths[nodeIndex] + 1);
+                drawData[x] = Math.max(drawData[x], entry.depth + 1);
         }
 
         var context = this._overviewCanvas.getContext("2d");
@@ -456,25 +515,26 @@
         var timelineData = this._calculateTimelineData();
         if (!timelineData)
             return;
+        var timelineEntries = timelineData.entries;
         this._canvas.height = height;
         this._canvas.width = width;
         var barHeight = this._barHeight;
 
         var context = this._canvas.getContext("2d");
 
-        for (var i = 0; i < timelineData.nodeCount; ++i) {
-            var startTime = timelineData.startTimes[i];
+        for (var i = 0; i < timelineEntries.length; ++i) {
+            var startTime = timelineEntries[i].startTime;
             if (startTime > this._timeWindowRight)
                 break;
-            if ((startTime + timelineData.durations[i]) < this._timeWindowLeft)
+            if ((startTime + timelineEntries[i].duration) < this._timeWindowLeft)
                 continue;
             var x = Math.floor(startTime * this._timeToPixel) - this._pixelWindowLeft;
-            var y = height - (timelineData.depths[i] + 1) * barHeight;
-            var barWidth = Math.floor(timelineData.durations[i] * this._timeToPixel);
+            var y = height - (timelineEntries[i].depth + 1) * barHeight;
+            var barWidth = Math.floor(timelineEntries[i].duration * this._timeToPixel);
             if (barWidth < this._minWidth)
                 continue;
 
-            var colorPair = timelineData.colorPairs[i];
+            var colorPair = timelineEntries[i].colorPair;
             var color;
             if (this._highlightedNodeIndex === i)
                 color =  colorPair.highlighted;
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to