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 |