1<!DOCTYPE html>
2<!--
3Copyright (c) 2013 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7
8<link rel="import" href="/tracing/base/color_scheme.html">
9<link rel="import" href="/tracing/base/guid.html">
10<link rel="import" href="/tracing/base/sorted_array_utils.html">
11<link rel="import" href="/tracing/core/filter.html">
12<link rel="import" href="/tracing/model/event_container.html">
13
14<script>
15'use strict';
16
17/**
18 * @fileoverview Provides the SliceGroup class.
19 */
20tr.exportTo('tr.model', function() {
21  var ColorScheme = tr.b.ColorScheme;
22  var Slice = tr.model.Slice;
23
24  function getSliceLo(s) {
25    return s.start;
26  }
27
28  function getSliceHi(s) {
29    return s.end;
30  }
31
32  /**
33   * A group of Slices, plus code to create them from B/E events, as
34   * well as arrange them into subRows.
35   *
36   * Do not mutate the slices array directly. Modify it only by
37   * SliceGroup mutation methods.
38   *
39   * @constructor
40   * @param {function(new:Slice, category, title, colorId, start, args)=}
41   *     opt_sliceConstructor The constructor to use when creating slices.
42   * @extends {tr.model.EventContainer}
43   */
44  function SliceGroup(parentContainer, opt_sliceConstructor, opt_name) {
45    tr.model.EventContainer.call(this);
46
47    this.parentContainer_ = parentContainer;
48
49    var sliceConstructor = opt_sliceConstructor || Slice;
50    this.sliceConstructor = sliceConstructor;
51
52    this.openPartialSlices_ = [];
53
54    this.slices = [];
55    this.topLevelSlices = [];
56    this.haveTopLevelSlicesBeenBuilt = false;
57    this.name_ = opt_name;
58
59    if (this.model === undefined)
60      throw new Error('SliceGroup must have model defined.');
61  }
62
63  SliceGroup.prototype = {
64    __proto__: tr.model.EventContainer.prototype,
65
66    get parentContainer() {
67      return this.parentContainer_;
68    },
69
70    get model() {
71      return this.parentContainer_.model;
72    },
73
74    get stableId() {
75      return this.parentContainer_.stableId + '.SliceGroup';
76    },
77
78    getSettingsKey: function() {
79      if (!this.name_)
80        return undefined;
81      var parentKey = this.parentContainer_.getSettingsKey();
82      if (!parentKey)
83        return undefined;
84      return parentKey + '.' + this.name;
85    },
86
87    /**
88     * @return {Number} The number of slices in this group.
89     */
90    get length() {
91      return this.slices.length;
92    },
93
94    /**
95     * Helper function that pushes the provided slice onto the slices array.
96     * @param {Slice} slice The slice to be added to the slices array.
97     */
98    pushSlice: function(slice) {
99      this.haveTopLevelSlicesBeenBuilt = false;
100      slice.parentContainer = this.parentContainer_;
101      this.slices.push(slice);
102      return slice;
103    },
104
105    /**
106     * Helper function that pushes the provided slices onto the slices array.
107     * @param {Array.<Slice>} slices An array of slices to be added.
108     */
109    pushSlices: function(slices) {
110      this.haveTopLevelSlicesBeenBuilt = false;
111      slices.forEach(function(slice) {
112        slice.parentContainer = this.parentContainer_;
113        this.slices.push(slice);
114      }, this);
115    },
116
117    /**
118     * Opens a new slice in the group's slices.
119     *
120     * Calls to beginSlice and
121     * endSlice must be made with non-monotonically-decreasing timestamps.
122     *
123     * @param {String} category Category name of the slice to add.
124     * @param {String} title Title of the slice to add.
125     * @param {Number} ts The timetsamp of the slice, in milliseconds.
126     * @param {Object.<string, Object>=} opt_args Arguments associated with
127     * the slice.
128     * @param {Number=} opt_colorId The color of the slice, defined by
129     * its palette id (see base/color_scheme.html).
130     */
131    beginSlice: function(category, title, ts, opt_args, opt_tts,
132                         opt_argsStripped, opt_colorId) {
133      if (this.openPartialSlices_.length) {
134        var prevSlice = this.openPartialSlices_[
135            this.openPartialSlices_.length - 1];
136        if (ts < prevSlice.start)
137          throw new Error('Slices must be added in increasing timestamp order');
138      }
139
140      var colorId = opt_colorId ||
141          ColorScheme.getColorIdForGeneralPurposeString(title);
142      var slice = new this.sliceConstructor(category, title, colorId, ts,
143                                            opt_args ? opt_args : {}, null,
144                                            opt_tts, undefined,
145                                            opt_argsStripped);
146      this.openPartialSlices_.push(slice);
147      slice.didNotFinish = true;
148      this.pushSlice(slice);
149
150      return slice;
151    },
152
153    isTimestampValidForBeginOrEnd: function(ts) {
154      if (!this.openPartialSlices_.length)
155        return true;
156      var top = this.openPartialSlices_[this.openPartialSlices_.length - 1];
157      return ts >= top.start;
158    },
159
160    /**
161     * @return {Number} The number of beginSlices for which an endSlice has not
162     * been issued.
163     */
164    get openSliceCount() {
165      return this.openPartialSlices_.length;
166    },
167
168    get mostRecentlyOpenedPartialSlice() {
169      if (!this.openPartialSlices_.length)
170        return undefined;
171      return this.openPartialSlices_[this.openPartialSlices_.length - 1];
172    },
173
174    /**
175     * Ends the last begun slice in this group and pushes it onto the slice
176     * array.
177     *
178     * @param {Number} ts Timestamp when the slice ended
179     * @param {Number=} opt_colorId The color of the slice, defined by
180     * its palette id (see base/color_scheme.html).
181     * @return {Slice} slice.
182     */
183    endSlice: function(ts, opt_tts, opt_colorId) {
184      if (!this.openSliceCount)
185        throw new Error('endSlice called without an open slice');
186
187      var slice = this.openPartialSlices_[this.openSliceCount - 1];
188      this.openPartialSlices_.splice(this.openSliceCount - 1, 1);
189      if (ts < slice.start)
190        throw new Error('Slice ' + slice.title +
191                        ' end time is before its start.');
192
193      slice.duration = ts - slice.start;
194      slice.didNotFinish = false;
195      slice.colorId = opt_colorId || slice.colorId;
196
197      if (opt_tts && slice.cpuStart !== undefined)
198        slice.cpuDuration = opt_tts - slice.cpuStart;
199
200      return slice;
201    },
202
203    /**
204     * Push a complete event as a Slice into the slice list.
205     * The timestamp can be in any order.
206     *
207     * @param {String} category Category name of the slice to add.
208     * @param {String} title Title of the slice to add.
209     * @param {Number} ts The timetsamp of the slice, in milliseconds.
210     * @param {Number} duration The duration of the slice, in milliseconds.
211     * @param {Object.<string, Object>=} opt_args Arguments associated with
212     * the slice.
213     * @param {Number=} opt_colorId The color of the slice, as defined by
214     * its palette id (see base/color_scheme.html).
215     */
216    pushCompleteSlice: function(category, title, ts, duration, tts,
217                                cpuDuration, opt_args, opt_argsStripped,
218                                opt_colorId, opt_bind_id) {
219      var colorId = opt_colorId ||
220          ColorScheme.getColorIdForGeneralPurposeString(title);
221      var slice = new this.sliceConstructor(category, title, colorId, ts,
222                                            opt_args ? opt_args : {},
223                                            duration, tts, cpuDuration,
224                                            opt_argsStripped, opt_bind_id);
225      if (duration === undefined)
226        slice.didNotFinish = true;
227      this.pushSlice(slice);
228      return slice;
229    },
230
231    /**
232     * Closes any open slices.
233     * @param {Number=} opt_maxTimestamp The end time to use for the closed
234     * slices. If not provided,
235     * the max timestamp for this slice is provided.
236     */
237    autoCloseOpenSlices: function() {
238      this.updateBounds();
239      var maxTimestamp = this.bounds.max;
240      for (var sI = 0; sI < this.slices.length; sI++) {
241        var slice = this.slices[sI];
242        if (slice.didNotFinish)
243          slice.duration = maxTimestamp - slice.start;
244      }
245      this.openPartialSlices_ = [];
246    },
247
248    /**
249     * Shifts all the timestamps inside this group forward by the amount
250     * specified.
251     */
252    shiftTimestampsForward: function(amount) {
253      for (var sI = 0; sI < this.slices.length; sI++) {
254        var slice = this.slices[sI];
255        slice.start = (slice.start + amount);
256      }
257    },
258
259    /**
260     * Updates the bounds for this group based on the slices it contains.
261     */
262    updateBounds: function() {
263      this.bounds.reset();
264      for (var i = 0; i < this.slices.length; i++) {
265        this.bounds.addValue(this.slices[i].start);
266        this.bounds.addValue(this.slices[i].end);
267      }
268    },
269
270    copySlice: function(slice) {
271      var newSlice = new this.sliceConstructor(slice.category, slice.title,
272          slice.colorId, slice.start,
273          slice.args, slice.duration, slice.cpuStart, slice.cpuDuration);
274      newSlice.didNotFinish = slice.didNotFinish;
275      return newSlice;
276    },
277
278    iterateAllEventsInThisContainer: function(eventTypePredicate,
279                                              callback, opt_this) {
280      if (eventTypePredicate.call(opt_this, this.sliceConstructor))
281        this.slices.forEach(callback, opt_this);
282    },
283
284    iterateAllChildEventContainers: function(callback, opt_this) {
285    },
286
287    getSlicesOfName: function(title) {
288      var slices = [];
289      for (var i = 0; i < this.slices.length; i++) {
290        if (this.slices[i].title == title) {
291          slices.push(this.slices[i]);
292        }
293      }
294      return slices;
295    },
296
297    iterSlicesInTimeRange: function(callback, start, end) {
298      var ret = [];
299      tr.b.iterateOverIntersectingIntervals(
300        this.topLevelSlices,
301        function(s) { return s.start; },
302        function(s) { return s.duration; },
303        start,
304        end,
305        function(topLevelSlice) {
306          callback(topLevelSlice);
307          topLevelSlice.iterateAllDescendents(callback);
308        });
309      return ret;
310    },
311
312    findFirstSlice: function() {
313      if (!this.haveTopLevelSlicesBeenBuilt)
314        throw new Error('Nope');
315      if (0 === this.slices.length)
316        return undefined;
317      return this.slices[0];
318    },
319
320    findSliceAtTs: function(ts) {
321      if (!this.haveTopLevelSlicesBeenBuilt)
322        throw new Error('Nope');
323      var i = tr.b.findIndexInSortedClosedIntervals(
324          this.topLevelSlices,
325          getSliceLo, getSliceHi,
326          ts);
327      if (i == -1 || i == this.topLevelSlices.length)
328        return undefined;
329
330      var curSlice = this.topLevelSlices[i];
331
332      // Now recurse on slice looking for subSlice of given ts.
333      while (true) {
334        var i = tr.b.findIndexInSortedClosedIntervals(
335            curSlice.subSlices,
336            getSliceLo, getSliceHi,
337            ts);
338        if (i == -1 || i == curSlice.subSlices.length)
339          return curSlice;
340        curSlice = curSlice.subSlices[i];
341      }
342    },
343
344    findNextSliceAfter: function(ts, refGuid) {
345      var i = tr.b.findLowIndexInSortedArray(
346          this.slices, getSliceLo, ts);
347      if (i === this.slices.length)
348        return undefined;
349      for (; i < this.slices.length; i++) {
350        var slice = this.slices[i];
351        if (slice.start > ts)
352          return slice;
353        if (slice.guid <= refGuid)
354          continue;
355        return slice;
356      }
357      return undefined;
358    },
359
360    /**
361     * Construct subSlices for this group.
362     * Populate the group topLevelSlices, parent slices get a subSlices[],
363     * a selfThreadTime and a selfTime, child slices get a parentSlice
364     * reference.
365     */
366    createSubSlices: function() {
367      this.haveTopLevelSlicesBeenBuilt = true;
368      this.createSubSlicesImpl_();
369      if (this.parentContainer.timeSlices)
370        this.addCpuTimeToSubslices_(this.parentContainer.timeSlices);
371      this.slices.forEach(function(slice) {
372        var selfTime = slice.duration;
373        for (var i = 0; i < slice.subSlices.length; i++)
374          selfTime -= slice.subSlices[i].duration;
375        slice.selfTime = selfTime;
376
377        if (slice.cpuDuration === undefined)
378          return;
379
380        var cpuSelfTime = slice.cpuDuration;
381        for (var i = 0; i < slice.subSlices.length; i++) {
382          if (slice.subSlices[i].cpuDuration !== undefined)
383            cpuSelfTime -= slice.subSlices[i].cpuDuration;
384        }
385        slice.cpuSelfTime = cpuSelfTime;
386      });
387    },
388    createSubSlicesImpl_: function() {
389      var precisionUnit = this.model.intrinsicTimeUnit;
390
391      function addSliceIfBounds(root, child) {
392        // Because we know that the start time of child is >= the start time
393        // of all other slices seen so far, we can just check the last slice
394        // of each row for bounding.
395        if (root.bounds(child, precisionUnit)) {
396          if (root.subSlices && root.subSlices.length > 0) {
397            if (addSliceIfBounds(root.subSlices[root.subSlices.length - 1],
398                                 child))
399              return true;
400          }
401          child.parentSlice = root;
402          if (root.subSlices === undefined)
403            root.subSlices = [];
404          root.subSlices.push(child);
405          return true;
406        }
407        return false;
408      }
409
410      if (!this.slices.length)
411        return;
412
413      var ops = [];
414      for (var i = 0; i < this.slices.length; i++) {
415        if (this.slices[i].subSlices)
416          this.slices[i].subSlices.splice(0,
417                                          this.slices[i].subSlices.length);
418        ops.push(i);
419      }
420
421      var originalSlices = this.slices;
422      ops.sort(function(ix, iy) {
423        var x = originalSlices[ix];
424        var y = originalSlices[iy];
425        if (x.start != y.start)
426          return x.start - y.start;
427
428        // Elements get inserted into the slices array in order of when the
429        // slices start. Because slices must be properly nested, we break
430        // start-time ties by assuming that the elements appearing earlier
431        // in the slices array (and thus ending earlier) start earlier.
432        return ix - iy;
433      });
434
435      var slices = new Array(this.slices.length);
436      for (var i = 0; i < ops.length; i++) {
437        slices[i] = originalSlices[ops[i]];
438      }
439
440      // Actually build the subrows.
441      var rootSlice = slices[0];
442      this.topLevelSlices = [];
443      this.topLevelSlices.push(rootSlice);
444      rootSlice.isTopLevel = true;
445      for (var i = 1; i < slices.length; i++) {
446        var slice = slices[i];
447        if (!addSliceIfBounds(rootSlice, slice)) {
448          rootSlice = slice;
449          rootSlice.isTopLevel = true;
450          this.topLevelSlices.push(rootSlice);
451        }
452      }
453
454      // Keep the slices in sorted form.
455      this.slices = slices;
456    },
457    addCpuTimeToSubslices_: function(timeSlices) {
458      var SCHEDULING_STATE = tr.model.SCHEDULING_STATE;
459      var sliceIdx = 0;
460      timeSlices.forEach(function(timeSlice) {
461        if (timeSlice.schedulingState == SCHEDULING_STATE.RUNNING) {
462          while (sliceIdx < this.topLevelSlices.length) {
463            if (this.addCpuTimeToSubslice_(this.topLevelSlices[sliceIdx],
464                timeSlice)) {
465              // The current top-level slice and children are fully
466              // accounted for, proceed to next top-level slice.
467              sliceIdx++;
468            } else {
469              // The current top-level runs beyond the time slice, break out
470              // so we can potentially add more time slices to it
471              break;
472            }
473          }
474        }
475      }, this);
476    },
477    /* Add run-time of this timeSlice to the passed in slice
478     * and all of it's children (recursively).
479     * Returns whether the slice ends before or at the end of the
480     * time slice, signaling we are done with this slice.
481     */
482    addCpuTimeToSubslice_: function(slice, timeSlice) {
483      // Make sure they overlap
484      if (slice.start > timeSlice.end || slice.end < timeSlice.start)
485        return slice.end <= timeSlice.end;
486
487      // Compute actual overlap
488      var duration = timeSlice.duration;
489      if (slice.start > timeSlice.start)
490        duration -= slice.start - timeSlice.start;
491      if (timeSlice.end > slice.end)
492        duration -= timeSlice.end - slice.end;
493
494      if (slice.cpuDuration) {
495        slice.cpuDuration += duration;
496      } else {
497        slice.cpuDuration = duration;
498      }
499
500      for (var i = 0; i < slice.subSlices.length; i++) {
501        this.addCpuTimeToSubslice_(slice.subSlices[i], timeSlice);
502      }
503
504      return slice.end <= timeSlice.end;
505    }
506  };
507
508  /**
509   * Merge two slice groups.
510   *
511   * If the two groups do not nest properly some of the slices of groupB will
512   * be split to accomodate the improper nesting.  This is done to accomodate
513   * combined kernel and userland call stacks on Android.  Because userland
514   * tracing is done by writing to the trace_marker file, the kernel calls
515   * that get invoked as part of that write may not be properly nested with
516   * the userland call trace.  For example the following sequence may occur:
517   *
518   *     kernel enter sys_write        (the write to trace_marker)
519   *     user   enter some_function
520   *     kernel exit  sys_write
521   *     ...
522   *     kernel enter sys_write        (the write to trace_marker)
523   *     user   exit  some_function
524   *     kernel exit  sys_write
525   *
526   * This is handled by splitting the sys_write call into two slices as
527   * follows:
528   *
529   *     | sys_write |            some_function            | sys_write (cont.) |
530   *                 | sys_write (cont.) |     | sys_write |
531   *
532   * The colorId of both parts of the split slices are kept the same, and the
533   * " (cont.)" suffix is appended to the later parts of a split slice.
534   *
535   * The two input SliceGroups are not modified by this, and the merged
536   * SliceGroup will contain a copy of each of the input groups' slices (those
537   * copies may be split).
538   */
539  SliceGroup.merge = function(groupA, groupB) {
540    // This is implemented by traversing the two slice groups in reverse
541    // order.  The slices in each group are sorted by ascending end-time, so
542    // we must do the traversal from back to front in order to maintain the
543    // sorting.
544    //
545    // We traverse the two groups simultaneously, merging as we go.  At each
546    // iteration we choose the group from which to take the next slice based
547    // on which group's next slice has the greater end-time.  During this
548    // traversal we maintain a stack of currently "open" slices for each input
549    // group.  A slice is considered "open" from the time it gets reached in
550    // our input group traversal to the time we reach an slice in this
551    // traversal with an end-time before the start time of the "open" slice.
552    //
553    // Each time a slice from groupA is opened or closed (events corresponding
554    // to the end-time and start-time of the input slice, respectively) we
555    // split all of the currently open slices from groupB.
556
557    if (groupA.openPartialSlices_.length > 0)
558      throw new Error('groupA has open partial slices');
559
560    if (groupB.openPartialSlices_.length > 0)
561      throw new Error('groupB has open partial slices');
562
563    if (groupA.parentContainer != groupB.parentContainer)
564      throw new Error('Different parent threads. Cannot merge');
565
566    if (groupA.sliceConstructor != groupB.sliceConstructor)
567      throw new Error('Different slice constructors. Cannot merge');
568
569    var result = new SliceGroup(groupA.parentContainer,
570                                groupA.sliceConstructor,
571                                groupA.name_);
572
573    var slicesA = groupA.slices;
574    var slicesB = groupB.slices;
575    var idxA = 0;
576    var idxB = 0;
577    var openA = [];
578    var openB = [];
579
580    var splitOpenSlices = function(when) {
581      for (var i = 0; i < openB.length; i++) {
582        var oldSlice = openB[i];
583        var oldEnd = oldSlice.end;
584        if (when < oldSlice.start || oldEnd < when) {
585          throw new Error('slice should not be split');
586        }
587
588        var newSlice = result.copySlice(oldSlice);
589        newSlice.start = when;
590        newSlice.duration = oldEnd - when;
591        if (newSlice.title.indexOf(' (cont.)') == -1)
592          newSlice.title += ' (cont.)';
593        oldSlice.duration = when - oldSlice.start;
594        openB[i] = newSlice;
595        result.pushSlice(newSlice);
596      }
597    };
598
599    var closeOpenSlices = function(upTo) {
600      while (openA.length > 0 || openB.length > 0) {
601        var nextA = openA[openA.length - 1];
602        var nextB = openB[openB.length - 1];
603        var endA = nextA && nextA.end;
604        var endB = nextB && nextB.end;
605
606        if ((endA === undefined || endA > upTo) &&
607            (endB === undefined || endB > upTo)) {
608          return;
609        }
610
611        if (endB === undefined || endA < endB) {
612          splitOpenSlices(endA);
613          openA.pop();
614        } else {
615          openB.pop();
616        }
617      }
618    };
619
620    while (idxA < slicesA.length || idxB < slicesB.length) {
621      var sA = slicesA[idxA];
622      var sB = slicesB[idxB];
623      var nextSlice, isFromB;
624
625      if (sA === undefined || (sB !== undefined && sA.start > sB.start)) {
626        nextSlice = result.copySlice(sB);
627        isFromB = true;
628        idxB++;
629      } else {
630        nextSlice = result.copySlice(sA);
631        isFromB = false;
632        idxA++;
633      }
634
635      closeOpenSlices(nextSlice.start);
636
637      result.pushSlice(nextSlice);
638
639      if (isFromB) {
640        openB.push(nextSlice);
641      } else {
642        splitOpenSlices(nextSlice.start);
643        openA.push(nextSlice);
644      }
645    }
646
647    closeOpenSlices();
648
649    return result;
650  };
651
652  return {
653    SliceGroup: SliceGroup
654  };
655});
656</script>
657