| OLD | NEW |
| (Empty) |
| 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 * @constructor | |
| 33 * @extends {WebInspector.SplitView} | |
| 34 * @param {!WebInspector.TimelineModeViewDelegate} delegate | |
| 35 * @param {!WebInspector.TimelineModel} model | |
| 36 */ | |
| 37 WebInspector.MemoryStatistics = function(delegate, model) | |
| 38 { | |
| 39 WebInspector.SplitView.call(this, true, false); | |
| 40 | |
| 41 this.element.id = "memory-graphs-container"; | |
| 42 | |
| 43 this._delegate = delegate; | |
| 44 this._model = model; | |
| 45 this._calculator = new WebInspector.TimelineCalculator(this._model); | |
| 46 | |
| 47 this._graphsContainer = this.mainElement(); | |
| 48 this._createCurrentValuesBar(); | |
| 49 this._canvasView = new WebInspector.VBoxWithResizeCallback(this._resize.bind
(this)); | |
| 50 this._canvasView.show(this._graphsContainer); | |
| 51 this._canvasContainer = this._canvasView.element; | |
| 52 this._canvasContainer.id = "memory-graphs-canvas-container"; | |
| 53 this._canvas = this._canvasContainer.createChild("canvas"); | |
| 54 this._canvas.id = "memory-counters-graph"; | |
| 55 | |
| 56 this._canvasContainer.addEventListener("mouseover", this._onMouseMove.bind(t
his), true); | |
| 57 this._canvasContainer.addEventListener("mousemove", this._onMouseMove.bind(t
his), true); | |
| 58 this._canvasContainer.addEventListener("mouseout", this._onMouseOut.bind(thi
s), true); | |
| 59 this._canvasContainer.addEventListener("click", this._onClick.bind(this), tr
ue); | |
| 60 // We create extra timeline grid here to reuse its event dividers. | |
| 61 this._timelineGrid = new WebInspector.TimelineGrid(); | |
| 62 this._canvasContainer.appendChild(this._timelineGrid.dividersElement); | |
| 63 | |
| 64 // Populate sidebar | |
| 65 this.sidebarElement().createChild("div", "sidebar-tree sidebar-tree-section"
).textContent = WebInspector.UIString("COUNTERS"); | |
| 66 this.createAllCounters(); | |
| 67 } | |
| 68 | |
| 69 /** | |
| 70 * @constructor | |
| 71 * @param {string} counterName | |
| 72 */ | |
| 73 WebInspector.MemoryStatistics.Counter = function(counterName) | |
| 74 { | |
| 75 this.counterName = counterName; | |
| 76 this.times = []; | |
| 77 this.values = []; | |
| 78 } | |
| 79 | |
| 80 WebInspector.MemoryStatistics.Counter.prototype = { | |
| 81 /** | |
| 82 * @param {number} time | |
| 83 * @param {!TimelineAgent.Counters} counters | |
| 84 */ | |
| 85 appendSample: function(time, counters) | |
| 86 { | |
| 87 var value = counters[this.counterName]; | |
| 88 if (value === undefined) | |
| 89 return; | |
| 90 if (this.values.length && this.values.peekLast() === value) | |
| 91 return; | |
| 92 this.times.push(time); | |
| 93 this.values.push(value); | |
| 94 }, | |
| 95 | |
| 96 reset: function() | |
| 97 { | |
| 98 this.times = []; | |
| 99 this.values = []; | |
| 100 }, | |
| 101 | |
| 102 /** | |
| 103 * @param {!WebInspector.TimelineCalculator} calculator | |
| 104 */ | |
| 105 _calculateVisibleIndexes: function(calculator) | |
| 106 { | |
| 107 var start = calculator.minimumBoundary(); | |
| 108 var end = calculator.maximumBoundary(); | |
| 109 | |
| 110 // Maximum index of element whose time <= start. | |
| 111 this._minimumIndex = Number.constrain(this.times.upperBound(start) - 1,
0, this.times.length - 1); | |
| 112 | |
| 113 // Minimum index of element whose time >= end. | |
| 114 this._maximumIndex = Number.constrain(this.times.lowerBound(end), 0, thi
s.times.length - 1); | |
| 115 | |
| 116 // Current window bounds. | |
| 117 this._minTime = start; | |
| 118 this._maxTime = end; | |
| 119 }, | |
| 120 | |
| 121 /** | |
| 122 * @param {number} width | |
| 123 */ | |
| 124 _calculateXValues: function(width) | |
| 125 { | |
| 126 if (!this.values.length) | |
| 127 return; | |
| 128 | |
| 129 var xFactor = width / (this._maxTime - this._minTime); | |
| 130 | |
| 131 this.x = new Array(this.values.length); | |
| 132 this.x[this._minimumIndex] = 0; | |
| 133 for (var i = this._minimumIndex + 1; i < this._maximumIndex; i++) | |
| 134 this.x[i] = xFactor * (this.times[i] - this._minTime); | |
| 135 this.x[this._maximumIndex] = width; | |
| 136 } | |
| 137 } | |
| 138 | |
| 139 /** | |
| 140 * @constructor | |
| 141 * @extends {WebInspector.Object} | |
| 142 */ | |
| 143 WebInspector.SwatchCheckbox = function(title, color) | |
| 144 { | |
| 145 this.element = document.createElement("div"); | |
| 146 this._swatch = this.element.createChild("div", "swatch"); | |
| 147 this.element.createChild("span", "title").textContent = title; | |
| 148 this._color = color; | |
| 149 this.checked = true; | |
| 150 | |
| 151 this.element.addEventListener("click", this._toggleCheckbox.bind(this), true
); | |
| 152 } | |
| 153 | |
| 154 WebInspector.SwatchCheckbox.Events = { | |
| 155 Changed: "Changed" | |
| 156 } | |
| 157 | |
| 158 WebInspector.SwatchCheckbox.prototype = { | |
| 159 get checked() | |
| 160 { | |
| 161 return this._checked; | |
| 162 }, | |
| 163 | |
| 164 set checked(v) | |
| 165 { | |
| 166 this._checked = v; | |
| 167 if (this._checked) | |
| 168 this._swatch.style.backgroundColor = this._color; | |
| 169 else | |
| 170 this._swatch.style.backgroundColor = ""; | |
| 171 }, | |
| 172 | |
| 173 _toggleCheckbox: function(event) | |
| 174 { | |
| 175 this.checked = !this.checked; | |
| 176 this.dispatchEventToListeners(WebInspector.SwatchCheckbox.Events.Changed
); | |
| 177 }, | |
| 178 | |
| 179 __proto__: WebInspector.Object.prototype | |
| 180 } | |
| 181 | |
| 182 /** | |
| 183 * @constructor | |
| 184 * @param {!WebInspector.MemoryStatistics} memoryCountersPane | |
| 185 * @param {string} title | |
| 186 * @param {string} graphColor | |
| 187 * @param {!WebInspector.MemoryStatistics.Counter} counter | |
| 188 */ | |
| 189 WebInspector.CounterUIBase = function(memoryCountersPane, title, graphColor, cou
nter) | |
| 190 { | |
| 191 this._memoryCountersPane = memoryCountersPane; | |
| 192 this.counter = counter; | |
| 193 var container = memoryCountersPane.sidebarElement().createChild("div", "memo
ry-counter-sidebar-info"); | |
| 194 var swatchColor = graphColor; | |
| 195 this._swatch = new WebInspector.SwatchCheckbox(WebInspector.UIString(title),
swatchColor); | |
| 196 this._swatch.addEventListener(WebInspector.SwatchCheckbox.Events.Changed, th
is._toggleCounterGraph.bind(this)); | |
| 197 container.appendChild(this._swatch.element); | |
| 198 | |
| 199 this._value = null; | |
| 200 this.graphColor = graphColor; | |
| 201 this.strokeColor = graphColor; | |
| 202 this.graphYValues = []; | |
| 203 } | |
| 204 | |
| 205 WebInspector.CounterUIBase.prototype = { | |
| 206 _toggleCounterGraph: function(event) | |
| 207 { | |
| 208 this._value.classList.toggle("hidden", !this._swatch.checked); | |
| 209 this._memoryCountersPane.refresh(); | |
| 210 }, | |
| 211 | |
| 212 /** | |
| 213 * @param {number} x | |
| 214 * @return {number} | |
| 215 */ | |
| 216 _recordIndexAt: function(x) | |
| 217 { | |
| 218 return this.counter.x.upperBound(x, null, this.counter._minimumIndex + 1
, this.counter._maximumIndex + 1) - 1; | |
| 219 }, | |
| 220 | |
| 221 /** | |
| 222 * @param {number} x | |
| 223 */ | |
| 224 updateCurrentValue: function(x) | |
| 225 { | |
| 226 if (!this.visible || !this.counter.values.length) | |
| 227 return; | |
| 228 var index = this._recordIndexAt(x); | |
| 229 this._value.textContent = WebInspector.UIString(this._currentValueLabel,
this.counter.values[index]); | |
| 230 var y = this.graphYValues[index]; | |
| 231 this._marker.style.left = x + "px"; | |
| 232 this._marker.style.top = y + "px"; | |
| 233 this._marker.classList.remove("hidden"); | |
| 234 }, | |
| 235 | |
| 236 clearCurrentValueAndMarker: function() | |
| 237 { | |
| 238 this._value.textContent = ""; | |
| 239 this._marker.classList.add("hidden"); | |
| 240 }, | |
| 241 | |
| 242 get visible() | |
| 243 { | |
| 244 return this._swatch.checked; | |
| 245 }, | |
| 246 } | |
| 247 | |
| 248 WebInspector.MemoryStatistics.prototype = { | |
| 249 _createCurrentValuesBar: function() | |
| 250 { | |
| 251 throw new Error("Not implemented"); | |
| 252 }, | |
| 253 | |
| 254 createAllCounters: function() | |
| 255 { | |
| 256 throw new Error("Not implemented"); | |
| 257 }, | |
| 258 | |
| 259 /** | |
| 260 * @param {!WebInspector.TimelineModel.Record} record | |
| 261 */ | |
| 262 addRecord: function(record) | |
| 263 { | |
| 264 throw new Error("Not implemented"); | |
| 265 }, | |
| 266 | |
| 267 reset: function() | |
| 268 { | |
| 269 for (var i = 0; i < this._counters.length; ++i) | |
| 270 this._counters[i].reset(); | |
| 271 | |
| 272 for (var i = 0; i < this._counterUI.length; ++i) | |
| 273 this._counterUI[i].reset(); | |
| 274 | |
| 275 this.refresh(); | |
| 276 }, | |
| 277 | |
| 278 _resize: function() | |
| 279 { | |
| 280 var parentElement = this._canvas.parentElement; | |
| 281 this._canvas.width = parentElement.clientWidth; | |
| 282 this._canvas.height = parentElement.clientHeight; | |
| 283 var timelinePaddingLeft = 15; | |
| 284 this._calculator.setDisplayWindow(timelinePaddingLeft, this._canvas.widt
h); | |
| 285 this.refresh(); | |
| 286 }, | |
| 287 | |
| 288 /** | |
| 289 * @param {number} startTime | |
| 290 * @param {number} endTime | |
| 291 */ | |
| 292 setWindowTimes: function(startTime, endTime) | |
| 293 { | |
| 294 this._calculator.setWindow(startTime, endTime); | |
| 295 this.scheduleRefresh(); | |
| 296 }, | |
| 297 | |
| 298 scheduleRefresh: function() | |
| 299 { | |
| 300 if (this._refreshTimer) | |
| 301 return; | |
| 302 this._refreshTimer = setTimeout(this.refresh.bind(this), 300); | |
| 303 }, | |
| 304 | |
| 305 draw: function() | |
| 306 { | |
| 307 for (var i = 0; i < this._counters.length; ++i) { | |
| 308 this._counters[i]._calculateVisibleIndexes(this._calculator); | |
| 309 this._counters[i]._calculateXValues(this._canvas.width); | |
| 310 } | |
| 311 this._clear(); | |
| 312 this._setVerticalClip(10, this._canvas.height - 20); | |
| 313 }, | |
| 314 | |
| 315 /** | |
| 316 * @param {?Event} event | |
| 317 */ | |
| 318 _onClick: function(event) | |
| 319 { | |
| 320 var x = event.x - this._canvasContainer.totalOffsetLeft(); | |
| 321 var minDistance = Infinity; | |
| 322 var bestTime; | |
| 323 for (var i = 0; i < this._counterUI.length; ++i) { | |
| 324 var counterUI = this._counterUI[i]; | |
| 325 if (!counterUI.counter.times.length) | |
| 326 continue; | |
| 327 var index = counterUI._recordIndexAt(x); | |
| 328 var distance = Math.abs(x - counterUI.counter.x[index]); | |
| 329 if (distance < minDistance) { | |
| 330 minDistance = distance; | |
| 331 bestTime = counterUI.counter.times[index]; | |
| 332 } | |
| 333 } | |
| 334 if (bestTime !== undefined) | |
| 335 this._revealRecordAt(bestTime); | |
| 336 }, | |
| 337 | |
| 338 /** | |
| 339 * @param {number} time | |
| 340 */ | |
| 341 _revealRecordAt: function(time) | |
| 342 { | |
| 343 var recordToReveal; | |
| 344 function findRecordToReveal(record) | |
| 345 { | |
| 346 if (record.startTime <= time && time <= record.endTime) { | |
| 347 recordToReveal = record; | |
| 348 return true; | |
| 349 } | |
| 350 // If there is no record containing the time than use the latest one
before that time. | |
| 351 if (!recordToReveal || record.endTime < time && recordToReveal.endTi
me < record.endTime) | |
| 352 recordToReveal = record; | |
| 353 return false; | |
| 354 } | |
| 355 this._model.forAllRecords(null, findRecordToReveal); | |
| 356 this._delegate.selectRecord(recordToReveal); | |
| 357 }, | |
| 358 | |
| 359 /** | |
| 360 * @param {?Event} event | |
| 361 */ | |
| 362 _onMouseOut: function(event) | |
| 363 { | |
| 364 delete this._markerXPosition; | |
| 365 this._clearCurrentValueAndMarker(); | |
| 366 }, | |
| 367 | |
| 368 _clearCurrentValueAndMarker: function() | |
| 369 { | |
| 370 for (var i = 0; i < this._counterUI.length; i++) | |
| 371 this._counterUI[i].clearCurrentValueAndMarker(); | |
| 372 }, | |
| 373 | |
| 374 /** | |
| 375 * @param {?Event} event | |
| 376 */ | |
| 377 _onMouseMove: function(event) | |
| 378 { | |
| 379 var x = event.x - this._canvasContainer.totalOffsetLeft(); | |
| 380 this._markerXPosition = x; | |
| 381 this._refreshCurrentValues(); | |
| 382 }, | |
| 383 | |
| 384 _refreshCurrentValues: function() | |
| 385 { | |
| 386 if (this._markerXPosition === undefined) | |
| 387 return; | |
| 388 for (var i = 0; i < this._counterUI.length; ++i) | |
| 389 this._counterUI[i].updateCurrentValue(this._markerXPosition); | |
| 390 }, | |
| 391 | |
| 392 refresh: function() | |
| 393 { | |
| 394 delete this._refreshTimer; | |
| 395 this._timelineGrid.updateDividers(this._calculator); | |
| 396 this.draw(); | |
| 397 this._refreshCurrentValues(); | |
| 398 }, | |
| 399 | |
| 400 refreshRecords: function() | |
| 401 { | |
| 402 this.reset(); | |
| 403 var records = this._model.records(); | |
| 404 for (var i = 0; i < records.length; ++i) | |
| 405 this.addRecord(records[i]); | |
| 406 }, | |
| 407 | |
| 408 /** | |
| 409 * @param {number} originY | |
| 410 * @param {number} height | |
| 411 */ | |
| 412 _setVerticalClip: function(originY, height) | |
| 413 { | |
| 414 this._originY = originY; | |
| 415 this._clippedHeight = height; | |
| 416 }, | |
| 417 | |
| 418 _clear: function() | |
| 419 { | |
| 420 var ctx = this._canvas.getContext("2d"); | |
| 421 ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); | |
| 422 }, | |
| 423 | |
| 424 /** | |
| 425 * @param {?WebInspector.TimelineModel.Record} record | |
| 426 * @param {string=} regex | |
| 427 * @param {boolean=} selectRecord | |
| 428 */ | |
| 429 highlightSearchResult: function(record, regex, selectRecord) | |
| 430 { | |
| 431 }, | |
| 432 | |
| 433 /** | |
| 434 * @param {?WebInspector.TimelineModel.Record} record | |
| 435 */ | |
| 436 setSelectedRecord: function(record) | |
| 437 { | |
| 438 }, | |
| 439 | |
| 440 __proto__: WebInspector.SplitView.prototype | |
| 441 } | |
| OLD | NEW |