Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(658)

Side by Side Diff: chrome/browser/resources/tracing/timeline_track.js

Issue 10543144: Remove old tracing code now that it has moved to third_party. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5
6 /**
7 * @fileoverview Renders an array of slices into the provided div,
8 * using a child canvas element. Uses a FastRectRenderer to draw only
9 * the visible slices.
10 */
11 cr.define('tracing', function() {
12
13 var pallette = tracing.getPallette();
14 var highlightIdBoost = tracing.getPalletteHighlightIdBoost();
15
16 // TODO(jrg): possibly obsoleted with the elided string cache.
17 // Consider removing.
18 var textWidthMap = { };
19 function quickMeasureText(ctx, text) {
20 var w = textWidthMap[text];
21 if (!w) {
22 w = ctx.measureText(text).width;
23 textWidthMap[text] = w;
24 }
25 return w;
26 }
27
28 /**
29 * Cache for elided strings.
30 * Moved from the ElidedTitleCache protoype to a "global" for speed
31 * (variable reference is 100x faster).
32 * key: String we wish to elide.
33 * value: Another dict whose key is width
34 * and value is an ElidedStringWidthPair.
35 */
36 var elidedTitleCacheDict = {};
37
38 /**
39 * A generic track that contains other tracks as its children.
40 * @constructor
41 */
42 var TimelineContainerTrack = cr.ui.define('div');
43 TimelineContainerTrack.prototype = {
44 __proto__: HTMLDivElement.prototype,
45
46 decorate: function() {
47 this.tracks_ = [];
48 },
49
50 detach: function() {
51 for (var i = 0; i < this.tracks_.length; i++)
52 this.tracks_[i].detach();
53 },
54
55 get viewport() {
56 return this.viewport_;
57 },
58
59 set viewport(v) {
60 this.viewport_ = v;
61 for (var i = 0; i < this.tracks_.length; i++)
62 this.tracks_[i].viewport = v;
63 this.updateChildTracks_();
64 },
65
66 get firstCanvas() {
67 if (this.tracks_.length)
68 return this.tracks_[0].firstCanvas;
69 return undefined;
70 },
71
72 /**
73 * Adds items intersecting a point to a selection.
74 * @param {number} wX X location to search at, in worldspace.
75 * @param {number} wY Y location to search at, in offset space.
76 * offset space.
77 * @param {TimelineSelection} selection Selection to which to add hits.
78 * @return {boolean} true if a slice was found, otherwise false.
79 */
80 addIntersectingItemsToSelection: function(wX, wY, selection) {
81 for (var i = 0; i < this.tracks_.length; i++) {
82 var trackClientRect = this.tracks_[i].getBoundingClientRect();
83 if (wY >= trackClientRect.top && wY < trackClientRect.bottom)
84 this.tracks_[i].addIntersectingItemsToSelection(wX, wY, selection);
85 }
86 return false;
87 },
88
89 /**
90 * Adds items intersecting the given range to a selection.
91 * @param {number} loWX Lower X bound of the interval to search, in
92 * worldspace.
93 * @param {number} hiWX Upper X bound of the interval to search, in
94 * worldspace.
95 * @param {number} loY Lower Y bound of the interval to search, in
96 * offset space.
97 * @param {number} hiY Upper Y bound of the interval to search, in
98 * offset space.
99 * @param {TimelineSelection} selection Selection to which to add hits.
100 */
101 addIntersectingItemsInRangeToSelection: function(
102 loWX, hiWX, loY, hiY, selection) {
103 for (var i = 0; i < this.tracks_.length; i++) {
104 var trackClientRect = this.tracks_[i].getBoundingClientRect();
105 var a = Math.max(loY, trackClientRect.top);
106 var b = Math.min(hiY, trackClientRect.bottom);
107 if (a <= b)
108 this.tracks_[i].addIntersectingItemsInRangeToSelection(
109 loWX, hiWX, loY, hiY, selection);
110 }
111 },
112
113 addAllObjectsMatchingFilterToSelection: function(filter, selection) {
114 for (var i = 0; i < this.tracks_.length; i++)
115 this.tracks_[i].addAllObjectsMatchingFilterToSelection(
116 filter, selection);
117 }
118 };
119
120 function addControlButtonElements(el, canCollapse) {
121 var closeEl = document.createElement('div');
122 closeEl.classList.add('timeline-track-button');
123 closeEl.classList.add('timeline-track-close-button');
124 closeEl.textContent = String.fromCharCode(215); // &times;
125 closeEl.addEventListener('click', function() {
126 el.style.display = 'None';
127 });
128 el.appendChild(closeEl);
129
130 if (canCollapse) {
131 var collapseEl = document.createElement('div');
132 collapseEl.classList.add('timeline-track-button');
133 collapseEl.classList.add('timeline-track-collapse-button');
134 var minus = '\u2212'; // minus sign;
135 var plus = '\u002b'; // plus sign;
136 collapseEl.textContent = minus;
137 var collapsed = false;
138 collapseEl.addEventListener('click', function() {
139 collapsed = !collapsed;
140 el.collapsedDidChange(collapsed);
141 collapseEl.textContent = collapsed ? plus : minus;
142 });
143 el.appendChild(collapseEl);
144 }
145 }
146
147 /**
148 * Visualizes a TimelineThread using a series of of TimelineSliceTracks.
149 * @constructor
150 */
151 var TimelineThreadTrack = cr.ui.define(TimelineContainerTrack);
152 TimelineThreadTrack.prototype = {
153 __proto__: TimelineContainerTrack.prototype,
154
155 decorate: function() {
156 this.classList.add('timeline-thread-track');
157 },
158
159 get thread() {
160 return this.thread_;
161 },
162
163 set thread(thread) {
164 this.thread_ = thread;
165 this.updateChildTracks_();
166 },
167
168 get tooltip() {
169 return this.tooltip_;
170 },
171
172 set tooltip(value) {
173 this.tooltip_ = value;
174 this.updateChildTracks_();
175 },
176
177 get heading() {
178 return this.heading_;
179 },
180
181 set heading(h) {
182 this.heading_ = h;
183 this.updateChildTracks_();
184 },
185
186 get headingWidth() {
187 return this.headingWidth_;
188 },
189
190 set headingWidth(width) {
191 this.headingWidth_ = width;
192 this.updateChildTracks_();
193 },
194
195 addTrack_: function(slices) {
196 var track = new TimelineSliceTrack();
197 track.heading = '';
198 track.slices = slices;
199 track.headingWidth = this.headingWidth_;
200 track.viewport = this.viewport_;
201
202 this.tracks_.push(track);
203 this.appendChild(track);
204 return track;
205 },
206
207 updateChildTracks_: function() {
208 this.detach();
209 this.textContent = '';
210 this.tracks_ = [];
211 if (this.thread_) {
212 if (this.thread_.cpuSlices) {
213 var track = this.addTrack_(this.thread_.cpuSlices);
214 track.height = '4px';
215 track.decorateHit = function(hit) {
216 hit.thread = this.thread_;
217 }
218 }
219
220 if (this.thread_.asyncSlices.length) {
221 var subRows = this.thread_.asyncSlices.subRows;
222 for (var srI = 0; srI < subRows.length; srI++) {
223 var track = this.addTrack_(subRows[srI]);
224 track.decorateHit = function(hit) {
225 // TODO(simonjam): figure out how to associate subSlice hits back
226 // to their parent slice.
227 }
228 track.asyncStyle = true;
229 }
230 }
231
232 for (var srI = 0; srI < this.thread_.subRows.length; srI++) {
233 var track = this.addTrack_(this.thread_.subRows[srI]);
234 track.decorateHit = function(hit) {
235 hit.thread = this.thread_;
236 }
237 }
238
239 if (this.tracks_.length > 0) {
240 if (this.thread_.cpuSlices) {
241 this.tracks_[1].heading = this.heading_;
242 this.tracks_[1].tooltip = this.tooltip_;
243 } else {
244 this.tracks_[0].heading = this.heading_;
245 this.tracks_[0].tooltip = this.tooltip_;
246 }
247 }
248 }
249 addControlButtonElements(this, this.tracks_.length >= 4);
250 },
251
252 collapsedDidChange: function(collapsed) {
253 if (collapsed) {
254 var h = parseInt(this.tracks_[0].height);
255 for (var i = 0; i < this.tracks_.length; ++i) {
256 if (h > 2) {
257 this.tracks_[i].height = Math.floor(h) + 'px';
258 } else {
259 this.tracks_[i].style.display = 'None';
260 }
261 h = h * 0.5;
262 }
263 } else {
264 for (var i = 0; i < this.tracks_.length; ++i) {
265 this.tracks_[i].height = this.tracks_[0].height;
266 this.tracks_[i].style.display = '';
267 }
268 }
269 }
270 };
271
272 /**
273 * Visualizes a TimelineCpu using a series of of TimelineSliceTracks.
274 * @constructor
275 */
276 var TimelineCpuTrack = cr.ui.define(TimelineContainerTrack);
277 TimelineCpuTrack.prototype = {
278 __proto__: TimelineContainerTrack.prototype,
279
280 decorate: function() {
281 this.classList.add('timeline-thread-track');
282 },
283
284 get cpu() {
285 return this.cpu_;
286 },
287
288 set cpu(cpu) {
289 this.cpu_ = cpu;
290 this.updateChildTracks_();
291 },
292
293 get tooltip() {
294 return this.tooltip_;
295 },
296
297 set tooltip(value) {
298 this.tooltip_ = value;
299 this.updateChildTracks_();
300 },
301
302 get heading() {
303 return this.heading_;
304 },
305
306 set heading(h) {
307 this.heading_ = h;
308 this.updateChildTracks_();
309 },
310
311 get headingWidth() {
312 return this.headingWidth_;
313 },
314
315 set headingWidth(width) {
316 this.headingWidth_ = width;
317 this.updateChildTracks_();
318 },
319
320 updateChildTracks_: function() {
321 this.detach();
322 this.textContent = '';
323 this.tracks_ = [];
324 if (this.cpu_) {
325 var track = new TimelineSliceTrack();
326 track.slices = this.cpu_.slices;
327 track.headingWidth = this.headingWidth_;
328 track.viewport = this.viewport_;
329
330 this.tracks_.push(track);
331 this.appendChild(track);
332
333 this.tracks_[0].heading = this.heading_;
334 this.tracks_[0].tooltip = this.tooltip_;
335 }
336 addControlButtonElements(this, false);
337 }
338 };
339
340 /**
341 * A canvas-based track constructed. Provides the basic heading and
342 * invalidation-managment infrastructure. Subclasses must implement drawing
343 * and picking code.
344 * @constructor
345 * @extends {HTMLDivElement}
346 */
347 var CanvasBasedTrack = cr.ui.define('div');
348
349 CanvasBasedTrack.prototype = {
350 __proto__: HTMLDivElement.prototype,
351
352 decorate: function() {
353 this.className = 'timeline-canvas-based-track';
354 this.slices_ = null;
355
356 this.headingDiv_ = document.createElement('div');
357 this.headingDiv_.className = 'timeline-canvas-based-track-title';
358 this.appendChild(this.headingDiv_);
359
360 this.canvasContainer_ = document.createElement('div');
361 this.canvasContainer_.className =
362 'timeline-canvas-based-track-canvas-container';
363 this.appendChild(this.canvasContainer_);
364 this.canvas_ = document.createElement('canvas');
365 this.canvas_.className = 'timeline-canvas-based-track-canvas';
366 this.canvasContainer_.appendChild(this.canvas_);
367
368 this.ctx_ = this.canvas_.getContext('2d');
369 },
370
371 detach: function() {
372 if (this.viewport_)
373 this.viewport_.removeEventListener('change',
374 this.viewportChangeBoundToThis_);
375 },
376
377 set headingWidth(width) {
378 this.headingDiv_.style.width = width;
379 },
380
381 get heading() {
382 return this.headingDiv_.textContent;
383 },
384
385 set heading(text) {
386 this.headingDiv_.textContent = text;
387 },
388
389 set tooltip(text) {
390 this.headingDiv_.title = text;
391 },
392
393 get viewport() {
394 return this.viewport_;
395 },
396
397 set viewport(v) {
398 this.viewport_ = v;
399 if (this.viewport_)
400 this.viewport_.removeEventListener('change',
401 this.viewportChangeBoundToThis_);
402 this.viewport_ = v;
403 if (this.viewport_) {
404 this.viewportChangeBoundToThis_ = this.viewportChange_.bind(this);
405 this.viewport_.addEventListener('change',
406 this.viewportChangeBoundToThis_);
407 }
408 this.invalidate();
409 },
410
411 viewportChange_: function() {
412 this.invalidate();
413 },
414
415 invalidate: function() {
416 if (this.rafPending_)
417 return;
418 webkitRequestAnimationFrame(function() {
419 this.rafPending_ = false;
420 if (!this.viewport_)
421 return;
422
423 var style = window.getComputedStyle(this.canvasContainer_);
424 var style_width = parseInt(style.width);
425 var style_height = parseInt(style.height);
426 if (this.canvas_.width != style_width)
427 this.canvas_.width = style_width;
428 if (this.canvas_.height != style_height)
429 this.canvas_.height = style_height;
430
431 this.redraw();
432 }.bind(this), this);
433 this.rafPending_ = true;
434 },
435
436 get firstCanvas() {
437 return this.canvas_;
438 }
439
440 };
441
442 /**
443 * A pair representing an elided string and world-coordinate width
444 * to draw it.
445 * @constructor
446 */
447 function ElidedStringWidthPair(string, width) {
448 this.string = string;
449 this.width = width;
450 }
451
452 /**
453 * A cache for elided strings.
454 * @constructor
455 */
456 function ElidedTitleCache() {
457 }
458
459 ElidedTitleCache.prototype = {
460 /**
461 * Return elided text.
462 * @param {track} A timeline slice track or other object that defines
463 * functions labelWidth() and labelWidthWorld().
464 * @param {pixWidth} Pixel width.
465 * @param {title} Original title text.
466 * @param {width} Drawn width in world coords.
467 * @param {sliceDuration} Where the title must fit (in world coords).
468 * @return {ElidedStringWidthPair} Elided string and width.
469 */
470 get: function(track, pixWidth, title, width, sliceDuration) {
471 var elidedDict = elidedTitleCacheDict[title];
472 if (!elidedDict) {
473 elidedDict = {};
474 elidedTitleCacheDict[title] = elidedDict;
475 }
476 var elidedDictForPixWidth = elidedDict[pixWidth];
477 if (!elidedDictForPixWidth) {
478 elidedDict[pixWidth] = {};
479 elidedDictForPixWidth = elidedDict[pixWidth];
480 }
481 var stringWidthPair = elidedDictForPixWidth[sliceDuration];
482 if (stringWidthPair === undefined) {
483 var newtitle = title;
484 var elided = false;
485 while (track.labelWidthWorld(newtitle, pixWidth) > sliceDuration) {
486 newtitle = newtitle.substring(0, newtitle.length * 0.75);
487 elided = true;
488 }
489 if (elided && newtitle.length > 3)
490 newtitle = newtitle.substring(0, newtitle.length - 3) + '...';
491 stringWidthPair = new ElidedStringWidthPair(
492 newtitle,
493 track.labelWidth(newtitle));
494 elidedDictForPixWidth[sliceDuration] = stringWidthPair;
495 }
496 return stringWidthPair;
497 }
498 };
499
500 /**
501 * A track that displays an array of TimelineSlice objects.
502 * @constructor
503 * @extends {CanvasBasedTrack}
504 */
505
506 var TimelineSliceTrack = cr.ui.define(CanvasBasedTrack);
507
508 TimelineSliceTrack.prototype = {
509
510 __proto__: CanvasBasedTrack.prototype,
511
512 /**
513 * Should we elide text on trace labels?
514 * Without eliding, text that is too wide isn't drawn at all.
515 * Disable if you feel this causes a performance problem.
516 * This is a default value that can be overridden in tracks for testing.
517 * @const
518 */
519 SHOULD_ELIDE_TEXT: true,
520
521 decorate: function() {
522 this.classList.add('timeline-slice-track');
523 this.elidedTitleCache = new ElidedTitleCache();
524 this.asyncStyle_ = false;
525 },
526
527 /**
528 * Called by all the addToSelection functions on the created selection
529 * hit objects. Override this function on parent classes to add
530 * context-specific information to the hit.
531 */
532 decorateHit: function(hit) {
533 },
534
535 get asyncStyle() {
536 return this.asyncStyle_;
537 },
538
539 set asyncStyle(v) {
540 this.asyncStyle_ = !!v;
541 this.invalidate();
542 },
543
544 get slices() {
545 return this.slices_;
546 },
547
548 set slices(slices) {
549 this.slices_ = slices;
550 this.invalidate();
551 },
552
553 get height() {
554 return window.getComputedStyle(this).height;
555 },
556
557 set height(height) {
558 this.style.height = height;
559 this.invalidate();
560 },
561
562 labelWidth: function(title) {
563 return quickMeasureText(this.ctx_, title) + 2;
564 },
565
566 labelWidthWorld: function(title, pixWidth) {
567 return this.labelWidth(title) * pixWidth;
568 },
569
570 redraw: function() {
571 var ctx = this.ctx_;
572 var canvasW = this.canvas_.width;
573 var canvasH = this.canvas_.height;
574
575 ctx.clearRect(0, 0, canvasW, canvasH);
576
577 // Culling parameters.
578 var vp = this.viewport_;
579 var pixWidth = vp.xViewVectorToWorld(1);
580 var viewLWorld = vp.xViewToWorld(0);
581 var viewRWorld = vp.xViewToWorld(canvasW);
582
583 // Draw grid without a transform because the scale
584 // affects line width.
585 if (vp.gridEnabled) {
586 var x = vp.gridTimebase;
587 ctx.beginPath();
588 while (x < viewRWorld) {
589 if (x >= viewLWorld) {
590 // Do conversion to viewspace here rather than on
591 // x to avoid precision issues.
592 var vx = vp.xWorldToView(x);
593 ctx.moveTo(vx, 0);
594 ctx.lineTo(vx, canvasH);
595 }
596 x += vp.gridStep;
597 }
598 ctx.strokeStyle = 'rgba(255,0,0,0.25)';
599 ctx.stroke();
600 }
601
602 // Begin rendering in world space.
603 ctx.save();
604 vp.applyTransformToCanavs(ctx);
605
606 // Slices.
607 if (this.asyncStyle_)
608 ctx.globalAlpha = 0.25;
609 var tr = new tracing.FastRectRenderer(ctx, viewLWorld, 2 * pixWidth,
610 2 * pixWidth, viewRWorld, pallette);
611 tr.setYandH(0, canvasH);
612 var slices = this.slices_;
613 for (var i = 0; i < slices.length; ++i) {
614 var slice = slices[i];
615 var x = slice.start;
616 // Less than 0.001 causes short events to disappear when zoomed in.
617 var w = Math.max(slice.duration, 0.001);
618 var colorId = slice.selected ?
619 slice.colorId + highlightIdBoost :
620 slice.colorId;
621
622 if (w < pixWidth)
623 w = pixWidth;
624 if (slice.duration > 0) {
625 tr.fillRect(x, w, colorId);
626 } else {
627 // Instant: draw a triangle. If zoomed too far, collapse
628 // into the FastRectRenderer.
629 if (pixWidth > 0.001) {
630 tr.fillRect(x, pixWidth, colorId);
631 } else {
632 ctx.fillStyle = pallette[colorId];
633 ctx.beginPath();
634 ctx.moveTo(x - (4 * pixWidth), canvasH);
635 ctx.lineTo(x, 0);
636 ctx.lineTo(x + (4 * pixWidth), canvasH);
637 ctx.closePath();
638 ctx.fill();
639 }
640 }
641 }
642 tr.flush();
643 ctx.restore();
644
645 // Labels.
646 if (canvasH > 8) {
647 ctx.textAlign = 'center';
648 ctx.textBaseline = 'top';
649 ctx.font = '10px sans-serif';
650 ctx.strokeStyle = 'rgb(0,0,0)';
651 ctx.fillStyle = 'rgb(0,0,0)';
652 // Don't render text until until it is 20px wide
653 var quickDiscardThresshold = pixWidth * 20;
654 var shouldElide = this.SHOULD_ELIDE_TEXT;
655 for (var i = 0; i < slices.length; ++i) {
656 var slice = slices[i];
657 if (slice.duration > quickDiscardThresshold) {
658 var title = slice.title;
659 if (slice.didNotFinish) {
660 title += ' (Did Not Finish)';
661 }
662 var drawnTitle = title;
663 var drawnWidth = this.labelWidth(drawnTitle);
664 if (shouldElide &&
665 this.labelWidthWorld(drawnTitle, pixWidth) > slice.duration) {
666 var elidedValues = this.elidedTitleCache.get(
667 this, pixWidth,
668 drawnTitle, drawnWidth,
669 slice.duration);
670 drawnTitle = elidedValues.string;
671 drawnWidth = elidedValues.width;
672 }
673 if (drawnWidth * pixWidth < slice.duration) {
674 var cX = vp.xWorldToView(slice.start + 0.5 * slice.duration);
675 ctx.fillText(drawnTitle, cX, 2.5, drawnWidth);
676 }
677 }
678 }
679 }
680 },
681
682 /**
683 * Finds slices intersecting the given interval.
684 * @param {number} wX X location to search at, in worldspace.
685 * @param {number} wY Y location to search at, in offset space.
686 * offset space.
687 * @param {TimelineSelection} selection Selection to which to add hits.
688 * @return {boolean} true if a slice was found, otherwise false.
689 */
690 addIntersectingItemsToSelection: function(wX, wY, selection) {
691 var clientRect = this.getBoundingClientRect();
692 if (wY < clientRect.top || wY >= clientRect.bottom)
693 return false;
694 var x = tracing.findLowIndexInSortedIntervals(this.slices_,
695 function(x) { return x.start; },
696 function(x) { return x.duration; },
697 wX);
698 if (x >= 0 && x < this.slices_.length) {
699 var hit = selection.addSlice(this, this.slices_[x]);
700 this.decorateHit(hit);
701 return true;
702 }
703 return false;
704 },
705
706 /**
707 * Adds items intersecting the given range to a selection.
708 * @param {number} loWX Lower X bound of the interval to search, in
709 * worldspace.
710 * @param {number} hiWX Upper X bound of the interval to search, in
711 * worldspace.
712 * @param {number} loY Lower Y bound of the interval to search, in
713 * offset space.
714 * @param {number} hiY Upper Y bound of the interval to search, in
715 * offset space.
716 * @param {TimelineSelection} selection Selection to which to add hits.
717 */
718 addIntersectingItemsInRangeToSelection: function(
719 loWX, hiWX, loY, hiY, selection) {
720 var clientRect = this.getBoundingClientRect();
721 var a = Math.max(loY, clientRect.top);
722 var b = Math.min(hiY, clientRect.bottom);
723 if (a > b)
724 return;
725
726 var that = this;
727 function onPickHit(slice) {
728 var hit = selection.addSlice(that, slice);
729 that.decorateHit(hit);
730 }
731 tracing.iterateOverIntersectingIntervals(this.slices_,
732 function(x) { return x.start; },
733 function(x) { return x.duration; },
734 loWX, hiWX,
735 onPickHit);
736 },
737
738 /**
739 * Find the index for the given slice.
740 * @return {index} Index of the given slice, or undefined.
741 * @private
742 */
743 indexOfSlice_: function(slice) {
744 var index = tracing.findLowIndexInSortedArray(this.slices_,
745 function(x) { return x.start; },
746 slice.start);
747 while (index < this.slices_.length &&
748 slice.start == this.slices_[index].start &&
749 slice.colorId != this.slices_[index].colorId) {
750 index++;
751 }
752 return index < this.slices_.length ? index : undefined;
753 },
754
755 /**
756 * Add the item to the left or right of the provided hit, if any, to the
757 * selection.
758 * @param {slice} The current slice.
759 * @param {Number} offset Number of slices away from the hit to look.
760 * @param {TimelineSelection} selection The selection to add a hit to,
761 * if found.
762 * @return {boolean} Whether a hit was found.
763 * @private
764 */
765 addItemNearToProvidedHitToSelection: function(hit, offset, selection) {
766 if (!hit.slice)
767 return false;
768
769 var index = this.indexOfSlice_(hit.slice);
770 if (index === undefined)
771 return false;
772
773 var newIndex = index + offset;
774 if (newIndex < 0 || newIndex >= this.slices_.length)
775 return false;
776
777 var hit = selection.addSlice(this, this.slices_[newIndex]);
778 this.decorateHit(hit);
779 return true;
780 },
781
782 addAllObjectsMatchingFilterToSelection: function(filter, selection) {
783 for (var i = 0; i < this.slices_.length; ++i) {
784 if (filter.matchSlice(this.slices_[i])) {
785 var hit = selection.addSlice(this, this.slices_[i]);
786 this.decorateHit(hit);
787 }
788 }
789 }
790 };
791
792 /**
793 * A track that displays the viewport size and scale.
794 * @constructor
795 * @extends {CanvasBasedTrack}
796 */
797
798 var TimelineViewportTrack = cr.ui.define(CanvasBasedTrack);
799
800 var logOf10 = Math.log(10);
801 function log10(x) {
802 return Math.log(x) / logOf10;
803 }
804
805 TimelineViewportTrack.prototype = {
806
807 __proto__: CanvasBasedTrack.prototype,
808
809 decorate: function() {
810 this.classList.add('timeline-viewport-track');
811 this.strings_secs_ = [];
812 this.strings_msecs_ = [];
813 },
814
815 redraw: function() {
816 var ctx = this.ctx_;
817 var canvasW = this.canvas_.width;
818 var canvasH = this.canvas_.height;
819
820 ctx.clearRect(0, 0, canvasW, canvasH);
821
822 // Culling parametrs.
823 var vp = this.viewport_;
824 var pixWidth = vp.xViewVectorToWorld(1);
825 var viewLWorld = vp.xViewToWorld(0);
826 var viewRWorld = vp.xViewToWorld(canvasW);
827
828 var idealMajorMarkDistancePix = 150;
829 var idealMajorMarkDistanceWorld =
830 vp.xViewVectorToWorld(idealMajorMarkDistancePix);
831
832 // The conservative guess is the nearest enclosing 0.1, 1, 10, 100, etc
833 var conservativeGuess =
834 Math.pow(10, Math.ceil(log10(idealMajorMarkDistanceWorld)));
835
836 // Once we have a conservative guess, consider things that evenly add up
837 // to the conservative guess, e.g. 0.5, 0.2, 0.1 Pick the one that still
838 // exceeds the ideal mark distance.
839 var divisors = [10, 5, 2, 1];
840 for (var i = 0; i < divisors.length; ++i) {
841 var tightenedGuess = conservativeGuess / divisors[i];
842 if (vp.xWorldVectorToView(tightenedGuess) < idealMajorMarkDistancePix)
843 continue;
844 majorMarkDistanceWorld = conservativeGuess / divisors[i - 1];
845 break;
846 }
847 var tickLabels = undefined;
848 if (majorMarkDistanceWorld < 100) {
849 unit = 'ms';
850 unitDivisor = 1;
851 tickLabels = this.strings_msecs_;
852 } else {
853 unit = 's';
854 unitDivisor = 1000;
855 tickLabels = this.strings_secs_;
856 }
857
858 var numTicksPerMajor = 5;
859 var minorMarkDistanceWorld = majorMarkDistanceWorld / numTicksPerMajor;
860 var minorMarkDistancePx = vp.xWorldVectorToView(minorMarkDistanceWorld);
861
862 var firstMajorMark =
863 Math.floor(viewLWorld / majorMarkDistanceWorld) *
864 majorMarkDistanceWorld;
865
866 var minorTickH = Math.floor(canvasH * 0.25);
867
868 ctx.fillStyle = 'rgb(0, 0, 0)';
869 ctx.strokeStyle = 'rgb(0, 0, 0)';
870 ctx.textAlign = 'left';
871 ctx.textBaseline = 'top';
872 ctx.font = '9px sans-serif';
873
874 // Each iteration of this loop draws one major mark
875 // and numTicksPerMajor minor ticks.
876 //
877 // Rendering can't be done in world space because canvas transforms
878 // affect line width. So, do the conversions manually.
879 for (var curX = firstMajorMark;
880 curX < viewRWorld;
881 curX += majorMarkDistanceWorld) {
882
883 var curXView = Math.floor(vp.xWorldToView(curX));
884
885 var unitValue = curX / unitDivisor;
886 var roundedUnitValue = Math.floor(unitValue * 100000) / 100000;
887 if (!tickLabels[roundedUnitValue])
888 tickLabels[roundedUnitValue] = roundedUnitValue + ' ' + unit;
889 ctx.fillText(tickLabels[roundedUnitValue], curXView + 2, 0);
890 ctx.beginPath();
891
892 // Major mark
893 ctx.moveTo(curXView, 0);
894 ctx.lineTo(curXView, canvasW);
895
896 // Minor marks
897 for (var i = 1; i < numTicksPerMajor; ++i) {
898 var xView = Math.floor(curXView + minorMarkDistancePx * i);
899 ctx.moveTo(xView, canvasH - minorTickH);
900 ctx.lineTo(xView, canvasH);
901 }
902
903 ctx.stroke();
904 }
905 },
906
907 /**
908 * Adds items intersecting a point to a selection.
909 * @param {number} wX X location to search at, in worldspace.
910 * @param {number} wY Y location to search at, in offset space.
911 * offset space.
912 * @param {TimelineSelection} selection Selection to which to add hits.
913 * @return {boolean} true if a slice was found, otherwise false.
914 */
915 addIntersectingItemsToSelection: function(wX, wY, selection) {
916 // Does nothing. There's nothing interesting to pick on the viewport
917 // track.
918 },
919
920 /**
921 * Adds items intersecting the given range to a selection.
922 * @param {number} loWX Lower X bound of the interval to search, in
923 * worldspace.
924 * @param {number} hiWX Upper X bound of the interval to search, in
925 * worldspace.
926 * @param {number} loY Lower Y bound of the interval to search, in
927 * offset space.
928 * @param {number} hiY Upper Y bound of the interval to search, in
929 * offset space.
930 * @param {TimelineSelection} selection Selection to which to add hits.
931 */
932 addIntersectingItemsInRangeToSelection: function(
933 loWX, hiWX, loY, hiY, selection) {
934 // Does nothing. There's nothing interesting to pick on the viewport
935 // track.
936 },
937
938 addAllObjectsMatchingFilterToSelection: function(filter, selection) {
939 }
940
941 };
942
943 /**
944 * A track that displays a TimelineCounter object.
945 * @constructor
946 * @extends {CanvasBasedTrack}
947 */
948
949 var TimelineCounterTrack = cr.ui.define(CanvasBasedTrack);
950
951 TimelineCounterTrack.prototype = {
952
953 __proto__: CanvasBasedTrack.prototype,
954
955 decorate: function() {
956 this.classList.add('timeline-counter-track');
957 addControlButtonElements(this, false);
958 this.selectedSamples_ = {};
959 },
960
961 /**
962 * Called by all the addToSelection functions on the created selection
963 * hit objects. Override this function on parent classes to add
964 * context-specific information to the hit.
965 */
966 decorateHit: function(hit) {
967 },
968
969 get counter() {
970 return this.counter_;
971 },
972
973 set counter(counter) {
974 this.counter_ = counter;
975 this.invalidate();
976 },
977
978 /**
979 * @return {Object} A sparce, mutable map from sample index to bool. Samples
980 * indices the map that are true are drawn as selected. Callers that mutate
981 * the map must manually call invalidate on the track to trigger a redraw.
982 */
983 get selectedSamples() {
984 return this.selectedSamples_;
985 },
986
987 redraw: function() {
988 var ctr = this.counter_;
989 var ctx = this.ctx_;
990 var canvasW = this.canvas_.width;
991 var canvasH = this.canvas_.height;
992
993 ctx.clearRect(0, 0, canvasW, canvasH);
994
995 // Culling parametrs.
996 var vp = this.viewport_;
997 var pixWidth = vp.xViewVectorToWorld(1);
998 var viewLWorld = vp.xViewToWorld(0);
999 var viewRWorld = vp.xViewToWorld(canvasW);
1000
1001 // Drop sampels that are less than skipDistancePix apart.
1002 var skipDistancePix = 1;
1003 var skipDistanceWorld = vp.xViewVectorToWorld(skipDistancePix);
1004
1005 // Begin rendering in world space.
1006 ctx.save();
1007 vp.applyTransformToCanavs(ctx);
1008
1009 // Figure out where drawing should begin.
1010 var numSeries = ctr.numSeries;
1011 var numSamples = ctr.numSamples;
1012 var startIndex = tracing.findLowIndexInSortedArray(ctr.timestamps,
1013 function() {
1014 },
1015 viewLWorld);
1016
1017 // Draw indices one by one until we fall off the viewRWorld.
1018 var yScale = canvasH / ctr.maxTotal;
1019 for (var seriesIndex = ctr.numSeries - 1;
1020 seriesIndex >= 0; seriesIndex--) {
1021 var colorId = ctr.seriesColors[seriesIndex];
1022 ctx.fillStyle = pallette[colorId];
1023 ctx.beginPath();
1024
1025 // Set iLast and xLast such that the first sample we draw is the
1026 // startIndex sample.
1027 var iLast = startIndex - 1;
1028 var xLast = iLast >= 0 ? ctr.timestamps[iLast] - skipDistanceWorld : -1;
1029 var yLastView = canvasH;
1030
1031 // Iterate over samples from iLast onward until we either fall off the
1032 // viewRWorld or we run out of samples. To avoid drawing too much, after
1033 // drawing a sample at xLast, skip subsequent samples that are less than
1034 // skipDistanceWorld from xLast.
1035 var hasMoved = false;
1036 while (true) {
1037 var i = iLast + 1;
1038 if (i >= numSamples) {
1039 ctx.lineTo(xLast, yLastView);
1040 ctx.lineTo(xLast + 8 * pixWidth, yLastView);
1041 ctx.lineTo(xLast + 8 * pixWidth, canvasH);
1042 break;
1043 }
1044
1045 var x = ctr.timestamps[i];
1046
1047 var y = ctr.totals[i * numSeries + seriesIndex];
1048 var yView = canvasH - (yScale * y);
1049
1050 if (x > viewRWorld) {
1051 ctx.lineTo(x, yLastView);
1052 ctx.lineTo(x, canvasH);
1053 break;
1054 }
1055
1056 if (x - xLast < skipDistanceWorld) {
1057 iLast = i;
1058 continue;
1059 }
1060
1061 if (!hasMoved) {
1062 ctx.moveTo(viewLWorld, canvasH);
1063 hasMoved = true;
1064 }
1065 ctx.lineTo(x, yLastView);
1066 ctx.lineTo(x, yView);
1067 iLast = i;
1068 xLast = x;
1069 yLastView = yView;
1070 }
1071 ctx.closePath();
1072 ctx.fill();
1073 }
1074 ctx.fillStyle = 'rgba(255, 0, 0, 1)';
1075 for (var i in this.selectedSamples_) {
1076 if (!this.selectedSamples_[i])
1077 continue;
1078
1079 var x = ctr.timestamps[i];
1080 for (var seriesIndex = ctr.numSeries - 1;
1081 seriesIndex >= 0; seriesIndex--) {
1082 var y = ctr.totals[i * numSeries + seriesIndex];
1083 var yView = canvasH - (yScale * y);
1084 ctx.fillRect(x - pixWidth, yView - 1, 3 * pixWidth, 3);
1085 }
1086 }
1087 ctx.restore();
1088 },
1089
1090 /**
1091 * Adds items intersecting a point to a selection.
1092 * @param {number} wX X location to search at, in worldspace.
1093 * @param {number} wY Y location to search at, in offset space.
1094 * offset space.
1095 * @param {TimelineSelection} selection Selection to which to add hits.
1096 * @return {boolean} true if a slice was found, otherwise false.
1097 */
1098 addIntersectingItemsToSelection: function(wX, wY, selection) {
1099 var clientRect = this.getBoundingClientRect();
1100 if (wY < clientRect.top || wY >= clientRect.bottom)
1101 return false;
1102 var ctr = this.counter_;
1103 if (wX < this.counter_.timestamps[0])
1104 return false;
1105 var i = tracing.findLowIndexInSortedArray(ctr.timestamps,
1106 function(x) { return x; },
1107 wX);
1108 if (i < 0 || i >= ctr.timestamps.length)
1109 return false;
1110
1111 // Sample i is going to either be exactly at wX or slightly above it,
1112 // E.g. asking for 7.5 in [7,8] gives i=1. So bump i back by 1 if needed.
1113 if (i > 0 && wX > this.counter_.timestamps[i - 1])
1114 i--;
1115
1116 // Some preliminaries.
1117 var canvasH = this.getBoundingClientRect().height;
1118 var yScale = canvasH / ctr.maxTotal;
1119
1120 /*
1121 // Figure out which sample we hit
1122 var seriesIndexHit;
1123 for (var seriesIndex = 0; seriesIndex < ctr.numSeries; seriesIndex++) {
1124 var y = ctr.totals[i * ctr.numSeries + seriesIndex];
1125 var yView = canvasH - (yScale * y) + clientRect.top;
1126 if (wY >= yView) {
1127 seriesIndexHit = seriesIndex;
1128 break;
1129 }
1130 }
1131 if (seriesIndexHit === undefined)
1132 return false;
1133 */
1134 var hit = selection.addCounterSample(this, this.counter, i);
1135 this.decorateHit(hit);
1136 return true;
1137 },
1138
1139 /**
1140 * Adds items intersecting the given range to a selection.
1141 * @param {number} loWX Lower X bound of the interval to search, in
1142 * worldspace.
1143 * @param {number} hiWX Upper X bound of the interval to search, in
1144 * worldspace.
1145 * @param {number} loY Lower Y bound of the interval to search, in
1146 * offset space.
1147 * @param {number} hiY Upper Y bound of the interval to search, in
1148 * offset space.
1149 * @param {TimelineSelection} selection Selection to which to add hits.
1150 */
1151 addIntersectingItemsInRangeToSelection: function(
1152 loWX, hiWX, loY, hiY, selection) {
1153
1154 var clientRect = this.getBoundingClientRect();
1155 var a = Math.max(loY, clientRect.top);
1156 var b = Math.min(hiY, clientRect.bottom);
1157 if (a > b)
1158 return;
1159
1160 var ctr = this.counter_;
1161
1162 var iLo = tracing.findLowIndexInSortedArray(ctr.timestamps,
1163 function(x) { return x; },
1164 loWX);
1165 var iHi = tracing.findLowIndexInSortedArray(ctr.timestamps,
1166 function(x) { return x; },
1167 hiWX);
1168
1169 // Sample i is going to either be exactly at wX or slightly above it,
1170 // E.g. asking for 7.5 in [7,8] gives i=1. So bump i back by 1 if needed.
1171 if (iLo > 0 && loWX > ctr.timestamps[iLo - 1])
1172 iLo--;
1173 if (iHi > 0 && hiWX > ctr.timestamps[iHi - 1])
1174 iHi--;
1175
1176 // Iterate over every sample intersecting..
1177 for (var i = iLo; i <= iHi; i++) {
1178 if (i >= ctr.timestamps.length)
1179 continue;
1180
1181 // TODO(nduca): Pick the seriesIndexHit based on the loY - hiY values.
1182 var hit = selection.addCounterSample(this, this.counter, i);
1183 this.decorateHit(hit);
1184 }
1185 },
1186
1187 addAllObjectsMatchingFilterToSelection: function(filter, selection) {
1188 }
1189
1190 };
1191
1192 return {
1193 TimelineCounterTrack: TimelineCounterTrack,
1194 TimelineSliceTrack: TimelineSliceTrack,
1195 TimelineThreadTrack: TimelineThreadTrack,
1196 TimelineViewportTrack: TimelineViewportTrack,
1197 TimelineCpuTrack: TimelineCpuTrack
1198 };
1199 });
OLDNEW
« no previous file with comments | « chrome/browser/resources/tracing/timeline_test.html ('k') | chrome/browser/resources/tracing/timeline_track_test.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698