Chromium Code Reviews| 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} |