1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 /** 6 * @constructor 7 * @extends {WebInspector.Object} 8 */ 9 WebInspector.TimelinePowerOverviewDataProvider = function() 10 { 11 this._records = []; 12 this._energies = []; 13 this._times = []; 14 WebInspector.powerProfiler.addEventListener(WebInspector.PowerProfiler.EventTypes.PowerEventRecorded, this._onRecordAdded, this); 15 } 16 17 WebInspector.TimelinePowerOverviewDataProvider.prototype = { 18 dispose: function() 19 { 20 WebInspector.powerProfiler.removeEventListener(WebInspector.PowerProfiler.EventTypes.PowerEventRecorded, this._onRecordAdded, this); 21 }, 22 23 /** 24 * @return {!Array.<!PowerAgent.PowerEvent>} 25 */ 26 records : function() 27 { 28 // The last record is not used, as its "value" is not set. 29 return this._records.slice(0, this._records.length - 1); 30 }, 31 32 /** 33 * @param {number} minTime 34 * @param {number} maxTime 35 * @return {number} energy in joules. 36 */ 37 _calculateEnergy : function(minTime, maxTime) 38 { 39 var times = this._times; 40 var energies = this._energies; 41 var last = times.length - 1; 42 43 if (last < 1 || minTime >= times[last] || maxTime <= times[0]) 44 return 0; 45 46 // Maximum index of element whose time <= minTime. 47 var start = Number.constrain(times.upperBound(minTime) - 1, 0, last); 48 49 // Minimum index of element whose time >= maxTime. 50 var end = Number.constrain(times.lowerBound(maxTime), 0, last); 51 52 var startTime = minTime < times[0] ? times[0] : minTime; 53 var endTime = maxTime > times[last] ? times[last] : maxTime; 54 55 if (start + 1 === end) 56 return (endTime - startTime) / (times[end] - times[start]) * (energies[end] - energies[start]) / 1000; 57 58 var totalEnergy = 0; 59 totalEnergy += energies[end - 1] - energies[start + 1]; 60 totalEnergy += (times[start + 1] - startTime) / (times[start + 1] - times[start]) * (energies[start + 1] - energies[start]); 61 totalEnergy += (endTime - times[end - 1]) / (times[end] - times[end - 1]) * (energies[end] - energies[end - 1]); 62 return totalEnergy / 1000; 63 }, 64 65 _onRecordAdded: function(event) 66 { 67 // "value" of original PowerEvent means the average power between previous sampling to current one. 68 // Here, it is converted to average power between current sampling to next one. 69 var record = event.data; 70 var curTime = record.timestamp; 71 var length = this._records.length; 72 var accumulatedEnergy = 0; 73 if (length) { 74 this._records[length - 1].value = record.value; 75 76 var prevTime = this._records[length - 1].timestamp; 77 accumulatedEnergy = this._energies[length - 1]; 78 accumulatedEnergy += (curTime - prevTime) * record.value; 79 } 80 this._energies.push(accumulatedEnergy); 81 this._records.push(record); 82 this._times.push(curTime); 83 }, 84 85 __proto__: WebInspector.Object.prototype 86 } 87 88 /** 89 * @constructor 90 * @extends {WebInspector.TimelineOverviewBase} 91 * @param {!WebInspector.TimelineModel} model 92 */ 93 WebInspector.TimelinePowerOverview = function(model) 94 { 95 WebInspector.TimelineOverviewBase.call(this, model); 96 this.element.id = "timeline-overview-power"; 97 this._dataProvider = new WebInspector.TimelinePowerOverviewDataProvider(); 98 99 this._maxPowerLabel = this.element.createChild("div", "max memory-graph-label"); 100 this._minPowerLabel = this.element.createChild("div", "min memory-graph-label"); 101 } 102 103 WebInspector.TimelinePowerOverview.prototype = { 104 dispose: function() 105 { 106 this._dataProvider.dispose(); 107 }, 108 109 timelineStarted: function() 110 { 111 if (WebInspector.targetManager.mainTarget().hasCapability(WebInspector.Target.Capabilities.CanProfilePower)) 112 WebInspector.powerProfiler.startProfile(); 113 }, 114 115 timelineStopped: function() 116 { 117 if (WebInspector.targetManager.mainTarget().hasCapability(WebInspector.Target.Capabilities.CanProfilePower)) 118 WebInspector.powerProfiler.stopProfile(); 119 }, 120 121 _resetPowerLabels: function() 122 { 123 this._maxPowerLabel.textContent = ""; 124 this._minPowerLabel.textContent = ""; 125 }, 126 127 update: function() 128 { 129 this.resetCanvas(); 130 131 var records = this._dataProvider.records(); 132 if (!records.length) { 133 this._resetPowerLabels(); 134 return; 135 } 136 137 const lowerOffset = 3; 138 var maxPower = 0; 139 var minPower = 100000000000; 140 var minTime = this._model.minimumRecordTime(); 141 var maxTime = this._model.maximumRecordTime(); 142 for (var i = 0; i < records.length; i++) { 143 var record = records[i]; 144 if (record.timestamp < minTime || record.timestamp > maxTime) 145 continue; 146 maxPower = Math.max(maxPower, record.value); 147 minPower = Math.min(minPower, record.value); 148 } 149 minPower = Math.min(minPower, maxPower); 150 151 152 var width = this._canvas.width; 153 var height = this._canvas.height - lowerOffset; 154 var xFactor = width / (maxTime - minTime); 155 var yFactor = height / Math.max(maxPower - minPower, 1); 156 157 var histogram = new Array(width); 158 for (var i = 0; i < records.length - 1; i++) { 159 var record = records[i]; 160 if (record.timestamp < minTime || record.timestamp > maxTime) 161 continue; 162 var x = Math.round((record.timestamp - minTime) * xFactor); 163 var y = Math.round((record.value- minPower ) * yFactor); 164 histogram[x] = Math.max(histogram[x] || 0, y); 165 } 166 167 var y = 0; 168 var isFirstPoint = true; 169 var ctx = this._context; 170 ctx.save(); 171 ctx.translate(0.5, 0.5); 172 ctx.beginPath(); 173 ctx.moveTo(-1, this._canvas.height); 174 for (var x = 0; x < histogram.length; x++) { 175 if (typeof histogram[x] === "undefined") 176 continue; 177 if (isFirstPoint) { 178 isFirstPoint = false; 179 y = histogram[x]; 180 ctx.lineTo(-1, height - y); 181 } 182 ctx.lineTo(x, height - y); 183 y = histogram[x]; 184 ctx.lineTo(x, height - y); 185 } 186 187 ctx.lineTo(width, height - y); 188 ctx.lineTo(width, this._canvas.height); 189 ctx.lineTo(-1, this._canvas.height); 190 ctx.closePath(); 191 192 ctx.fillStyle = "rgba(255,192,0, 0.8);"; 193 ctx.fill(); 194 195 ctx.lineWidth = 0.5; 196 ctx.strokeStyle = "rgba(20,0,0,0.8)"; 197 ctx.stroke(); 198 ctx.restore(); 199 200 this._maxPowerLabel.textContent = WebInspector.UIString("%.2f\u2009watts", maxPower); 201 this._minPowerLabel.textContent = WebInspector.UIString("%.2f\u2009watts", minPower); 202 }, 203 204 /** 205 * @param {number} minTime 206 * @param {number} maxTime 207 * @return {number} energy in joules. 208 */ 209 calculateEnergy: function(minTime, maxTime) 210 { 211 return this._dataProvider._calculateEnergy(minTime, maxTime); 212 }, 213 214 __proto__: WebInspector.TimelineOverviewBase.prototype 215 } 216 217 218