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 |
+} |