Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(118)

Unified Diff: Source/devtools/front_end/TimelineOverviewPane.js

Issue 104523002: [DevTools] Add power profiler and power overview in timeline panel. (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: Source/devtools/front_end/TimelineOverviewPane.js
diff --git a/Source/devtools/front_end/TimelineOverviewPane.js b/Source/devtools/front_end/TimelineOverviewPane.js
old mode 100644
new mode 100755
index 5dae8620eb80ed9b34a39214803530a48d5df091..3d6de9d394bd6aea4a7e654d9b2284f9ca84bf68
--- a/Source/devtools/front_end/TimelineOverviewPane.js
+++ b/Source/devtools/front_end/TimelineOverviewPane.js
@@ -39,6 +39,10 @@ WebInspector.TimelineOverviewPane = function(model)
this.element.id = "timeline-overview-panel";
this.element.addStyleClass("hbox");
+ this._powerProfileEnabled = WebInspector.experimentsSettings.powerProfile.isEnabled();
+ if (this._powerProfileEnabled)
+ this.element.addStyleClass("with-power");
+
this._windowStartTime = 0;
this._windowEndTime = Infinity;
this._eventDividers = [];
@@ -63,12 +67,16 @@ WebInspector.TimelineOverviewPane = function(model)
this._overviewItems[WebInspector.TimelineOverviewPane.Mode.Memory] = new WebInspector.SidebarTreeElement("timeline-overview-sidebar-memory",
WebInspector.UIString("Memory"));
+ if (this._powerProfileEnabled)
+ this._overviewItems[WebInspector.TimelineOverviewPane.Mode.Power] = new WebInspector.SidebarTreeElement("timeline-overview-sidebar-power",
+ WebInspector.UIString("Power"))
+
for (var mode in this._overviewItems) {
var item = this._overviewItems[mode];
item.onselect = this.setMode.bind(this, mode);
topPaneSidebarTree.appendChild(item);
}
-
+
this._overviewGrid = new WebInspector.OverviewGrid("timeline");
this.element.appendChild(this._overviewGrid.element);
@@ -89,7 +97,8 @@ WebInspector.TimelineOverviewPane = function(model)
WebInspector.TimelineOverviewPane.Mode = {
Events: "Events",
Frames: "Frames",
- Memory: "Memory"
+ Memory: "Memory",
+ Power: "Power"
};
WebInspector.TimelineOverviewPane.Events = {
@@ -145,6 +154,13 @@ WebInspector.TimelineOverviewPane.prototype = {
return new WebInspector.TimelineFrameOverview(this._model);
case WebInspector.TimelineOverviewPane.Mode.Memory:
return new WebInspector.TimelineMemoryOverview(this._model);
+ case WebInspector.TimelineOverviewPane.Mode.Power:
+ if (this._powerProfileEnabled) {
+ return new WebInspector.TimelinePowerOverview(this._model, this._windowStartTime, this._windowEndTime);
+ } else {
+ this._currentMode = WebInspector.TimelineOverviewPane.Mode.Events;
+ return new WebInspector.TimelineEventOverview(this._model);
+ }
}
throw new Error("Invalid overview mode: " + this._currentMode);
},
@@ -258,11 +274,12 @@ WebInspector.TimelineOverviewPane.prototype = {
return this._overviewGrid.windowRight();
},
- _onWindowChanged: function()
+ _onWindowChanged: function(timeLeft, timeRight)
{
if (this._ignoreWindowChangedEvent)
return;
var times = this._overviewControl.windowTimes(this.windowLeft(), this.windowRight());
+ this._overviewControl.windowChanged(times.startTime, times.endTime);
this._windowStartTime = times.startTime;
this._windowEndTime = times.endTime;
this.dispatchEventToListeners(WebInspector.TimelineOverviewPane.Events.WindowChanged);
@@ -283,7 +300,9 @@ WebInspector.TimelineOverviewPane.prototype = {
{
var windowBoundaries = this._overviewControl.windowBoundaries(this._windowStartTime, this._windowEndTime);
this._ignoreWindowChangedEvent = true;
- this._overviewGrid.setWindow(windowBoundaries.left, windowBoundaries.right);
+ // frame overview will return zero windowBoundaries when no frame bar were covered, we should avoid this.
+ if (!(this._windowStartTime < this._windowEndTime && windowBoundaries.left === 0 && windowBoundaries.right === 0))
+ this._overviewGrid.setWindow(windowBoundaries.left, windowBoundaries.right);
this._overviewGrid.setResizeEnabled(this._model.records.length);
this._ignoreWindowChangedEvent = false;
},
@@ -399,6 +418,8 @@ WebInspector.TimelineOverviewBase.prototype = {
update: function() { },
reset: function() { },
+ windowChanged: function(timeLeft, timeRight) { },
+
categoryVisibilityChanged: function() { },
/**
@@ -1025,6 +1046,379 @@ WebInspector.TimelineFrameOverview.prototype = {
}
/**
+ * @constructor
+ * @extends {WebInspector.TimelineOverviewBase}
+ * @param {WebInspector.TimelineModel} model
+ */
+WebInspector.TimelinePowerOverview = function(model, windowStartTime, windowEndTime)
pfeldman 2014/01/09 11:38:52 This change probably needs merging. Could you reba
Pan 2014/02/24 09:49:47 rebased, thanks!
+{
+ WebInspector.TimelineOverviewBase.call(this, model);
+ this.element.id = "timeline-overview-power";
+ this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this));
+ this._powerEventsHistogram = [];
+ this._maxPower = 0;
+ this._minPower = 0;
+ this._yFactor = 0;
+ this._powerDrawHeight = 0;
+ this._windowStartTime = windowStartTime;
+ this._windowEndTime = windowEndTime;
+}
+
+WebInspector.TimelinePowerOverview.prototype = {
+ windowChanged: function(startTime, endTime)
+ {
+ this._windowStartTime = startTime;
+ this._windowEndTime = endTime;
+ this._drawEnergy(startTime, endTime);
+ },
+
+ _calculateEnergyInSelectedRegion: function(startX, endX) {
+
+ // if pos is on the power event, just the prev and next will be similer to pos
+ function findAdjacents(pos, histograms) {
+ var prevPowerEventIndex;
+ var nextPowerEventIndex;
+ // find previous power Event
+ for(var i = pos; i >= 0; i--) {
+ if (histograms[i]) {
+ prevPowerEventIndex = i;
+ break;
+ }
+ }
+ // find next Event
+ for(var i = pos; i < histograms.length; i++) {
+ if (histograms[i]) {
+ nextPowerEventIndex= i;
+ break;
+ }
+ }
+ // as pos may fall into a blank region, which only have next or prev. in this case we will return 0 timespance
+ return {
+ prev : prevPowerEventIndex,
+ next : nextPowerEventIndex,
+ timeSpan : (!prevPowerEventIndex || !nextPowerEventIndex) ? 0 : (histograms[nextPowerEventIndex].startTime - histograms[prevPowerEventIndex].startTime) / 1000, // in seconds
+ xSpan : (!prevPowerEventIndex || !nextPowerEventIndex) ? 0 : (nextPowerEventIndex - prevPowerEventIndex),
+ power : (!nextPowerEventIndex) ? 0 : histograms[nextPowerEventIndex].value
+ };
+ }
+
+ var energy = 0;
+ var startAdjancents = findAdjacents(startX, this._powerEventsHistogram);
+ var endAdjancents = findAdjacents(endX, this._powerEventsHistogram);
+
+ // cross a power event
+ if (startAdjancents.prev !== endAdjancents.prev) {
+
+ if (startAdjancents.timeSpan)
+ energy += (startAdjancents.next - startX) / startAdjancents.xSpan * startAdjancents.timeSpan * startAdjancents.power;
+
+ if (endAdjancents.timeSpan)
+ energy += (endX- endAdjancents.prev) / endAdjancents.xSpan * endAdjancents.timeSpan * endAdjancents.power;
+
+ } else { //start and end are in the same power event duration.
+ energy += (endX - startX) / startAdjancents.xSpan * startAdjancents.timeSpan * endAdjancents.power;
+ }
+
+ var prevX = -1;
+ for(var i = startX; i <= endX; i++) {
+ if (!this._powerEventsHistogram[i])
+ continue;
+ if (prevX !== -1) {
+ var timeSpan = (this._powerEventsHistogram[i].startTime- this._powerEventsHistogram[prevX].startTime) / 1000;
+ energy += timeSpan * this._powerEventsHistogram[i].value;
+ }
+ prevX = i;
+ }
+ return energy;
+
+ },
+
+ _drawEnergy: function(startTime, endTime) {
+ // clear power value firstly
+ this._restorePowerMarker();
+
+ var startX = Math.round(this._xFactor * (startTime - this._model.minimumRecordTime()));
+ var endX = Math.round(this._xFactor * (endTime- this._model.minimumRecordTime()));
+
+ var energy = this._calculateEnergyInSelectedRegion(startX, endX);
+
+ this._restoreImageUnderMarker(this._energyTextSaver);
+ if (energy)
+ this._drawEnergyText(startX, endX, energy);
+ },
+
+ _createTextLabel: function(labelName)
+ {
+ const labelPadding = 4 * window.devicePixelRatio;
+ var labelWidth = this._context.measureText(labelName).width + 2 * labelPadding;
+ var contextFont = this._context.font;
+ var labelHeight = contextFont.substr(0, contextFont.indexOf("px ")) - 0;
+ return { name : labelName,
+ width : labelWidth,
+ height : labelHeight
+ };
+ },
+
+ _drawEnergyText: function(startX, endX, energyValue)
+ {
+ var label = this._createTextLabel(WebInspector.UIString("%.2f\u2009joule", energyValue));
+ var textXPos = (startX + endX) > label.width ? Math.round((startX + endX - label.width) / 2) : Math.round((startX + endX) / 2);
+ var textYPos = Math.round(this._powerDrawHeight / 2);
+ this._context.beginPath();
+ this._energyTextSaver = this._saveRectImageUnderMarker(textXPos, textYPos - label.height, label.width, label.height + 2);
+ this._context.strokeStyle = "black";
+ this._context.fillStyle = "black";
+ this._context.fillText(label.name, textXPos, textYPos);
+ this._context.stroke();
+ },
+
+ _resetPowerBoundaries: function(allRecords)
+ {
+ var maxPower = 0;
+ var minPower = 100000000000;
+ var minTime = this._model.minimumRecordTime();
+ WebInspector.TimelinePresentationModel.forAllRecords(allRecords, function(r) {
+ if (r.type !== WebInspector.TimelineModel.RecordType.SoC_Package || WebInspector.TimelineModel.endTimeInSeconds(r) < minTime)
+ return;
+ maxPower = Math.max(maxPower, r.value);
+ minPower = Math.min(minPower, r.value);
+ });
+ minPower = Math.min(minPower, maxPower);
+ this._maxPower = maxPower;
+ this._minPower = minPower;
+ },
+
+ _prepareHistograms: function(allRecords)
+ {
+ const lowerOffset = 3;
+ var width = this._canvas.width;
+ var height = this._canvas.height - lowerOffset;
+ var minTime = this._model.minimumRecordTime();
+ var maxTime = this._model.maximumRecordTime();
+ var xFactor = width / (maxTime - minTime);
+ var yFactor = height / (this._maxPower - this._minPower);
+ this._xFactor = xFactor;
+ this._yFactor = yFactor;
+
+ var histogram = new Array(width);
+ var powerEventsHistogram = new Array(width);
+ var minPower = this._minPower;
+ WebInspector.TimelinePresentationModel.forAllRecords(allRecords, function(r) {
+ if (r.type !== WebInspector.TimelineModel.RecordType.SoC_Package || WebInspector.TimelineModel.endTimeInSeconds(r) < minTime)
+ return;
+ var x = Math.round((WebInspector.TimelineModel.endTimeInSeconds(r) - minTime) * xFactor);
+ var y = Math.round((r.value- minPower ) * yFactor);
+ histogram[x] = Math.max(histogram[x] || 0, y);
+ powerEventsHistogram[x] = r;
+ });
+
+ this._powerEventsHistogram = powerEventsHistogram;
+
+ // +1 so that the border always fit into the canvas area.
+ this._powerDrawHeight = height + 1;
+
+ return histogram;
+ },
+
+ update: function()
+ {
+ this._resetCanvas();
+ delete this._pointSaver;
+ delete this._powerTextSaver;
+ delete this._energyTextSaver;
+
+ this._powerEventsHistogram = [];
+ this._yFactor = 0;
+
+ var records = this._model.records;
+ if (!records.length)
+ return;
+
+ this._resetPowerBoundaries(records);
+
+ var histogram = this._prepareHistograms(records);
+
+ var height = this._powerDrawHeight;
+ // draw gridline
+ this._drawGridline(this._minPower, this._maxPower, height);
+
+ // draw power graph
+ // as we only have power samples in timestamp, we should eliminated the first event as we don't know its start point of time period.
+ var initialX = 0;
+ var initialY = 0;
+ var previousX = 0;
+ for (var k = 0; k < histogram.length; k++) {
+ var value = histogram[k];
+ if (value !== undefined) {
+ initialX = k;
+ previousX = k;
+ initialY = value;
+ break;
+ }
+ }
+
+ var ctx = this._context;
+ ctx.beginPath();
+ var isFirst = true;
+
+ for (var x = previousX + 1; x < histogram.length; x++) {
+ if (histogram[x] === undefined)
+ continue;
+ var currentY = height - histogram[x];
+ if (!isFirst) {
+ ctx.lineTo(previousX, currentY);
+ } else {
+ ctx.moveTo(initialX, currentY);
+ isFirst = false;
+ }
+ ctx.lineTo(x, currentY);
+ previousX = x;
+ }
+
+ ctx.lineWidth = 0.5;
+ ctx.strokeStyle = "rgba(20,0,0,0.8)";
+ ctx.stroke();
+
+ ctx.fillStyle = "rgba(255,192,0, 0.8);";
+ ctx.lineTo(previousX, this._canvas.height);
+ ctx.lineTo(initialX, this._canvas.height);
+ ctx.lineTo(initialX, height - initialY);
+ ctx.fill();
+ ctx.closePath();
+
+ // draw energy for a selected time period
+ if (this._windowStartTime > 0 && this._windowEndTime < Infinity && this._windowStartTime < this._windowEndTime)
+ this._drawEnergy(this._windowStartTime, this._windowEndTime);
+ },
+
+ _drawGridline: function(minPower, maxPower, height)
+ {
+ var ctx = this._context;
+ var width = this._canvas.width;
+ var yFactor = (maxPower - minPower) / height;
+ const labelPadding = 4 * window.devicePixelRatio;
+
+ ctx.strokeStyle = "rgba(128,128,128,0.5)";
+ var gridHeight = height / 4;
+ var yPositions = [];
+ var powerScales = [];
+
+ for (var i = 1; i < 4; i++) {
+ var yPos = Math.round(gridHeight * i) - 0.5;
+ var powerScale = yFactor * (height - yPos) + minPower;
+
+ //draw gridline
+ ctx.beginPath();
+ ctx.moveTo(0, yPos);
+ ctx.lineTo(width, yPos);
+ ctx.stroke();
+
+ //draw scale for gridline
+ if (!powerScale)
+ continue;
+ ctx.beginPath();
+ var label = this._createTextLabel(WebInspector.UIString("%.2f\u2009watt", powerScale));
+ ctx.save();
+ this._context.fillStyle = "black";
+ ctx.fillText(label.name, 2, yPos - 2);
+ ctx.stroke();
+ ctx.restore();
+ }
+ },
+
+ _restorePowerMarker: function()
+ {
+ this._restoreImageUnderMarker(this._pointSaver);
+ delete this._pointSaver;
+
+ this._restoreImageUnderMarker(this._powerTextSaver);
+ delete this._powerTextSaver;
+ },
+
+ _onMouseMove: function(event)
+ {
+ this._restorePowerMarker();
+ var powerValue;
+ var index = Math.round(event.offsetX);
+ var isInBlankArea = true;
+ for (var i = 0; i < index + 1; i++) {
+ if (this._powerEventsHistogram[i]) {
+ isInBlankArea = false;
+ break;
+ }
+ }
+ if (isInBlankArea)
+ return;
+
+ for (var i = index + 1; i < this._powerEventsHistogram.length; i++) {
+ if (this._powerEventsHistogram[i]) {
+ powerValue = this._powerEventsHistogram[i].value;
+ break;
+ }
+ }
+
+ var xPos = event.offsetX;
+ var yPos = this._powerDrawHeight - Math.round(this._yFactor * (powerValue - this._minPower));
+ this._drawPoint(xPos, yPos);
+
+ this._drawPowerText(xPos, yPos, powerValue);
+ },
+
+ _drawPoint: function(xPos, yPos)
+ {
+ const radius = 2;
+ this._pointSaver = this._saveArcImageUnderMarker(xPos, yPos, radius);
+ this._context.beginPath();
+ this._context.arc(xPos, yPos, radius, Math.PI*2, false)
+ this._context.strokeStyle = "red";
+ this._context.fillStyle = "red";
+ this._context.fill();
+ this._context.stroke();
+ },
+
+ _drawPowerText: function(xPointPos, yPointPos, powerValue)
+ {
+ var label = this._createTextLabel(WebInspector.UIString("%.2f\u2009watt", powerValue));
+ var textYPos = yPointPos - 3 - label.height > 0 ? yPointPos - 3: yPointPos + 3 + label.height;
+ var textXPos = xPointPos + label.width > this._canvas.width ? this._canvas.width - label.width : xPointPos;
+ this._context.beginPath();
+ this._powerTextSaver = this._saveRectImageUnderMarker(textXPos, textYPos - label.height, label.width, label.height);
+ this._context.fillText(label.name, textXPos, textYPos);
+ this._context.stroke();
+ },
+
+ _saveArcImageUnderMarker: function(x, y, radius)
+ {
+ const w = radius + 1;
+ var imageData = this._context.getImageData(x - w, y - w, 2 * w, 2 * w);
+ return {
+ x: x - w,
+ y: y - w,
+ imageData: imageData
+ };
+ },
+
+ _saveRectImageUnderMarker: function(x, y, w, h)
+ {
+ var imageData = this._context.getImageData(x, y, w, h);
+ return {
+ x: x,
+ y: y,
+ imageData: imageData
+ };
+ },
+
+
+ _restoreImageUnderMarker: function(imageUnderMarker)
+ {
+ if (imageUnderMarker)
+ this._context.putImageData(imageUnderMarker.imageData, imageUnderMarker.x, imageUnderMarker.y);
+ },
+
+ __proto__: WebInspector.TimelineOverviewBase.prototype
+}
+
+/**
* @param {WebInspector.TimelineOverviewPane} pane
* @constructor
* @implements {WebInspector.TimelinePresentationModel.Filter}

Powered by Google App Engine
This is Rietveld 408576698