OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 'use strict'; | 5 'use strict'; |
6 | 6 |
7 /** | 7 /** |
8 * @fileoverview TimelineView visualizes TRACE_EVENT events using the | 8 * @fileoverview TimelineView visualizes TRACE_EVENT events using the |
9 * tracing.Timeline component and adds in selection summary and control buttons. | 9 * tracing.Timeline component and adds in selection summary and control buttons. |
10 */ | 10 */ |
11 cr.define('tracing', function() { | 11 cr.define('tracing', function() { |
12 function tsRound(ts) { | |
13 return Math.round(ts * 1000.0) / 1000.0; | |
14 } | |
15 function getPadding(text, width) { | |
16 width = width || 0; | |
17 | |
18 if (typeof text != 'string') | |
19 text = String(text); | |
20 | |
21 if (text.length >= width) | |
22 return ''; | |
23 | |
24 var pad = ''; | |
25 for (var i = 0; i < width - text.length; i++) | |
26 pad += ' '; | |
27 return pad; | |
28 } | |
29 | |
30 function leftAlign(text, width) { | |
31 return text + getPadding(text, width); | |
32 } | |
33 | |
34 function rightAlign(text, width) { | |
35 return getPadding(text, width) + text; | |
36 } | |
37 | |
38 /** | 12 /** |
39 * TimelineFindControl | 13 * TimelineFindControl |
40 * @constructor | 14 * @constructor |
41 * @extends {tracing.Overlay} | 15 * @extends {tracing.Overlay} |
42 */ | 16 */ |
43 var TimelineFindControl = cr.ui.define('div'); | 17 var TimelineFindControl = cr.ui.define('div'); |
44 | 18 |
45 TimelineFindControl.prototype = { | 19 TimelineFindControl.prototype = { |
46 __proto__: tracing.Overlay.prototype, | 20 __proto__: tracing.Overlay.prototype, |
47 | 21 |
48 decorate: function() { | 22 decorate: function() { |
49 tracing.Overlay.prototype.decorate.call(this); | 23 tracing.Overlay.prototype.decorate.call(this); |
50 | 24 |
51 this.className = 'timeline-find-control'; | 25 this.className = 'timeline-find-control'; |
52 | 26 |
53 this.hitCountEl_ = document.createElement('span'); | 27 this.hitCountEl_ = document.createElement('div'); |
54 this.hitCountEl_.className = 'hit-count-label'; | 28 this.hitCountEl_.className = 'hit-count-label'; |
55 this.hitCountEl_.textContent = '1 of 7'; | 29 this.hitCountEl_.textContent = '1 of 7'; |
56 | 30 |
57 var findPreviousBn = document.createElement('span'); | 31 var findPreviousBn = document.createElement('div'); |
58 findPreviousBn.className = 'find-button find-previous'; | 32 findPreviousBn.className = 'timeline-button find-previous'; |
59 findPreviousBn.textContent = '\u2190'; | 33 findPreviousBn.textContent = '\u2190'; |
60 findPreviousBn.addEventListener('click', function() { | 34 findPreviousBn.addEventListener('click', function() { |
61 this.controller.findPrevious(); | 35 this.controller.findPrevious(); |
62 this.updateHitCountEl_(); | 36 this.updateHitCountEl_(); |
63 }.bind(this)); | 37 }.bind(this)); |
64 | 38 |
65 var findNextBn = document.createElement('span'); | 39 var findNextBn = document.createElement('div'); |
66 findNextBn.className = 'find-button find-next'; | 40 findNextBn.className = 'timeline-button find-next'; |
67 findNextBn.textContent = '\u2192'; | 41 findNextBn.textContent = '\u2192'; |
68 findNextBn.addEventListener('click', function() { | 42 findNextBn.addEventListener('click', function() { |
69 this.controller.findNext(); | 43 this.controller.findNext(); |
70 this.updateHitCountEl_(); | 44 this.updateHitCountEl_(); |
71 }.bind(this)); | 45 }.bind(this)); |
72 | 46 |
73 // Filter input element. | 47 // Filter input element. |
74 this.filterEl_ = document.createElement('input'); | 48 this.filterEl_ = document.createElement('input'); |
75 this.filterEl_.type = 'input'; | 49 this.filterEl_.type = 'input'; |
76 | 50 |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
132 this.hitCountEl_.textContent = '0 of 0'; | 106 this.hitCountEl_.textContent = '0 of 0'; |
133 else | 107 else |
134 this.hitCountEl_.textContent = (i + 1) + ' of ' + n; | 108 this.hitCountEl_.textContent = (i + 1) + ' of ' + n; |
135 } | 109 } |
136 }; | 110 }; |
137 | 111 |
138 function TimelineFindController() { | 112 function TimelineFindController() { |
139 this.timeline_ = undefined; | 113 this.timeline_ = undefined; |
140 this.model_ = undefined; | 114 this.model_ = undefined; |
141 this.filterText_ = ''; | 115 this.filterText_ = ''; |
| 116 this.filterHits_ = new tracing.TimelineSelection(); |
142 this.filterHitsDirty_ = true; | 117 this.filterHitsDirty_ = true; |
143 this.currentHitIndex_ = 0; | 118 this.currentHitIndex_ = 0; |
144 }; | 119 }; |
145 | 120 |
146 TimelineFindController.prototype = { | 121 TimelineFindController.prototype = { |
147 __proto__: Object.prototype, | 122 __proto__: Object.prototype, |
148 | 123 |
149 get timeline() { | 124 get timeline() { |
150 return this.timeline_; | 125 return this.timeline_; |
151 }, | 126 }, |
(...skipping 13 matching lines...) Expand all Loading... |
165 this.filterText_ = f; | 140 this.filterText_ = f; |
166 this.filterHitsDirty_ = true; | 141 this.filterHitsDirty_ = true; |
167 this.findNext(); | 142 this.findNext(); |
168 }, | 143 }, |
169 | 144 |
170 get filterHits() { | 145 get filterHits() { |
171 if (this.filterHitsDirty_) { | 146 if (this.filterHitsDirty_) { |
172 this.filterHitsDirty_ = false; | 147 this.filterHitsDirty_ = false; |
173 if (this.timeline_) { | 148 if (this.timeline_) { |
174 var filter = new tracing.TimelineFilter(this.filterText); | 149 var filter = new tracing.TimelineFilter(this.filterText); |
175 this.filterHits_ = this.timeline.findAllObjectsMatchingFilter(filter); | 150 this.filterHits_.clear(); |
| 151 this.timeline.addAllObjectsMatchingFilterToSelection( |
| 152 filter, this.filterHits_); |
176 this.currentHitIndex_ = this.filterHits_.length - 1; | 153 this.currentHitIndex_ = this.filterHits_.length - 1; |
177 } else { | 154 } else { |
178 this.filterHits_ = []; | 155 this.filterHits_.clear(); |
179 this.currentHitIndex_ = 0; | 156 this.currentHitIndex_ = 0; |
180 } | 157 } |
181 } | 158 } |
182 return this.filterHits_; | 159 return this.filterHits_; |
183 }, | 160 }, |
184 | 161 |
185 get currentHitIndex() { | 162 get currentHitIndex() { |
186 return this.currentHitIndex_; | 163 return this.currentHitIndex_; |
187 }, | 164 }, |
188 | 165 |
189 find_: function(dir) { | 166 find_: function(dir) { |
190 if (!this.timeline) | 167 if (!this.timeline) |
191 return; | 168 return; |
192 | 169 |
193 var N = this.filterHits.length; | 170 var N = this.filterHits.length; |
194 this.currentHitIndex_ = this.currentHitIndex_ + dir; | 171 this.currentHitIndex_ = this.currentHitIndex_ + dir; |
195 | 172 |
196 if (this.currentHitIndex_ < 0) this.currentHitIndex_ = N - 1; | 173 if (this.currentHitIndex_ < 0) this.currentHitIndex_ = N - 1; |
197 if (this.currentHitIndex_ >= N) this.currentHitIndex_ = 0; | 174 if (this.currentHitIndex_ >= N) this.currentHitIndex_ = 0; |
198 | 175 |
199 if (this.currentHitIndex_ < 0 || this.currentHitIndex_ >= N) { | 176 if (this.currentHitIndex_ < 0 || this.currentHitIndex_ >= N) { |
200 this.timeline.selection = []; | 177 this.timeline.selection = new tracing.TimelineSelection(); |
201 return; | 178 return; |
202 } | 179 } |
203 | 180 |
204 var hit = this.filterHits[this.currentHitIndex_]; | |
205 | |
206 // We allow the zoom level to change on the first hit level. But, when | 181 // We allow the zoom level to change on the first hit level. But, when |
207 // then cycling through subsequent changes, restrict it to panning. | 182 // then cycling through subsequent changes, restrict it to panning. |
208 var zoomAllowed = this.currentHitIndex_ == 0; | 183 var zoomAllowed = this.currentHitIndex_ == 0; |
209 this.timeline.setSelectionAndMakeVisible([hit], zoomAllowed); | 184 var subSelection = this.filterHits.subSelection(this.currentHitIndex_); |
| 185 this.timeline.setSelectionAndMakeVisible(subSelection, zoomAllowed); |
210 }, | 186 }, |
211 | 187 |
212 findNext: function() { | 188 findNext: function() { |
213 this.find_(1); | 189 this.find_(1); |
214 }, | 190 }, |
215 | 191 |
216 findPrevious: function() { | 192 findPrevious: function() { |
217 this.find_(-1); | 193 this.find_(-1); |
218 }, | 194 }, |
219 }; | 195 }; |
220 | 196 |
221 /** | 197 /** |
222 * TimelineView | 198 * TimelineView |
223 * @constructor | 199 * @constructor |
224 * @extends {HTMLDivElement} | 200 * @extends {HTMLDivElement} |
225 */ | 201 */ |
226 var TimelineView = cr.ui.define('div'); | 202 var TimelineView = cr.ui.define('div'); |
227 | 203 |
228 TimelineView.prototype = { | 204 TimelineView.prototype = { |
229 __proto__: HTMLDivElement.prototype, | 205 __proto__: HTMLDivElement.prototype, |
230 | 206 |
231 decorate: function() { | 207 decorate: function() { |
232 this.classList.add('timeline-view'); | 208 this.classList.add('timeline-view'); |
233 | 209 |
234 // Create individual elements. | 210 // Create individual elements. |
235 this.titleEl_ = document.createElement('span'); | 211 this.titleEl_ = document.createElement('div'); |
236 this.titleEl_.textContent = 'Tracing: '; | 212 this.titleEl_.textContent = 'Tracing: '; |
237 | 213 |
238 this.controlDiv_ = document.createElement('div'); | 214 this.controlDiv_ = document.createElement('div'); |
239 this.controlDiv_.className = 'control'; | 215 this.controlDiv_.className = 'control'; |
240 | 216 |
241 this.leftControlsEl_ = document.createElement('div'); | 217 this.leftControlsEl_ = document.createElement('div'); |
| 218 this.leftControlsEl_.className = 'controls'; |
242 this.rightControlsEl_ = document.createElement('div'); | 219 this.rightControlsEl_ = document.createElement('div'); |
| 220 this.rightControlsEl_.className = 'controls'; |
243 | 221 |
244 var spacingEl = document.createElement('div'); | 222 var spacingEl = document.createElement('div'); |
245 spacingEl.className = 'spacer'; | 223 spacingEl.className = 'spacer'; |
246 | 224 |
247 this.timelineContainer_ = document.createElement('div'); | 225 this.timelineContainer_ = document.createElement('div'); |
248 this.timelineContainer_.className = 'timeline-container'; | 226 this.timelineContainer_.className = 'timeline-container'; |
249 | 227 |
250 var summaryContainer_ = document.createElement('div'); | 228 var analysisContainer_ = document.createElement('div'); |
251 summaryContainer_.className = 'summary-container'; | 229 analysisContainer_.className = 'analysis-container'; |
252 | 230 |
253 this.summaryEl_ = document.createElement('pre'); | 231 this.analysisEl_ = new tracing.TimelineAnalysisView(); |
254 this.summaryEl_.className = 'summary'; | |
255 | 232 |
256 this.findCtl_ = new TimelineFindControl(); | 233 this.findCtl_ = new TimelineFindControl(); |
257 this.findCtl_.controller = new TimelineFindController(); | 234 this.findCtl_.controller = new TimelineFindController(); |
258 | 235 |
259 // Connect everything up. | 236 // Connect everything up. |
260 this.rightControls.appendChild(this.findCtl_); | 237 this.rightControls.appendChild(this.findCtl_); |
261 this.controlDiv_.appendChild(this.titleEl_); | 238 this.controlDiv_.appendChild(this.titleEl_); |
262 this.controlDiv_.appendChild(this.leftControlsEl_); | 239 this.controlDiv_.appendChild(this.leftControlsEl_); |
263 this.controlDiv_.appendChild(spacingEl); | 240 this.controlDiv_.appendChild(spacingEl); |
264 this.controlDiv_.appendChild(this.rightControlsEl_); | 241 this.controlDiv_.appendChild(this.rightControlsEl_); |
265 this.appendChild(this.controlDiv_); | 242 this.appendChild(this.controlDiv_); |
266 | 243 |
267 this.appendChild(this.timelineContainer_); | 244 this.appendChild(this.timelineContainer_); |
268 | 245 |
269 summaryContainer_.appendChild(this.summaryEl_); | 246 analysisContainer_.appendChild(this.analysisEl_); |
270 this.appendChild(summaryContainer_); | 247 this.appendChild(analysisContainer_); |
| 248 |
| 249 this.rightControls.appendChild(this.createHelpButton_()); |
271 | 250 |
272 // Bookkeeping. | 251 // Bookkeeping. |
273 this.onSelectionChangedBoundToThis_ = this.onSelectionChanged_.bind(this); | 252 this.onSelectionChangedBoundToThis_ = this.onSelectionChanged_.bind(this); |
274 document.addEventListener('keypress', this.onKeypress_.bind(this), true); | 253 document.addEventListener('keypress', this.onKeypress_.bind(this), true); |
275 }, | 254 }, |
276 | 255 |
| 256 createHelpButton_: function() { |
| 257 var dlg = new tracing.Overlay(); |
| 258 dlg.classList.add('timeline-view-help-overlay'); |
| 259 |
| 260 var showHelpEl = document.createElement('div'); |
| 261 showHelpEl.className = 'timeline-button timeline-view-help-button'; |
| 262 showHelpEl.textContent = '?'; |
| 263 |
| 264 var helpTextEl = document.createElement('div'); |
| 265 helpTextEl.style.whiteSpace = 'pre'; |
| 266 helpTextEl.style.fontFamily = 'monospace'; |
| 267 |
| 268 function onClick() { |
| 269 dlg.visible = true; |
| 270 helpTextEl.textContent = this.timeline_.keyHelp; |
| 271 document.addEventListener('keydown', onKey, true); |
| 272 } |
| 273 |
| 274 function onKey(e) { |
| 275 if (!dlg.visible) |
| 276 return; |
| 277 |
| 278 if (e.keyCode == 27 || e.keyCode == '?'.charCodeAt(0)) { |
| 279 e.preventDefault(); |
| 280 document.removeEventListener('keydown', onKey); |
| 281 dlg.visible = false; |
| 282 } |
| 283 } |
| 284 showHelpEl.addEventListener('click', onClick.bind(this)); |
| 285 |
| 286 dlg.appendChild(helpTextEl); |
| 287 |
| 288 return showHelpEl; |
| 289 }, |
| 290 |
277 get leftControls() { | 291 get leftControls() { |
278 return this.leftControlsEl_; | 292 return this.leftControlsEl_; |
279 }, | 293 }, |
280 | 294 |
281 get rightControls() { | 295 get rightControls() { |
282 return this.rightControlsEl_; | 296 return this.rightControlsEl_; |
283 }, | 297 }, |
284 | 298 |
285 get title() { | 299 get title() { |
286 return this.titleEl_.textContent.substring( | 300 return this.titleEl_.textContent.substring( |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
368 return true; | 382 return true; |
369 if (this.focusElement.tabIndex >= 0) | 383 if (this.focusElement.tabIndex >= 0) |
370 return document.activeElement == this.focusElement; | 384 return document.activeElement == this.focusElement; |
371 return true; | 385 return true; |
372 }, | 386 }, |
373 | 387 |
374 onKeypress_: function(e) { | 388 onKeypress_: function(e) { |
375 if (!this.listenToKeys_) | 389 if (!this.listenToKeys_) |
376 return; | 390 return; |
377 | 391 |
378 if (event.keyCode == 47) { | 392 if (event.keyCode == '/'.charCodeAt(0)) { // / key |
379 this.findCtl_.focus(); | 393 this.findCtl_.focus(); |
380 event.preventDefault(); | 394 event.preventDefault(); |
381 return; | 395 return; |
| 396 } else if (e.keyCode == '?'.charCodeAt(0)) { |
| 397 this.querySelector('.timeline-view-help-button').click(); |
| 398 e.preventDefault(); |
382 } | 399 } |
383 }, | 400 }, |
384 | 401 |
385 beginFind: function() { | 402 beginFind: function() { |
386 if (this.findInProgress_) | 403 if (this.findInProgress_) |
387 return; | 404 return; |
388 this.findInProgress_ = true; | 405 this.findInProgress_ = true; |
389 var dlg = TimelineFindControl(); | 406 var dlg = TimelineFindControl(); |
390 dlg.controller = new TimelineFindController(); | 407 dlg.controller = new TimelineFindController(); |
391 dlg.controller.timeline = this.timeline; | 408 dlg.controller.timeline = this.timeline; |
392 dlg.visible = true; | 409 dlg.visible = true; |
393 dlg.addEventListener('close', function() { | 410 dlg.addEventListener('close', function() { |
394 this.findInProgress_ = false; | 411 this.findInProgress_ = false; |
395 }.bind(this)); | 412 }.bind(this)); |
396 dlg.addEventListener('findNext', function() { | 413 dlg.addEventListener('findNext', function() { |
397 }); | 414 }); |
398 dlg.addEventListener('findPrevious', function() { | 415 dlg.addEventListener('findPrevious', function() { |
399 }); | 416 }); |
400 }, | 417 }, |
401 | 418 |
402 onSelectionChanged_: function(e) { | 419 onSelectionChanged_: function(e) { |
403 var timeline = this.timeline_; | |
404 var selection = timeline.selection; | |
405 if (!selection.length) { | |
406 var oldScrollTop = this.timelineContainer_.scrollTop; | |
407 this.summaryEl_.textContent = timeline.keyHelp; | |
408 this.timelineContainer_.scrollTop = oldScrollTop; | |
409 return; | |
410 } | |
411 | |
412 var text = ''; | |
413 if (selection.length == 1) { | |
414 var c0Width = 14; | |
415 var slice = selection[0].slice; | |
416 text = 'Selected item:\n'; | |
417 text += leftAlign('Title', c0Width) + ': ' + slice.title + '\n'; | |
418 text += leftAlign('Start', c0Width) + ': ' + | |
419 tsRound(slice.start) + ' ms\n'; | |
420 text += leftAlign('Duration', c0Width) + ': ' + | |
421 tsRound(slice.duration) + ' ms\n'; | |
422 if (slice.durationInUserTime) | |
423 text += leftAlign('Duration (U)', c0Width) + ': ' + | |
424 tsRound(slice.durationInUserTime) + ' ms\n'; | |
425 | |
426 var n = 0; | |
427 for (var argName in slice.args) { | |
428 n += 1; | |
429 } | |
430 if (n > 0) { | |
431 text += leftAlign('Args', c0Width) + ':\n'; | |
432 for (var argName in slice.args) { | |
433 var argVal = slice.args[argName]; | |
434 text += leftAlign(' ' + argName, c0Width) + ': ' + argVal + '\n'; | |
435 } | |
436 } | |
437 } else { | |
438 var c0Width = 55; | |
439 var c1Width = 12; | |
440 var c2Width = 5; | |
441 text = 'Selection summary:\n'; | |
442 var tsLo = Math.min.apply(Math, selection.map( | |
443 function(s) {return s.slice.start;})); | |
444 var tsHi = Math.max.apply(Math, selection.map( | |
445 function(s) {return s.slice.end;})); | |
446 | |
447 // compute total selection duration | |
448 var titles = selection.map(function(i) { return i.slice.title; }); | |
449 | |
450 var slicesByTitle = {}; | |
451 for (var i = 0; i < selection.length; i++) { | |
452 var slice = selection[i].slice; | |
453 if (!slicesByTitle[slice.title]) | |
454 slicesByTitle[slice.title] = { | |
455 slices: [] | |
456 }; | |
457 slicesByTitle[slice.title].slices.push(slice); | |
458 } | |
459 var totalDuration = 0; | |
460 for (var sliceGroupTitle in slicesByTitle) { | |
461 var sliceGroup = slicesByTitle[sliceGroupTitle]; | |
462 var duration = 0; | |
463 for (i = 0; i < sliceGroup.slices.length; i++) | |
464 duration += sliceGroup.slices[i].duration; | |
465 totalDuration += duration; | |
466 | |
467 text += ' ' + | |
468 leftAlign(sliceGroupTitle, c0Width) + ': ' + | |
469 rightAlign(tsRound(duration) + 'ms', c1Width) + ' ' + | |
470 rightAlign(String(sliceGroup.slices.length), c2Width) + | |
471 ' occurrences' + '\n'; | |
472 } | |
473 | |
474 text += leftAlign('*Totals', c0Width) + ' : ' + | |
475 rightAlign(tsRound(totalDuration) + 'ms', c1Width) + ' ' + | |
476 rightAlign(String(selection.length), c2Width) + ' occurrences' + | |
477 '\n'; | |
478 | |
479 text += '\n'; | |
480 | |
481 text += leftAlign('Selection start', c0Width) + ' : ' + | |
482 rightAlign(tsRound(tsLo) + 'ms', c1Width) + | |
483 '\n'; | |
484 text += leftAlign('Selection extent', c0Width) + ' : ' + | |
485 rightAlign(tsRound(tsHi - tsLo) + 'ms', c1Width) + | |
486 '\n'; | |
487 } | |
488 | |
489 // done | |
490 var oldScrollTop = this.timelineContainer_.scrollTop; | 420 var oldScrollTop = this.timelineContainer_.scrollTop; |
491 this.summaryEl_.textContent = text; | 421 this.analysisEl_.selection = this.timeline_.selection; |
492 this.timelineContainer_.scrollTop = oldScrollTop; | 422 this.timelineContainer_.scrollTop = oldScrollTop; |
493 } | 423 } |
494 }; | 424 }; |
495 | 425 |
496 return { | 426 return { |
497 TimelineFindControl: TimelineFindControl, | 427 TimelineFindControl: TimelineFindControl, |
498 TimelineFindController: TimelineFindController, | 428 TimelineFindController: TimelineFindController, |
499 TimelineView: TimelineView | 429 TimelineView: TimelineView |
500 }; | 430 }; |
501 }); | 431 }); |
OLD | NEW |