Home | History | Annotate | Download | only in model
      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  */
     10 base.require('range');
     11 base.require('model.slice');
     12 base.require('color_scheme');
     13 base.require('filter');
     14 
     15 base.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