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