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