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

Side by Side Diff: chrome/browser/resources/tracing/timeline_model.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 'use strict';
6
7 /**
8 * @fileoverview TimelineModel is a parsed representation of the
9 * TraceEvents obtained from base/trace_event in which the begin-end
10 * tokens are converted into a hierarchy of processes, threads,
11 * subrows, and slices.
12 *
13 * The building block of the model is a slice. A slice is roughly
14 * equivalent to function call executing on a specific thread. As a
15 * result, slices may have one or more subslices.
16 *
17 * A thread contains one or more subrows of slices. Row 0 corresponds to
18 * the "root" slices, e.g. the topmost slices. Row 1 contains slices that
19 * are nested 1 deep in the stack, and so on. We use these subrows to draw
20 * nesting tasks.
21 *
22 */
23 cr.define('tracing', function() {
24 /**
25 * A TimelineSlice represents an interval of time plus parameters associated
26 * with that interval.
27 *
28 * All time units are stored in milliseconds.
29 * @constructor
30 */
31 function TimelineSlice(title, colorId, start, args, opt_duration) {
32 this.title = title;
33 this.start = start;
34 this.colorId = colorId;
35 this.args = args;
36 this.didNotFinish = false;
37 if (opt_duration !== undefined)
38 this.duration = opt_duration;
39 }
40
41 TimelineSlice.prototype = {
42 selected: false,
43
44 duration: undefined,
45
46 get end() {
47 return this.start + this.duration;
48 }
49 };
50
51 /**
52 * A TimelineThreadSlice represents an interval of time on a thread resource
53 * with associated nestinged slice information.
54 *
55 * ThreadSlices are typically associated with a specific trace event pair on a
56 * specific thread.
57 * For example,
58 * TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms
59 * TRACE_EVENT_END0() at time=0.3ms
60 * This results in a single timeline slice from 0.1 with duration 0.2 on a
61 * specific thread.
62 *
63 * @constructor
64 */
65 function TimelineThreadSlice(title, colorId, start, args, opt_duration) {
66 TimelineSlice.call(this, title, colorId, start, args, opt_duration);
67 this.subSlices = [];
68 }
69
70 TimelineThreadSlice.prototype = {
71 __proto__: TimelineSlice.prototype
72 };
73
74 /**
75 * A TimelineAsyncSlice represents an interval of time during which an
76 * asynchronous operation is in progress. An AsyncSlice consumes no CPU time
77 * itself and so is only associated with Threads at its start and end point.
78 *
79 * @constructor
80 */
81 function TimelineAsyncSlice(title, colorId, start, args) {
82 TimelineSlice.call(this, title, colorId, start, args);
83 };
84
85 TimelineAsyncSlice.prototype = {
86 __proto__: TimelineSlice.prototype,
87
88 id: undefined,
89
90 startThread: undefined,
91
92 endThread: undefined,
93
94 subSlices: undefined
95 };
96
97 /**
98 * A TimelineThread stores all the trace events collected for a particular
99 * thread. We organize the synchronous slices on a thread by "subrows," where
100 * subrow 0 has all the root slices, subrow 1 those nested 1 deep, and so on.
101 * The asynchronous slices are stored in an TimelineAsyncSliceGroup object.
102 *
103 * The slices stored on a TimelineThread should be instances of
104 * TimelineThreadSlice.
105 *
106 * @constructor
107 */
108 function TimelineThread(parent, tid) {
109 if (!parent)
110 throw 'Parent must be provided.';
111 this.parent = parent;
112 this.tid = tid;
113 this.subRows = [[]];
114 this.asyncSlices = new TimelineAsyncSliceGroup(this.ptid);
115 }
116
117 var ptidMap = {};
118
119 /**
120 * @return {String} A string that can be used as a unique key for a specific
121 * thread within a process.
122 */
123 TimelineThread.getPTIDFromPidAndTid = function(pid, tid) {
124 if (!ptidMap[pid])
125 ptidMap[pid] = {};
126 if (!ptidMap[pid][tid])
127 ptidMap[pid][tid] = pid + ':' + tid;
128 return ptidMap[pid][tid];
129 }
130
131 TimelineThread.prototype = {
132 /**
133 * Name of the thread, if present.
134 */
135 name: undefined,
136
137 /**
138 * @return {string} A concatenation of the parent id and the thread's
139 * tid. Can be used to uniquely identify a thread.
140 */
141 get ptid() {
142 return TimelineThread.getPTIDFromPidAndTid(this.tid, this.parent.pid);
143 },
144
145 getSubrow: function(i) {
146 while (i >= this.subRows.length)
147 this.subRows.push([]);
148 return this.subRows[i];
149 },
150
151
152 shiftSubRow_: function(subRow, amount) {
153 for (var tS = 0; tS < subRow.length; tS++) {
154 var slice = subRow[tS];
155 slice.start = (slice.start + amount);
156 }
157 },
158
159 /**
160 * Shifts all the timestamps inside this thread forward by the amount
161 * specified.
162 */
163 shiftTimestampsForward: function(amount) {
164 if (this.cpuSlices)
165 this.shiftSubRow_(this.cpuSlices, amount);
166
167 for (var tSR = 0; tSR < this.subRows.length; tSR++) {
168 this.shiftSubRow_(this.subRows[tSR], amount);
169 }
170
171 this.asyncSlices.shiftTimestampsForward(amount);
172 },
173
174 /**
175 * Updates the minTimestamp and maxTimestamp fields based on the
176 * current objects associated with the thread.
177 */
178 updateBounds: function() {
179 var values = [];
180 var slices;
181 if (this.subRows[0].length != 0) {
182 slices = this.subRows[0];
183 values.push(slices[0].start);
184 values.push(slices[slices.length - 1].end);
185 }
186 if (this.asyncSlices.slices.length) {
187 this.asyncSlices.updateBounds();
188 values.push(this.asyncSlices.minTimestamp);
189 values.push(this.asyncSlices.maxTimestamp);
190 }
191 if (values.length) {
192 this.minTimestamp = Math.min.apply(Math, values);
193 this.maxTimestamp = Math.max.apply(Math, values);
194 } else {
195 this.minTimestamp = undefined;
196 this.maxTimestamp = undefined;
197 }
198 },
199
200 /**
201 * @return {String} A user-friendly name for this thread.
202 */
203 get userFriendlyName() {
204 var tname = this.name || this.tid;
205 return this.parent.pid + ': ' + tname;
206 },
207
208 /**
209 * @return {String} User friendly details about this thread.
210 */
211 get userFriendlyDetails() {
212 return 'pid: ' + this.parent.pid +
213 ', tid: ' + this.tid +
214 (this.name ? ', name: ' + this.name : '');
215 }
216 };
217
218 /**
219 * Comparison between threads that orders first by pid,
220 * then by names, then by tid.
221 */
222 TimelineThread.compare = function(x, y) {
223 if (x.parent.pid != y.parent.pid) {
224 return TimelineProcess.compare(x.parent, y.parent.pid);
225 }
226
227 if (x.name && y.name) {
228 var tmp = x.name.localeCompare(y.name);
229 if (tmp == 0)
230 return x.tid - y.tid;
231 return tmp;
232 } else if (x.name) {
233 return -1;
234 } else if (y.name) {
235 return 1;
236 } else {
237 return x.tid - y.tid;
238 }
239 };
240
241 /**
242 * Stores all the samples for a given counter.
243 * @constructor
244 */
245 function TimelineCounter(parent, id, name) {
246 this.parent = parent;
247 this.id = id;
248 this.name = name;
249 this.seriesNames = [];
250 this.seriesColors = [];
251 this.timestamps = [];
252 this.samples = [];
253 }
254
255 TimelineCounter.prototype = {
256 __proto__: Object.prototype,
257
258 get numSeries() {
259 return this.seriesNames.length;
260 },
261
262 get numSamples() {
263 return this.timestamps.length;
264 },
265
266 /**
267 * Shifts all the timestamps inside this counter forward by the amount
268 * specified.
269 */
270 shiftTimestampsForward: function(amount) {
271 for (var sI = 0; sI < this.timestamps.length; sI++)
272 this.timestamps[sI] = (this.timestamps[sI] + amount);
273 },
274
275 /**
276 * Updates the bounds for this counter based on the samples it contains.
277 */
278 updateBounds: function() {
279 if (this.seriesNames.length != this.seriesColors.length)
280 throw 'seriesNames.length must match seriesColors.length';
281 if (this.numSeries * this.numSamples != this.samples.length)
282 throw 'samples.length must be a multiple of numSamples.';
283
284 this.totals = [];
285 if (this.samples.length == 0) {
286 this.minTimestamp = undefined;
287 this.maxTimestamp = undefined;
288 this.maxTotal = 0;
289 return;
290 }
291 this.minTimestamp = this.timestamps[0];
292 this.maxTimestamp = this.timestamps[this.timestamps.length - 1];
293
294 var numSeries = this.numSeries;
295 var maxTotal = -Infinity;
296 for (var i = 0; i < this.timestamps.length; i++) {
297 var total = 0;
298 for (var j = 0; j < numSeries; j++) {
299 total += this.samples[i * numSeries + j];
300 this.totals.push(total);
301 }
302 if (total > maxTotal)
303 maxTotal = total;
304 }
305 this.maxTotal = maxTotal;
306 }
307
308 };
309
310 /**
311 * Comparison between counters that orders by pid, then name.
312 */
313 TimelineCounter.compare = function(x, y) {
314 if (x.parent.pid != y.parent.pid) {
315 return TimelineProcess.compare(x.parent, y.parent.pid);
316 }
317 var tmp = x.name.localeCompare(y.name);
318 if (tmp == 0)
319 return x.tid - y.tid;
320 return tmp;
321 };
322
323 /**
324 * The TimelineProcess represents a single process in the
325 * trace. Right now, we keep this around purely for bookkeeping
326 * reasons.
327 * @constructor
328 */
329 function TimelineProcess(pid) {
330 this.pid = pid;
331 this.threads = {};
332 this.counters = {};
333 };
334
335 TimelineProcess.prototype = {
336 get numThreads() {
337 var n = 0;
338 for (var p in this.threads) {
339 n++;
340 }
341 return n;
342 },
343
344 /**
345 * Shifts all the timestamps inside this process forward by the amount
346 * specified.
347 */
348 shiftTimestampsForward: function(amount) {
349 for (var tid in this.threads)
350 this.threads[tid].shiftTimestampsForward(amount);
351 for (var id in this.counters)
352 this.counters[id].shiftTimestampsForward(amount);
353 },
354
355 /**
356 * @return {TimlineThread} The thread identified by tid on this process,
357 * creating it if it doesn't exist.
358 */
359 getOrCreateThread: function(tid) {
360 if (!this.threads[tid])
361 this.threads[tid] = new TimelineThread(this, tid);
362 return this.threads[tid];
363 },
364
365 /**
366 * @return {TimlineCounter} The counter on this process named 'name',
367 * creating it if it doesn't exist.
368 */
369 getOrCreateCounter: function(cat, name) {
370 var id = cat + '.' + name;
371 if (!this.counters[id])
372 this.counters[id] = new TimelineCounter(this, id, name);
373 return this.counters[id];
374 }
375 };
376
377 /**
378 * Comparison between processes that orders by pid.
379 */
380 TimelineProcess.compare = function(x, y) {
381 return x.pid - y.pid;
382 };
383
384 /**
385 * The TimelineCpu represents a Cpu from the kernel's point of view.
386 * @constructor
387 */
388 function TimelineCpu(number) {
389 this.cpuNumber = number;
390 this.slices = [];
391 this.counters = {};
392 };
393
394 TimelineCpu.prototype = {
395 /**
396 * @return {TimlineCounter} The counter on this process named 'name',
397 * creating it if it doesn't exist.
398 */
399 getOrCreateCounter: function(cat, name) {
400 var id;
401 if (cat.length)
402 id = cat + '.' + name;
403 else
404 id = name;
405 if (!this.counters[id])
406 this.counters[id] = new TimelineCounter(this, id, name);
407 return this.counters[id];
408 },
409
410 /**
411 * Shifts all the timestamps inside this CPU forward by the amount
412 * specified.
413 */
414 shiftTimestampsForward: function(amount) {
415 for (var sI = 0; sI < this.slices.length; sI++)
416 this.slices[sI].start = (this.slices[sI].start + amount);
417 for (var id in this.counters)
418 this.counters[id].shiftTimestampsForward(amount);
419 },
420
421 /**
422 * Updates the minTimestamp and maxTimestamp fields based on the
423 * current slices attached to the cpu.
424 */
425 updateBounds: function() {
426 var values = [];
427 if (this.slices.length) {
428 this.minTimestamp = this.slices[0].start;
429 this.maxTimestamp = this.slices[this.slices.length - 1].end;
430 } else {
431 this.minTimestamp = undefined;
432 this.maxTimestamp = undefined;
433 }
434 }
435 };
436
437 /**
438 * Comparison between processes that orders by cpuNumber.
439 */
440 TimelineCpu.compare = function(x, y) {
441 return x.cpuNumber - y.cpuNumber;
442 };
443
444 /**
445 * A group of AsyncSlices.
446 * @constructor
447 */
448 function TimelineAsyncSliceGroup(name) {
449 this.name = name;
450 this.slices = [];
451 }
452
453 TimelineAsyncSliceGroup.prototype = {
454 __proto__: Object.prototype,
455
456 /**
457 * Helper function that pushes the provided slice onto the slices array.
458 */
459 push: function(slice) {
460 this.slices.push(slice);
461 },
462
463 /**
464 * @return {Number} The number of slices in this group.
465 */
466 get length() {
467 return this.slices.length;
468 },
469
470 /**
471 * Built automatically by rebuildSubRows().
472 */
473 subRows_: undefined,
474
475 /**
476 * Updates the bounds for this group based on the slices it contains.
477 */
478 sortSlices_: function() {
479 this.slices.sort(function(x, y) {
480 return x.start - y.start;
481 });
482 },
483
484 /**
485 * Shifts all the timestamps inside this group forward by the amount
486 * specified.
487 */
488 shiftTimestampsForward: function(amount) {
489 for (var sI = 0; sI < this.slices.length; sI++) {
490 var slice = this.slices[sI];
491 slice.start = (slice.start + amount);
492 for (var sJ = 0; sJ < slice.subSlices.length; sJ++)
493 slice.subSlices[sJ].start += amount;
494 }
495 },
496
497 /**
498 * Updates the bounds for this group based on the slices it contains.
499 */
500 updateBounds: function() {
501 this.sortSlices_();
502 if (this.slices.length) {
503 this.minTimestamp = this.slices[0].start;
504 this.maxTimestamp = this.slices[this.slices.length - 1].end;
505 } else {
506 this.minTimestamp = undefined;
507 this.maxTimestamp = undefined;
508 }
509 this.subRows_ = undefined;
510 },
511
512 get subRows() {
513 if (!this.subRows_)
514 this.rebuildSubRows_();
515 return this.subRows_;
516 },
517
518 /**
519 * Breaks up the list of slices into N rows, each of which is a list of
520 * slices that are non overlapping.
521 *
522 * It uses a very simple approach: walk through the slices in sorted order
523 * by start time. For each slice, try to fit it in an existing subRow. If it
524 * doesn't fit in any subrow, make another subRow.
525 */
526 rebuildSubRows_: function() {
527 this.sortSlices_();
528 var subRows = [];
529 for (var i = 0; i < this.slices.length; i++) {
530 var slice = this.slices[i];
531
532 var found = false;
533 for (var j = 0; j < subRows.length; j++) {
534 var subRow = subRows[j];
535 var lastSliceInSubRow = subRow[subRow.length - 1];
536 if (slice.start >= lastSliceInSubRow.end) {
537 found = true;
538 // Instead of plotting one big slice for the entire
539 // TimelineAsyncEvent, we plot each of the subSlices.
540 if (slice.subSlices === undefined || slice.subSlices.length < 1)
541 throw 'TimelineAsyncEvent missing subSlices: ' + slice.name;
542 for (var k = 0; k < slice.subSlices.length; k++)
543 subRow.push(slice.subSlices[k]);
544 break;
545 }
546 }
547 if (!found) {
548 var subRow = [];
549 if (slice.subSlices !== undefined) {
550 for (var k = 0; k < slice.subSlices.length; k++)
551 subRow.push(slice.subSlices[k]);
552 subRows.push(subRow);
553 }
554 }
555 }
556 this.subRows_ = subRows;
557 },
558
559 /**
560 * Breaks up this group into slices based on start thread.
561 *
562 * @return {Array} An array of TimelineAsyncSliceGroups where each group has
563 * slices that started on the same thread.
564 **/
565 computeSubGroups: function() {
566 var subGroupsByPTID = {};
567 for (var i = 0; i < this.slices.length; ++i) {
568 var slice = this.slices[i];
569 var slicePTID = slice.startThread.ptid;
570 if (!subGroupsByPTID[slicePTID])
571 subGroupsByPTID[slicePTID] = new TimelineAsyncSliceGroup(this.name);
572 subGroupsByPTID[slicePTID].slices.push(slice);
573 }
574 var groups = [];
575 for (var ptid in subGroupsByPTID) {
576 var group = subGroupsByPTID[ptid];
577 group.updateBounds();
578 groups.push(group);
579 }
580 return groups;
581 }
582 };
583
584 /**
585 * Comparison between counters that orders by pid, then name.
586 */
587 TimelineCounter.compare = function(x, y) {
588 if (x.parent.pid != y.parent.pid) {
589 return TimelineProcess.compare(x.parent, y.parent.pid);
590 }
591 var tmp = x.name.localeCompare(y.name);
592 if (tmp == 0)
593 return x.tid - y.tid;
594 return tmp;
595 };
596
597 // The color pallette is split in half, with the upper
598 // half of the pallette being the "highlighted" verison
599 // of the base color. So, color 7's highlighted form is
600 // 7 + (pallette.length / 2).
601 //
602 // These bright versions of colors are automatically generated
603 // from the base colors.
604 //
605 // Within the color pallette, there are "regular" colors,
606 // which can be used for random color selection, and
607 // reserved colors, which are used when specific colors
608 // need to be used, e.g. where red is desired.
609 var palletteBase = [
610 {r: 138, g: 113, b: 152},
611 {r: 175, g: 112, b: 133},
612 {r: 127, g: 135, b: 225},
613 {r: 93, g: 81, b: 137},
614 {r: 116, g: 143, b: 119},
615 {r: 178, g: 214, b: 122},
616 {r: 87, g: 109, b: 147},
617 {r: 119, g: 155, b: 95},
618 {r: 114, g: 180, b: 160},
619 {r: 132, g: 85, b: 103},
620 {r: 157, g: 210, b: 150},
621 {r: 148, g: 94, b: 86},
622 {r: 164, g: 108, b: 138},
623 {r: 139, g: 191, b: 150},
624 {r: 110, g: 99, b: 145},
625 {r: 80, g: 129, b: 109},
626 {r: 125, g: 140, b: 149},
627 {r: 93, g: 124, b: 132},
628 {r: 140, g: 85, b: 140},
629 {r: 104, g: 163, b: 162},
630 {r: 132, g: 141, b: 178},
631 {r: 131, g: 105, b: 147},
632 {r: 135, g: 183, b: 98},
633 {r: 152, g: 134, b: 177},
634 {r: 141, g: 188, b: 141},
635 {r: 133, g: 160, b: 210},
636 {r: 126, g: 186, b: 148},
637 {r: 112, g: 198, b: 205},
638 {r: 180, g: 122, b: 195},
639 {r: 203, g: 144, b: 152},
640 // Reserved Entires
641 {r: 182, g: 125, b: 143},
642 {r: 126, g: 200, b: 148},
643 {r: 133, g: 160, b: 210},
644 {r: 240, g: 240, b: 240}];
645
646 // Make sure this number tracks the number of reserved entries in the
647 // pallette.
648 var numReservedColorIds = 4;
649
650 function brighten(c) {
651 var k;
652 if (c.r >= 240 && c.g >= 240 && c.b >= 240)
653 k = -0.20;
654 else
655 k = 0.45;
656
657 return {r: Math.min(255, c.r + Math.floor(c.r * k)),
658 g: Math.min(255, c.g + Math.floor(c.g * k)),
659 b: Math.min(255, c.b + Math.floor(c.b * k))};
660 }
661 function colorToString(c) {
662 return 'rgb(' + c.r + ',' + c.g + ',' + c.b + ')';
663 }
664
665 /**
666 * The number of color IDs that getStringColorId can choose from.
667 */
668 var numRegularColorIds = palletteBase.length - numReservedColorIds;
669 var highlightIdBoost = palletteBase.length;
670
671 var pallette = palletteBase.concat(palletteBase.map(brighten)).
672 map(colorToString);
673 /**
674 * Computes a simplistic hashcode of the provide name. Used to chose colors
675 * for slices.
676 * @param {string} name The string to hash.
677 */
678 function getStringHash(name) {
679 var hash = 0;
680 for (var i = 0; i < name.length; ++i)
681 hash = (hash + 37 * hash + 11 * name.charCodeAt(i)) % 0xFFFFFFFF;
682 return hash;
683 }
684
685 /**
686 * Gets the color pallette.
687 */
688 function getPallette() {
689 return pallette;
690 }
691
692 /**
693 * @return {Number} The value to add to a color ID to get its highlighted
694 * colro ID. E.g. 7 + getPalletteHighlightIdBoost() yields a brightened from
695 * of 7's base color.
696 */
697 function getPalletteHighlightIdBoost() {
698 return highlightIdBoost;
699 }
700
701 /**
702 * @param {String} name The color name.
703 * @return {Number} The color ID for the given color name.
704 */
705 function getColorIdByName(name) {
706 if (name == 'iowait')
707 return numRegularColorIds;
708 if (name == 'running')
709 return numRegularColorIds + 1;
710 if (name == 'runnable')
711 return numRegularColorIds + 2;
712 if (name == 'sleeping')
713 return numRegularColorIds + 3;
714 throw 'Unrecognized color ' + name;
715 }
716
717 // Previously computed string color IDs. They are based on a stable hash, so
718 // it is safe to save them throughout the program time.
719 var stringColorIdCache = {};
720
721 /**
722 * @return {Number} A color ID that is stably associated to the provided via
723 * the getStringHash method. The color ID will be chosen from the regular
724 * ID space only, e.g. no reserved ID will be used.
725 */
726 function getStringColorId(string) {
727 if (stringColorIdCache[string] === undefined) {
728 var hash = getStringHash(string);
729 stringColorIdCache[string] = hash % numRegularColorIds;
730 }
731 return stringColorIdCache[string];
732 }
733
734 /**
735 * Builds a model from an array of TraceEvent objects.
736 * @param {Object=} opt_data The event data to import into the new model.
737 * See TimelineModel.importEvents for details and more advanced ways to
738 * import data.
739 * @param {bool=} opt_zeroAndBoost Whether to align to zero and boost the
740 * by 15%. Defaults to true.
741 * @constructor
742 */
743 function TimelineModel(opt_eventData, opt_zeroAndBoost) {
744 this.cpus = {};
745 this.processes = {};
746 this.importErrors = [];
747 this.asyncSliceGroups = {};
748
749 if (opt_eventData)
750 this.importEvents(opt_eventData, opt_zeroAndBoost);
751 }
752
753 var importerConstructors = [];
754
755 /**
756 * Registers an importer. All registered importers are considered
757 * when processing an import request.
758 *
759 * @param {Function} importerConstructor The importer's constructor function.
760 */
761 TimelineModel.registerImporter = function(importerConstructor) {
762 importerConstructors.push(importerConstructor);
763 };
764
765 function TimelineModelEmptyImporter(events) {
766 };
767
768 TimelineModelEmptyImporter.canImport = function(eventData) {
769 if (eventData instanceof Array && eventData.length == 0)
770 return true;
771 if (typeof(eventData) === 'string' || eventData instanceof String) {
772 return eventData.length == 0;
773 }
774 return false;
775 };
776
777 TimelineModelEmptyImporter.prototype = {
778 __proto__: Object.prototype,
779
780 importEvents: function() {
781 },
782 finalizeImport: function() {
783 }
784 };
785
786 TimelineModel.registerImporter(TimelineModelEmptyImporter);
787
788 TimelineModel.prototype = {
789 __proto__: cr.EventTarget.prototype,
790
791 get numProcesses() {
792 var n = 0;
793 for (var p in this.processes)
794 n++;
795 return n;
796 },
797
798 /**
799 * @return {TimelineProcess} Gets a specific TimelineCpu or creates one if
800 * it does not exist.
801 */
802 getOrCreateCpu: function(cpuNumber) {
803 if (!this.cpus[cpuNumber])
804 this.cpus[cpuNumber] = new TimelineCpu(cpuNumber);
805 return this.cpus[cpuNumber];
806 },
807
808 /**
809 * @return {TimelineProcess} Gets a TimlineProcess for a specified pid or
810 * creates one if it does not exist.
811 */
812 getOrCreateProcess: function(pid) {
813 if (!this.processes[pid])
814 this.processes[pid] = new TimelineProcess(pid);
815 return this.processes[pid];
816 },
817
818 /**
819 * The import takes an array of json-ified TraceEvents and adds them into
820 * the TimelineModel as processes, threads, and slices.
821 */
822
823 /**
824 * Removes threads from the model that are fully empty.
825 */
826 pruneEmptyThreads: function() {
827 for (var pid in this.processes) {
828 var process = this.processes[pid];
829 var prunedThreads = {};
830 for (var tid in process.threads) {
831 var thread = process.threads[tid];
832
833 // Begin-events without matching end events leave a thread in a state
834 // where the toplevel subrows are empty but child subrows have
835 // entries. The autocloser will fix this up later. But, for the
836 // purposes of pruning, such threads need to be treated as having
837 // content.
838 var hasNonEmptySubrow = false;
839 for (var s = 0; s < thread.subRows.length; s++)
840 hasNonEmptySubrow |= thread.subRows[s].length > 0;
841
842 if (hasNonEmptySubrow || thread.asyncSlices.length > 0)
843 prunedThreads[tid] = thread;
844 }
845 process.threads = prunedThreads;
846 }
847 },
848
849 updateBounds: function() {
850 var wmin = Infinity;
851 var wmax = -wmin;
852 var hasData = false;
853
854 var threads = this.getAllThreads();
855 for (var tI = 0; tI < threads.length; tI++) {
856 var thread = threads[tI];
857 thread.updateBounds();
858 if (thread.minTimestamp != undefined &&
859 thread.maxTimestamp != undefined) {
860 wmin = Math.min(wmin, thread.minTimestamp);
861 wmax = Math.max(wmax, thread.maxTimestamp);
862 hasData = true;
863 }
864 }
865 var counters = this.getAllCounters();
866 for (var tI = 0; tI < counters.length; tI++) {
867 var counter = counters[tI];
868 counter.updateBounds();
869 if (counter.minTimestamp != undefined &&
870 counter.maxTimestamp != undefined) {
871 hasData = true;
872 wmin = Math.min(wmin, counter.minTimestamp);
873 wmax = Math.max(wmax, counter.maxTimestamp);
874 }
875 }
876
877 for (var cpuNumber in this.cpus) {
878 var cpu = this.cpus[cpuNumber];
879 cpu.updateBounds();
880 if (cpu.minTimestamp != undefined &&
881 cpu.maxTimestamp != undefined) {
882 hasData = true;
883 wmin = Math.min(wmin, cpu.minTimestamp);
884 wmax = Math.max(wmax, cpu.maxTimestamp);
885 }
886 }
887
888 if (hasData) {
889 this.minTimestamp = wmin;
890 this.maxTimestamp = wmax;
891 } else {
892 this.maxTimestamp = undefined;
893 this.minTimestamp = undefined;
894 }
895 },
896
897 shiftWorldToZero: function() {
898 if (this.minTimestamp === undefined)
899 return;
900 var timeBase = this.minTimestamp;
901 for (var pid in this.processes)
902 this.processes[pid].shiftTimestampsForward(-timeBase);
903 for (var cpuNumber in this.cpus)
904 this.cpus[cpuNumber].shiftTimestampsForward(-timeBase);
905 this.updateBounds();
906 },
907
908 getAllThreads: function() {
909 var threads = [];
910 for (var pid in this.processes) {
911 var process = this.processes[pid];
912 for (var tid in process.threads) {
913 threads.push(process.threads[tid]);
914 }
915 }
916 return threads;
917 },
918
919 /**
920 * @return {Array} An array of all cpus in the model.
921 */
922 getAllCpus: function() {
923 var cpus = [];
924 for (var cpu in this.cpus)
925 cpus.push(this.cpus[cpu]);
926 return cpus;
927 },
928
929 /**
930 * @return {Array} An array of all processes in the model.
931 */
932 getAllProcesses: function() {
933 var processes = [];
934 for (var pid in this.processes)
935 processes.push(this.processes[pid]);
936 return processes;
937 },
938
939 /**
940 * @return {Array} An array of all the counters in the model.
941 */
942 getAllCounters: function() {
943 var counters = [];
944 for (var pid in this.processes) {
945 var process = this.processes[pid];
946 for (var tid in process.counters) {
947 counters.push(process.counters[tid]);
948 }
949 }
950 for (var cpuNumber in this.cpus) {
951 var cpu = this.cpus[cpuNumber];
952 for (var counterName in cpu.counters)
953 counters.push(cpu.counters[counterName]);
954 }
955 return counters;
956 },
957
958 /**
959 * Imports the provided events into the model. The eventData type
960 * is undefined and will be passed to all the timeline importers registered
961 * via TimelineModel.registerImporter. The first importer that returns true
962 * for canImport(events) will be used to import the events.
963 *
964 * @param {Object} events Events to import.
965 * @param {boolean} isAdditionalImport True the eventData being imported is
966 * an additional trace after the primary eventData.
967 * @return {TimelineModelImporter} The importer used for the eventData.
968 */
969 importOneTrace_: function(eventData, isAdditionalImport) {
970 var importerConstructor;
971 for (var i = 0; i < importerConstructors.length; ++i) {
972 if (importerConstructors[i].canImport(eventData)) {
973 importerConstructor = importerConstructors[i];
974 break;
975 }
976 }
977 if (!importerConstructor)
978 throw 'Could not find an importer for the provided eventData.';
979
980 var importer = new importerConstructor(
981 this, eventData, isAdditionalImport);
982 importer.importEvents();
983 return importer;
984 },
985
986 /**
987 * Imports the provided traces into the model. The eventData type
988 * is undefined and will be passed to all the timeline importers registered
989 * via TimelineModel.registerImporter. The first importer that returns true
990 * for canImport(events) will be used to import the events.
991 *
992 * The primary trace is provided via the eventData variable. If multiple
993 * traces are to be imported, specify the first one as events, and the
994 * remainder in the opt_additionalEventData array.
995 *
996 * @param {Object} eventData Events to import.
997 * @param {bool=} opt_zeroAndBoost Whether to align to zero and boost the
998 * by 15%. Defaults to true.
999 * @param {Array=} opt_additionalEventData An array of eventData objects
1000 * (e.g. array of arrays) to
1001 * import after importing the primary events.
1002 */
1003 importEvents: function(eventData,
1004 opt_zeroAndBoost, opt_additionalEventData) {
1005 if (opt_zeroAndBoost === undefined)
1006 opt_zeroAndBoost = true;
1007
1008 var activeImporters = [];
1009 var importer = this.importOneTrace_(eventData, false);
1010 activeImporters.push(importer);
1011 if (opt_additionalEventData) {
1012 for (var i = 0; i < opt_additionalEventData.length; ++i) {
1013 importer = this.importOneTrace_(opt_additionalEventData[i], true);
1014 activeImporters.push(importer);
1015 }
1016 }
1017 for (var i = 0; i < activeImporters.length; ++i)
1018 activeImporters[i].finalizeImport();
1019
1020 for (var i = 0; i < activeImporters.length; ++i)
1021 this.pruneEmptyThreads();
1022
1023 this.updateBounds();
1024
1025 if (opt_zeroAndBoost)
1026 this.shiftWorldToZero();
1027
1028 if (opt_zeroAndBoost &&
1029 this.minTimestamp !== undefined &&
1030 this.maxTimestamp !== undefined) {
1031 var boost = (this.maxTimestamp - this.minTimestamp) * 0.15;
1032 this.minTimestamp = this.minTimestamp - boost;
1033 this.maxTimestamp = this.maxTimestamp + boost;
1034 }
1035 }
1036 };
1037
1038 /**
1039 * @constructor A filter that can be passed into
1040 * Timeline.findAllObjectsMatchingFilter
1041 */
1042 function TimelineFilter(text) {
1043 this.text_ = text;
1044 }
1045 TimelineFilter.prototype = {
1046 __proto__: Object.prototype,
1047
1048 matchSlice: function(slice) {
1049 if (this.text_.length == 0)
1050 return false;
1051 return slice.title.indexOf(this.text_) != -1;
1052 }
1053
1054 };
1055
1056 return {
1057 getPallette: getPallette,
1058 getPalletteHighlightIdBoost: getPalletteHighlightIdBoost,
1059 getColorIdByName: getColorIdByName,
1060 getStringHash: getStringHash,
1061 getStringColorId: getStringColorId,
1062
1063 TimelineSlice: TimelineSlice,
1064 TimelineThreadSlice: TimelineThreadSlice,
1065 TimelineAsyncSlice: TimelineAsyncSlice,
1066 TimelineThread: TimelineThread,
1067 TimelineCounter: TimelineCounter,
1068 TimelineProcess: TimelineProcess,
1069 TimelineCpu: TimelineCpu,
1070 TimelineAsyncSliceGroup: TimelineAsyncSliceGroup,
1071 TimelineModel: TimelineModel,
1072 TimelineFilter: TimelineFilter
1073 };
1074
1075 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698