1 /* 2 * Copyright (C) 2012 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 */ 37 WebInspector.MemoryStatistics = function(timelinePanel, model, sidebarWidth) 38 { 39 this._timelinePanel = timelinePanel; 40 this._counters = []; 41 42 model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onRecordAdded, this); 43 model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this); 44 45 this._containerAnchor = timelinePanel.element.lastChild; 46 this._memorySidebarView = new WebInspector.SidebarView(WebInspector.SidebarView.SidebarPosition.Start, undefined, sidebarWidth); 47 this._memorySidebarView.sidebarElement.addStyleClass("sidebar"); 48 this._memorySidebarView.element.id = "memory-graphs-container"; 49 50 this._memorySidebarView.addEventListener(WebInspector.SidebarView.EventTypes.Resized, this._sidebarResized.bind(this)); 51 52 this._canvasContainer = this._memorySidebarView.mainElement; 53 this._canvasContainer.id = "memory-graphs-canvas-container"; 54 this._createCurrentValuesBar(); 55 this._canvas = this._canvasContainer.createChild("canvas"); 56 this._canvas.id = "memory-counters-graph"; 57 this._lastMarkerXPosition = 0; 58 59 this._canvas.addEventListener("mouseover", this._onMouseOver.bind(this), true); 60 this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this), true); 61 this._canvas.addEventListener("mouseout", this._onMouseOut.bind(this), true); 62 this._canvas.addEventListener("click", this._onClick.bind(this), true); 63 // We create extra timeline grid here to reuse its event dividers. 64 this._timelineGrid = new WebInspector.TimelineGrid(); 65 this._canvasContainer.appendChild(this._timelineGrid.dividersElement); 66 67 // Populate sidebar 68 this._memorySidebarView.sidebarElement.createChild("div", "sidebar-tree sidebar-tree-section").textContent = WebInspector.UIString("COUNTERS"); 69 this._counterUI = this._createCounterUIList(); 70 } 71 72 /** 73 * @constructor 74 * @param {number} time 75 */ 76 WebInspector.MemoryStatistics.Counter = function(time) 77 { 78 this.time = time; 79 } 80 81 /** 82 * @constructor 83 * @extends {WebInspector.Object} 84 */ 85 WebInspector.SwatchCheckbox = function(title, color) 86 { 87 this.element = document.createElement("div"); 88 this._swatch = this.element.createChild("div", "swatch"); 89 this.element.createChild("span", "title").textContent = title; 90 this._color = color; 91 this.checked = true; 92 93 this.element.addEventListener("click", this._toggleCheckbox.bind(this), true); 94 } 95 96 WebInspector.SwatchCheckbox.Events = { 97 Changed: "Changed" 98 } 99 100 WebInspector.SwatchCheckbox.prototype = { 101 get checked() 102 { 103 return this._checked; 104 }, 105 106 set checked(v) 107 { 108 this._checked = v; 109 if (this._checked) 110 this._swatch.style.backgroundColor = this._color; 111 else 112 this._swatch.style.backgroundColor = ""; 113 }, 114 115 _toggleCheckbox: function(event) 116 { 117 this.checked = !this.checked; 118 this.dispatchEventToListeners(WebInspector.SwatchCheckbox.Events.Changed); 119 }, 120 121 __proto__: WebInspector.Object.prototype 122 } 123 124 /** 125 * @constructor 126 */ 127 WebInspector.CounterUIBase = function(memoryCountersPane, title, graphColor, valueGetter) 128 { 129 this._memoryCountersPane = memoryCountersPane; 130 this.valueGetter = valueGetter; 131 var container = memoryCountersPane._memorySidebarView.sidebarElement.createChild("div", "memory-counter-sidebar-info"); 132 var swatchColor = graphColor; 133 this._swatch = new WebInspector.SwatchCheckbox(WebInspector.UIString(title), swatchColor); 134 this._swatch.addEventListener(WebInspector.SwatchCheckbox.Events.Changed, this._toggleCounterGraph.bind(this)); 135 container.appendChild(this._swatch.element); 136 137 this._value = null; 138 this.graphColor =graphColor; 139 this.strokeColor = graphColor; 140 this.graphYValues = []; 141 } 142 143 WebInspector.CounterUIBase.prototype = { 144 _toggleCounterGraph: function(event) 145 { 146 if (this._swatch.checked) 147 this._value.removeStyleClass("hidden"); 148 else 149 this._value.addStyleClass("hidden"); 150 this._memoryCountersPane.refresh(); 151 }, 152 153 updateCurrentValue: function(countersEntry) 154 { 155 this._value.textContent = Number.bytesToString(this.valueGetter(countersEntry)); 156 }, 157 158 clearCurrentValueAndMarker: function(ctx) 159 { 160 this._value.textContent = ""; 161 }, 162 163 get visible() 164 { 165 return this._swatch.checked; 166 }, 167 } 168 169 WebInspector.MemoryStatistics.prototype = { 170 _createCurrentValuesBar: function() 171 { 172 throw new Error("Not implemented"); 173 }, 174 175 _createCounterUIList: function() 176 { 177 throw new Error("Not implemented"); 178 }, 179 180 _onRecordsCleared: function() 181 { 182 this._counters = []; 183 }, 184 185 /** 186 * @param {WebInspector.TimelineGrid} timelineGrid 187 */ 188 setMainTimelineGrid: function(timelineGrid) 189 { 190 this._mainTimelineGrid = timelineGrid; 191 }, 192 193 /** 194 * @param {number} top 195 */ 196 setTopPosition: function(top) 197 { 198 this._memorySidebarView.element.style.top = top + "px"; 199 this._updateSize(); 200 }, 201 202 /** 203 * @param {number} width 204 */ 205 setSidebarWidth: function(width) 206 { 207 if (this._ignoreSidebarResize) 208 return; 209 this._ignoreSidebarResize = true; 210 this._memorySidebarView.setSidebarWidth(width); 211 this._ignoreSidebarResize = false; 212 }, 213 214 /** 215 * @param {WebInspector.Event} event 216 */ 217 _sidebarResized: function(event) 218 { 219 if (this._ignoreSidebarResize) 220 return; 221 this._ignoreSidebarResize = true; 222 this._timelinePanel.splitView.setSidebarWidth(event.data); 223 this._ignoreSidebarResize = false; 224 }, 225 226 _canvasHeight: function() 227 { 228 throw new Error("Not implemented"); 229 }, 230 231 _updateSize: function() 232 { 233 var width = this._mainTimelineGrid.dividersElement.offsetWidth + 1; 234 this._canvasContainer.style.width = width + "px"; 235 236 var height = this._canvasHeight(); 237 this._canvas.width = width; 238 this._canvas.height = height; 239 }, 240 241 /** 242 * @param {WebInspector.Event} event 243 */ 244 _onRecordAdded: function(event) 245 { 246 throw new Error("Not implemented"); 247 }, 248 249 _draw: function() 250 { 251 this._calculateVisibleIndexes(); 252 this._calculateXValues(); 253 this._clear(); 254 255 this._setVerticalClip(10, this._canvas.height - 20); 256 }, 257 258 _calculateVisibleIndexes: function() 259 { 260 var calculator = this._timelinePanel.calculator; 261 var start = calculator.minimumBoundary() * 1000; 262 var end = calculator.maximumBoundary() * 1000; 263 function comparator(value, sample) 264 { 265 return value - sample.time; 266 } 267 268 // Maximum index of element whose time <= start. 269 this._minimumIndex = Number.constrain(this._counters.upperBound(start, comparator) - 1, 0, this._counters.length - 1); 270 271 // Minimum index of element whose time >= end. 272 this._maximumIndex = Number.constrain(this._counters.lowerBound(end, comparator), 0, this._counters.length - 1); 273 274 // Current window bounds. 275 this._minTime = start; 276 this._maxTime = end; 277 }, 278 279 /** 280 * @param {MouseEvent} event 281 */ 282 _onClick: function(event) 283 { 284 var x = event.x - event.target.offsetParent.offsetLeft; 285 var i = this._recordIndexAt(x); 286 var counter = this._counters[i]; 287 if (counter) 288 this._timelinePanel.revealRecordAt(counter.time / 1000); 289 }, 290 291 /** 292 * @param {MouseEvent} event 293 */ 294 _onMouseOut: function(event) 295 { 296 delete this._markerXPosition; 297 298 var ctx = this._canvas.getContext("2d"); 299 this._clearCurrentValueAndMarker(ctx); 300 }, 301 302 /** 303 * @param {CanvasRenderingContext2D} ctx 304 */ 305 _clearCurrentValueAndMarker: function(ctx) 306 { 307 for (var i = 0; i < this._counterUI.length; i++) 308 this._counterUI[i].clearCurrentValueAndMarker(ctx); 309 }, 310 311 /** 312 * @param {MouseEvent} event 313 */ 314 _onMouseOver: function(event) 315 { 316 this._onMouseMove(event); 317 }, 318 319 /** 320 * @param {MouseEvent} event 321 */ 322 _onMouseMove: function(event) 323 { 324 var x = event.x - event.target.offsetParent.offsetLeft 325 this._markerXPosition = x; 326 this._refreshCurrentValues(); 327 }, 328 329 _refreshCurrentValues: function() 330 { 331 if (!this._counters.length) 332 return; 333 if (this._markerXPosition === undefined) 334 return; 335 if (this._maximumIndex === -1) 336 return; 337 var i = this._recordIndexAt(this._markerXPosition); 338 339 this._updateCurrentValue(this._counters[i]); 340 341 this._highlightCurrentPositionOnGraphs(this._markerXPosition, i); 342 }, 343 344 _updateCurrentValue: function(counterEntry) 345 { 346 for (var j = 0; j < this._counterUI.length; j++) 347 this._counterUI[j].updateCurrentValue(counterEntry); 348 }, 349 350 _recordIndexAt: function(x) 351 { 352 var i; 353 for (i = this._minimumIndex + 1; i <= this._maximumIndex; i++) { 354 var statX = this._counters[i].x; 355 if (x < statX) 356 break; 357 } 358 i--; 359 return i; 360 }, 361 362 _highlightCurrentPositionOnGraphs: function(x, index) 363 { 364 var ctx = this._canvas.getContext("2d"); 365 this._restoreImageUnderMarker(ctx); 366 this._drawMarker(ctx, x, index); 367 }, 368 369 _restoreImageUnderMarker: function(ctx) 370 { 371 throw new Error("Not implemented"); 372 }, 373 374 _drawMarker: function(ctx, x, index) 375 { 376 throw new Error("Not implemented"); 377 }, 378 379 visible: function() 380 { 381 return this._memorySidebarView.isShowing(); 382 }, 383 384 show: function() 385 { 386 var anchor = /** @type {Element|null} */ (this._containerAnchor.nextSibling); 387 var savedSidebarSize = this._timelinePanel.splitView.sidebarWidth(); 388 this._memorySidebarView.show(this._timelinePanel.element, anchor); 389 if (savedSidebarSize > 0) { 390 this.setSidebarWidth(savedSidebarSize); 391 this._timelinePanel.splitView.setSidebarWidth(savedSidebarSize); 392 } 393 this._updateSize(); 394 this._refreshDividers(); 395 setTimeout(this._draw.bind(this), 0); 396 }, 397 398 refresh: function() 399 { 400 this._updateSize(); 401 this._refreshDividers(); 402 this._draw(); 403 this._refreshCurrentValues(); 404 }, 405 406 hide: function() 407 { 408 this._memorySidebarView.detach(); 409 }, 410 411 _refreshDividers: function() 412 { 413 this._timelineGrid.updateDividers(this._timelinePanel.calculator); 414 }, 415 416 _setVerticalClip: function(originY, height) 417 { 418 this._originY = originY; 419 this._clippedHeight = height; 420 }, 421 422 _calculateXValues: function() 423 { 424 if (!this._counters.length) 425 return; 426 427 var width = this._canvas.width; 428 var xFactor = width / (this._maxTime - this._minTime); 429 430 this._counters[this._minimumIndex].x = 0; 431 for (var i = this._minimumIndex + 1; i < this._maximumIndex; i++) 432 this._counters[i].x = xFactor * (this._counters[i].time - this._minTime); 433 this._counters[this._maximumIndex].x = width; 434 }, 435 436 _clear: function() { 437 var ctx = this._canvas.getContext("2d"); 438 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); 439 this._discardImageUnderMarker(); 440 }, 441 442 _discardImageUnderMarker: function() 443 { 444 throw new Error("Not implemented"); 445 } 446 } 447 448