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