• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2013 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 Provides the SliceGroup class.
9 */
10base.require('base.range');
11base.require('tracing.trace_model.slice');
12base.require('tracing.color_scheme');
13base.require('tracing.filter');
14
15base.exportTo('tracing.trace_model', function() {
16  var Slice = tracing.trace_model.Slice;
17
18  /**
19   * A group of Slices, plus code to create them from B/E events, as
20   * well as arrange them into subRows.
21   *
22   * Do not mutate the slices array directly. Modify it only by
23   * SliceGroup mutation methods.
24   *
25   * @constructor
26   * @param {function(new:Slice, category, title, colorId, start, args)=}
27   *     opt_sliceConstructor The constructor to use when creating slices.
28   */
29  function SliceGroup(opt_sliceConstructor) {
30    var sliceConstructor = opt_sliceConstructor || Slice;
31    this.sliceConstructor = sliceConstructor;
32
33    this.openPartialSlices_ = [];
34
35    this.slices = [];
36    this.bounds = new base.Range();
37  }
38
39  SliceGroup.prototype = {
40    __proto__: Object.prototype,
41
42    /**
43     * @return {Number} The number of slices in this group.
44     */
45    get length() {
46      return this.slices.length;
47    },
48
49    /**
50     * Helper function that pushes the provided slice onto the slices array.
51     * @param {Slice} slice The slice to be added to the slices array.
52     */
53    pushSlice: function(slice) {
54      this.slices.push(slice);
55      return slice;
56    },
57
58    /**
59     * Helper function that pushes the provided slice onto the slices array.
60     * @param {Array.<Slice>} slices An array of slices to be added.
61     */
62    pushSlices: function(slices) {
63      this.slices.push.apply(this.slices, slices);
64    },
65
66    /**
67     * Push an instant event into the slice list.
68     * @param {tracing.trace_model.InstantEvent} instantEvent The instantEvent.
69     */
70    pushInstantEvent: function(instantEvent) {
71      this.slices.push(instantEvent);
72    },
73
74    /**
75     * Opens a new slice in the group's slices.
76     *
77     * Calls to beginSlice and
78     * endSlice must be made with non-monotonically-decreasing timestamps.
79     *
80     * @param {String} title Title of the slice to add.
81     * @param {Number} ts The timetsamp of the slice, in milliseconds.
82     * @param {Object.<string, Object>=} opt_args Arguments associated with
83     * the slice.
84     */
85    beginSlice: function(category, title, ts, opt_args) {
86      if (this.openPartialSlices_.length) {
87        var prevSlice = this.openPartialSlices_[
88            this.openPartialSlices_.length - 1];
89        if (ts < prevSlice.start)
90          throw new Error('Slices must be added in increasing timestamp order');
91      }
92
93      var colorId = tracing.getStringColorId(title);
94      var slice = new this.sliceConstructor(category, title, colorId, ts,
95                                            opt_args ? opt_args : {});
96      this.openPartialSlices_.push(slice);
97
98      return slice;
99    },
100
101    isTimestampValidForBeginOrEnd: function(ts) {
102      if (!this.openPartialSlices_.length)
103        return true;
104      var top = this.openPartialSlices_[this.openPartialSlices_.length - 1];
105      return ts >= top.start;
106    },
107
108    /**
109     * @return {Number} The number of beginSlices for which an endSlice has not
110     * been issued.
111     */
112    get openSliceCount() {
113      return this.openPartialSlices_.length;
114    },
115
116    get mostRecentlyOpenedPartialSlice() {
117      if (!this.openPartialSlices_.length)
118        return undefined;
119      return this.openPartialSlices_[this.openPartialSlices_.length - 1];
120    },
121
122    /**
123     * Ends the last begun slice in this group and pushes it onto the slice
124     * array.
125     *
126     * @param {Number} ts Timestamp when the slice ended.
127     * @return {Slice} slice.
128     */
129    endSlice: function(ts) {
130      if (!this.openSliceCount)
131        throw new Error('endSlice called without an open slice');
132
133      var slice = this.openPartialSlices_[this.openSliceCount - 1];
134      this.openPartialSlices_.splice(this.openSliceCount - 1, 1);
135      if (ts < slice.start)
136        throw new Error('Slice ' + slice.title +
137                        ' end time is before its start.');
138
139      slice.duration = ts - slice.start;
140      this.pushSlice(slice);
141
142      return slice;
143    },
144
145    /**
146     * Closes any open slices.
147     * @param {Number=} opt_maxTimestamp The end time to use for the closed
148     * slices. If not provided,
149     * the max timestamp for this slice is provided.
150     */
151    autoCloseOpenSlices: function(opt_maxTimestamp) {
152      if (!opt_maxTimestamp) {
153        this.updateBounds();
154        opt_maxTimestamp = this.bounds.max;
155      }
156      while (this.openSliceCount > 0) {
157        var slice = this.endSlice(opt_maxTimestamp);
158        slice.didNotFinish = true;
159      }
160    },
161
162    /**
163     * Shifts all the timestamps inside this group forward by the amount
164     * specified.
165     */
166    shiftTimestampsForward: function(amount) {
167      for (var sI = 0; sI < this.slices.length; sI++) {
168        var slice = this.slices[sI];
169        slice.start = (slice.start + amount);
170      }
171      for (var sI = 0; sI < this.openPartialSlices_.length; sI++) {
172        var slice = this.openPartialSlices_[i];
173        slice.start = (slice.start + amount);
174      }
175    },
176
177    /**
178     * Updates the bounds for this group based on the slices it contains.
179     */
180    updateBounds: function() {
181      this.bounds.reset();
182      for (var i = 0; i < this.slices.length; i++) {
183        this.bounds.addValue(this.slices[i].start);
184        this.bounds.addValue(this.slices[i].end);
185      }
186
187      if (this.openPartialSlices_.length) {
188        this.bounds.addValue(this.openPartialSlices_[0].start);
189        this.bounds.addValue(
190            this.openPartialSlices_[this.openPartialSlices_.length - 1].start);
191      }
192    },
193
194    copySlice: function(slice) {
195      var newSlice = new this.sliceConstructor(slice.category, slice.title,
196          slice.colorId, slice.start,
197          slice.args, slice.duration);
198      newSlice.didNotFinish = slice.didNotFinish;
199      return newSlice;
200    }
201  };
202
203  /**
204   * Merge two slice groups.
205   *
206   * If the two groups do not nest properly some of the slices of groupB will
207   * be split to accomodate the improper nesting.  This is done to accomodate
208   * combined kernel and userland call stacks on Android.  Because userland
209   * tracing is done by writing to the trace_marker file, the kernel calls
210   * that get invoked as part of that write may not be properly nested with
211   * the userland call trace.  For example the following sequence may occur:
212   *
213   *     kernel enter sys_write        (the write to trace_marker)
214   *     user   enter some_function
215   *     kernel exit  sys_write
216   *     ...
217   *     kernel enter sys_write        (the write to trace_marker)
218   *     user   exit  some_function
219   *     kernel exit  sys_write
220   *
221   * This is handled by splitting the sys_write call into two slices as
222   * follows:
223   *
224   *     | sys_write |            some_function            | sys_write (cont.) |
225   *                 | sys_write (cont.) |     | sys_write |
226   *
227   * The colorId of both parts of the split slices are kept the same, and the
228   * " (cont.)" suffix is appended to the later parts of a split slice.
229   *
230   * The two input SliceGroups are not modified by this, and the merged
231   * SliceGroup will contain a copy of each of the input groups' slices (those
232   * copies may be split).
233   */
234  SliceGroup.merge = function(groupA, groupB) {
235    // This is implemented by traversing the two slice groups in reverse
236    // order.  The slices in each group are sorted by ascending end-time, so
237    // we must do the traversal from back to front in order to maintain the
238    // sorting.
239    //
240    // We traverse the two groups simultaneously, merging as we go.  At each
241    // iteration we choose the group from which to take the next slice based
242    // on which group's next slice has the greater end-time.  During this
243    // traversal we maintain a stack of currently "open" slices for each input
244    // group.  A slice is considered "open" from the time it gets reached in
245    // our input group traversal to the time we reach an slice in this
246    // traversal with an end-time before the start time of the "open" slice.
247    //
248    // Each time a slice from groupA is opened or closed (events corresponding
249    // to the end-time and start-time of the input slice, respectively) we
250    // split all of the currently open slices from groupB.
251
252    if (groupA.openPartialSlices_.length > 0) {
253      throw new Error('groupA has open partial slices');
254    }
255    if (groupB.openPartialSlices_.length > 0) {
256      throw new Error('groupB has open partial slices');
257    }
258
259    var result = new SliceGroup();
260
261    var slicesA = groupA.slices;
262    var slicesB = groupB.slices;
263    var idxA = slicesA.length - 1;
264    var idxB = slicesB.length - 1;
265    var openA = [];
266    var openB = [];
267
268    var splitOpenSlices = function(when) {
269      for (var i = 0; i < openB.length; i++) {
270        var oldSlice = openB[i];
271        var oldEnd = oldSlice.end;
272        if (when < oldSlice.start || oldEnd < when) {
273          throw new Error('slice should not be split');
274        }
275
276        var newSlice = result.copySlice(oldSlice);
277        oldSlice.start = when;
278        oldSlice.duration = oldEnd - when;
279        oldSlice.title += ' (cont.)';
280        newSlice.duration = when - newSlice.start;
281        openB[i] = newSlice;
282        result.pushSlice(newSlice);
283      }
284    };
285
286    var closeOpenSlices = function(upTo) {
287      while (openA.length > 0 || openB.length > 0) {
288        var nextA = openA[openA.length - 1];
289        var nextB = openB[openB.length - 1];
290        var startA = nextA && nextA.start;
291        var startB = nextB && nextB.start;
292
293        if ((startA === undefined || startA < upTo) &&
294            (startB === undefined || startB < upTo)) {
295          return;
296        }
297
298        if (startB === undefined || startA > startB) {
299          splitOpenSlices(startA);
300          openA.pop();
301        } else {
302          openB.pop();
303        }
304      }
305    };
306
307    while (idxA >= 0 || idxB >= 0) {
308      var sA = slicesA[idxA];
309      var sB = slicesB[idxB];
310      var nextSlice, isFromB;
311
312      if (sA === undefined || (sB !== undefined && sA.end < sB.end)) {
313        nextSlice = result.copySlice(sB);
314        isFromB = true;
315        idxB--;
316      } else {
317        nextSlice = result.copySlice(sA);
318        isFromB = false;
319        idxA--;
320      }
321
322      closeOpenSlices(nextSlice.end);
323
324      result.pushSlice(nextSlice);
325
326      if (isFromB) {
327        openB.push(nextSlice);
328      } else {
329        splitOpenSlices(nextSlice.end);
330        openA.push(nextSlice);
331      }
332    }
333
334    closeOpenSlices();
335
336    result.slices.reverse();
337
338    return result;
339  };
340
341  return {
342    SliceGroup: SliceGroup
343  };
344});
345