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 */
23cr.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});
1076