1/* 2 * Copyright (C) 2013 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31/** 32 * @param {WebInspector.TimelinePanel} timelinePanel 33 * @param {WebInspector.TimelineModel} model 34 * @param {number} sidebarWidth 35 * @constructor 36 * @extends {WebInspector.MemoryStatistics} 37 */ 38WebInspector.NativeMemoryGraph = function(timelinePanel, model, sidebarWidth) 39{ 40 WebInspector.MemoryStatistics.call(this, timelinePanel, model, sidebarWidth); 41} 42 43/** 44 * @constructor 45 * @extends {WebInspector.MemoryStatistics.Counter} 46 */ 47WebInspector.NativeMemoryGraph.Counter = function(time, nativeCounters) 48{ 49 WebInspector.MemoryStatistics.Counter.call(this, time); 50 this.nativeCounters = nativeCounters; 51} 52 53/** 54 * @constructor 55 * @extends {WebInspector.CounterUIBase} 56 * @param {WebInspector.NativeMemoryGraph} memoryCountersPane 57 * @param {string} title 58 * @param {Array.<number>} hsl 59 * @param {function(WebInspector.NativeMemoryGraph.Counter):number} valueGetter 60 */ 61WebInspector.NativeMemoryCounterUI = function(memoryCountersPane, title, hsl, valueGetter) 62{ 63 var swatchColor = this._hslToString(hsl); 64 WebInspector.CounterUIBase.call(this, memoryCountersPane, title, swatchColor, valueGetter); 65 this._value = this._swatch.element.createChild("span", "memory-category-value"); 66 67 const borderLightnessDifference = 3; 68 hsl[2] -= borderLightnessDifference; 69 this.strokeColor = this._hslToString(hsl); 70 this.graphYValues = []; 71} 72 73WebInspector.NativeMemoryCounterUI.prototype = { 74 _hslToString: function(hsl) 75 { 76 return "hsl(" + hsl[0] + "," + hsl[1] + "%," + hsl[2] + "%)"; 77 }, 78 79 updateCurrentValue: function(countersEntry) 80 { 81 var bytes = this.valueGetter(countersEntry); 82 var megabytes = bytes / (1024 * 1024); 83 this._value.textContent = WebInspector.UIString("%.1f\u2009MB", megabytes); 84 }, 85 86 clearCurrentValueAndMarker: function(ctx) 87 { 88 this._value.textContent = ""; 89 }, 90 91 __proto__: WebInspector.CounterUIBase.prototype 92} 93 94 95WebInspector.NativeMemoryGraph.prototype = { 96 _createCurrentValuesBar: function() 97 { 98 }, 99 100 _createCounterUIList: function() 101 { 102 var nativeCounters = [ 103 "JSExternalResources", 104 "CSS", 105 "GlyphCache", 106 "Image", 107 "Resources", 108 "DOM", 109 "Rendering", 110 "Audio", 111 "WebInspector", 112 "JSHeap.Used", 113 "JSHeap.Unused", 114 "MallocWaste", 115 "Other", 116 "PrivateBytes", 117 ]; 118 119 /** 120 * @param {string} name 121 * @return {number} 122 */ 123 function getCounterValue(name, entry) 124 { 125 return (entry.nativeCounters && entry.nativeCounters[name]) || 0; 126 } 127 128 var list = []; 129 for (var i = nativeCounters.length - 1; i >= 0; i--) { 130 var name = nativeCounters[i]; 131 if ("PrivateBytes" === name) { 132 var counterUI = new WebInspector.NativeMemoryCounterUI(this, "Total", [0, 0, 0], getCounterValue.bind(this, name)) 133 this._privateBytesCounter = counterUI; 134 } else { 135 var counterUI = new WebInspector.NativeMemoryCounterUI(this, name, [i * 20, 65, 63], getCounterValue.bind(this, name)) 136 list.push(counterUI); 137 } 138 } 139 return list.reverse(); 140 }, 141 142 _canvasHeight: function() 143 { 144 return this._canvasContainer.offsetHeight; 145 }, 146 147 /** 148 * @param {WebInspector.Event} event 149 */ 150 _onRecordAdded: function(event) 151 { 152 var statistics = this._counters; 153 function addStatistics(record) 154 { 155 var nativeCounters = record["nativeHeapStatistics"]; 156 if (!nativeCounters) 157 return; 158 159 var knownSize = 0; 160 for (var name in nativeCounters) { 161 if (name === "PrivateBytes") 162 continue; 163 knownSize += nativeCounters[name]; 164 } 165 nativeCounters["Other"] = nativeCounters["PrivateBytes"] - knownSize; 166 167 statistics.push(new WebInspector.NativeMemoryGraph.Counter( 168 record.endTime || record.startTime, 169 nativeCounters 170 )); 171 } 172 WebInspector.TimelinePresentationModel.forAllRecords([event.data], null, addStatistics); 173 }, 174 175 _draw: function() 176 { 177 WebInspector.MemoryStatistics.prototype._draw.call(this); 178 179 var maxValue = this._maxCounterValue(); 180 this._resetTotalValues(); 181 182 var previousCounterUI; 183 for (var i = 0; i < this._counterUI.length; i++) { 184 this._drawGraph(this._counterUI[i], previousCounterUI, maxValue); 185 if (this._counterUI[i].visible) 186 previousCounterUI = this._counterUI[i]; 187 } 188 }, 189 190 /** 191 * @param {CanvasRenderingContext2D} ctx 192 */ 193 _clearCurrentValueAndMarker: function(ctx) 194 { 195 WebInspector.MemoryStatistics.prototype._clearCurrentValueAndMarker.call(this, ctx); 196 this._privateBytesCounter.clearCurrentValueAndMarker(ctx); 197 }, 198 199 _updateCurrentValue: function(counterEntry) 200 { 201 WebInspector.MemoryStatistics.prototype._updateCurrentValue.call(this, counterEntry); 202 this._privateBytesCounter.updateCurrentValue(counterEntry); 203 }, 204 205 /** 206 * @param {CanvasRenderingContext2D} ctx 207 */ 208 _restoreImageUnderMarker: function(ctx) 209 { 210 if (this._imageUnderMarker) 211 ctx.putImageData(this._imageUnderMarker.imageData, this._imageUnderMarker.x, this._imageUnderMarker.y); 212 this._discardImageUnderMarker(); 213 }, 214 215 /** 216 * @param {CanvasRenderingContext2D} ctx 217 * @param {number} left 218 * @param {number} top 219 * @param {number} right 220 * @param {number} bottom 221 */ 222 _saveImageUnderMarker: function(ctx, left, top, right, bottom) 223 { 224 var imageData = ctx.getImageData(left, top, right, bottom); 225 this._imageUnderMarker = { 226 x: left, 227 y: top, 228 imageData: imageData 229 }; 230 }, 231 232 /** 233 * @param {CanvasRenderingContext2D} ctx 234 * @param {number} x 235 * @param {number} index 236 */ 237 _drawMarker: function(ctx, x, index) 238 { 239 var left = this._counters[index].x; 240 var right = index + 1 < this._counters.length ? this._counters[index + 1].x : left; 241 var top = this._originY; 242 top = 0; 243 var bottom = top + this._clippedHeight; 244 bottom += this._originY; 245 246 this._saveImageUnderMarker(ctx, left, top, right, bottom); 247 248 ctx.beginPath(); 249 ctx.moveTo(left, top); 250 ctx.lineTo(right, top); 251 ctx.lineTo(right, bottom); 252 ctx.lineTo(left, bottom); 253 ctx.lineWidth = 1; 254 ctx.closePath(); 255 ctx.fillStyle = "rgba(220,220,220,0.3)"; 256 ctx.fill(); 257 }, 258 259 /** 260 * @return {number} 261 */ 262 _maxCounterValue: function() 263 { 264 if (!this._counters.length) 265 return 0; 266 267 var valueGetter = this._privateBytesCounter.valueGetter; 268 var result = 0; 269 for (var i = this._minimumIndex; i < this._maximumIndex; i++) { 270 var counter = this._counters[i]; 271 var value = valueGetter(counter); 272 if (value > result) 273 result = value; 274 } 275 return result; 276 }, 277 278 _resetTotalValues: function() 279 { 280 for (var i = this._minimumIndex; i <= this._maximumIndex; i++) { 281 var counter = this._counters[i]; 282 counter.total = 0; 283 } 284 }, 285 286 /** 287 * @param {WebInspector.CounterUIBase} counterUI 288 * @param {WebInspector.CounterUIBase} previousCounterUI 289 * @param {number} maxTotalValue 290 */ 291 _drawGraph: function(counterUI, previousCounterUI, maxTotalValue) 292 { 293 var canvas = this._canvas; 294 var ctx = canvas.getContext("2d"); 295 var width = canvas.width; 296 var height = this._clippedHeight; 297 var originY = this._originY; 298 var valueGetter = counterUI.valueGetter; 299 300 if (!this._counters.length) 301 return; 302 303 if (!counterUI.visible) 304 return; 305 306 for (var i = this._minimumIndex; i <= this._maximumIndex; i++) { 307 var counter = this._counters[i]; 308 var value = valueGetter(counter); 309 counter.total += value; 310 } 311 312 var yValues = counterUI.graphYValues; 313 yValues.length = this._counters.length; 314 315 var maxYRange = maxTotalValue; 316 var yFactor = maxYRange ? height / (maxYRange) : 1; 317 318 ctx.beginPath(); 319 if (previousCounterUI) { 320 var prevYValues = previousCounterUI.graphYValues; 321 var currentY = prevYValues[this._maximumIndex]; 322 ctx.moveTo(width, currentY); 323 var currentX = width; 324 for (var i = this._maximumIndex - 1; i >= this._minimumIndex; i--) { 325 currentY = prevYValues[i]; 326 currentX = this._counters[i].x; 327 ctx.lineTo(currentX, currentY); 328 } 329 } else { 330 var lastY = originY + height; 331 ctx.moveTo(width, lastY); 332 ctx.lineTo(0, lastY); 333 } 334 335 var currentY = originY + (height - this._counters[this._minimumIndex].total * yFactor); 336 ctx.lineTo(0, currentY); 337 for (var i = this._minimumIndex; i <= this._maximumIndex; i++) { 338 var counter = this._counters[i]; 339 var x = counter.x; 340 currentY = originY + (height - counter.total * yFactor); 341 ctx.lineTo(x, currentY); 342 343 yValues[i] = currentY; 344 } 345 ctx.lineTo(width, currentY); 346 ctx.closePath(); 347 ctx.lineWidth = 1; 348 349 ctx.strokeStyle = counterUI.strokeColor; 350 ctx.fillStyle = counterUI.graphColor; 351 ctx.fill(); 352 ctx.stroke(); 353 }, 354 355 _discardImageUnderMarker: function() 356 { 357 delete this._imageUnderMarker; 358 }, 359 360 __proto__: WebInspector.MemoryStatistics.prototype 361} 362 363