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

Unified Diff: Source/devtools/front_end/TimelinePowerOverview.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: rebase Created 6 years, 9 months 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/TimelinePowerOverview.js
diff --git a/Source/devtools/front_end/TimelinePowerOverview.js b/Source/devtools/front_end/TimelinePowerOverview.js
new file mode 100644
index 0000000000000000000000000000000000000000..e942a5298759b02484f77a06997bed029029128f
--- /dev/null
+++ b/Source/devtools/front_end/TimelinePowerOverview.js
@@ -0,0 +1,374 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @constructor
+ * @extends {WebInspector.TimelineOverviewBase}
pfeldman 2014/03/11 12:24:02 For now, you can add startTimeline and stopTimel
Pan 2014/03/18 10:16:48 Done.
+ * @param {!WebInspector.TimelineModel} model
+ */
+WebInspector.TimelinePowerOverview = function(model)
pfeldman 2014/03/11 12:24:02 Yes, perfect. You should have your own overview ty
+{
+ 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 = 0;
+ this._windowEndTime = Infinity;
+}
+
+WebInspector.TimelinePowerOverview.prototype = {
+ windowChanged: function(startTime, endTime)
pfeldman 2014/03/11 12:24:02 Please annotate every method.
+ {
+ this._windowStartTime = startTime;
+ this._windowEndTime = endTime;
+ this._drawEnergyMark(startTime, endTime);
+ },
+
+ _drawEnergyMark: function(startTime, endTime) {
+ // Clear previous power marks firstly.
+ this._restorePowerMarker();
pfeldman 2014/03/11 12:24:02 Lets think if we can unify this with memory graph
Pan 2014/03/12 13:11:20 Hi @pfeldman, thanks for your help, do you mean it
+
+ // Clear previous energy consumption text.
+ this._restoreImageUnderMarker(this._energyTextSaver);
+
+ 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);
+
+ if (!energy)
+ return;
+
+ // Draw energy consumption text.
+ var label = this._createTextLabel(WebInspector.UIString("%.2f\u2009joule", energy));
+ 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();
+ },
+
+ _calculateEnergyInSelectedRegion: function(startX, endX) {
+
+ // If pos is on the power event, just the prev and next will be same 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 power 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 timespan and xSpan.
+ 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);
+
+ // Calculate energy consumption of sliced power events.
+ if (startAdjancents.prev !== endAdjancents.prev) { // Cross at least one power event.
+ 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;
+ }
+
+ // Calculate energy consumption of internal power events.
+ 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;
+ },
+
+ _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._drawPowerMark(xPos, yPos, powerValue);
+ },
+
+ update: function()
+ {
+ this.resetCanvas();
+ delete this._powerPointSaver;
+ 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 power gridline and scales.
+ this._drawPowerGridline(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.
+ 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._drawEnergyMark(this._windowStartTime, this._windowEndTime);
+ },
+
+ _resetPowerBoundaries: function(allRecords)
+ {
+ var maxPower = 0;
+ var minPower = 100000000000;
+ var minTime = this._model.minimumRecordTime();
+ this._model.forAllRecords(function(r) {
+ record = r._record;
+ if (record.type !== WebInspector.TimelineModel.RecordType.SoC_Package)
+ return;
+ maxPower = Math.max(maxPower, record.value);
+ minPower = Math.min(minPower, record.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;
+ this._model.forAllRecords(function(r) {
+ record = r._record;
+ if (record.type !== WebInspector.TimelineModel.RecordType.SoC_Package)
+ return;
+ var x = Math.round((record.startTime - minTime) * xFactor);
+ var y = Math.round((record.value- minPower ) * yFactor);
+ histogram[x] = Math.max(histogram[x] || 0, y);
+ powerEventsHistogram[x] = record;
+ });
+
+ this._powerEventsHistogram = powerEventsHistogram;
+
+ // +1 so that the border always fit into the canvas area.
+ this._powerDrawHeight = height + 1;
+
+ return histogram;
+ },
+
+ _drawPowerGridline: 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._powerPointSaver);
+ delete this._powerPointSaver;
+
+ this._restoreImageUnderMarker(this._powerTextSaver);
+ delete this._powerTextSaver;
+ },
+
+ _drawPowerMark: function(xPointPos, yPointPos, powerValue)
+ {
+ // Draw power point.
+ const radius = 2;
+ this._powerPointSaver = this._saveArcImageUnderMarker(xPointPos, yPointPos, radius);
+ this._context.beginPath();
+ this._context.arc(xPointPos, yPointPos, radius, Math.PI*2, false)
+ this._context.strokeStyle = "red";
+ this._context.fillStyle = "red";
+ this._context.fill();
+ this._context.stroke();
+
+ // Draw power text.
+ 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();
+ },
+
+ _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
+ };
+ },
+
+ _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
+}

Powered by Google App Engine
This is Rietveld 408576698