Home | History | Annotate | Download | only in model
      1 <!DOCTYPE html>
      2 <!--
      3 Copyright (c) 2013 The Chromium Authors. All rights reserved.
      4 Use of this source code is governed by a BSD-style license that can be
      5 found 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  */
     20 tr.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