Home | History | Annotate | Download | only in gpu_internals
      1 // Copyright (c) 2011 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 
      6 /**
      7  * @fileoverview TimelineModel is a parsed representation of the
      8  * TraceEvents obtained from base/trace_event in which the begin-end
      9  * tokens are converted into a hierarchy of processes, threads,
     10  * subrows, and slices.
     11  *
     12  * The building block of the model is a slice. A slice is roughly
     13  * equivalent to function call executing on a specific thread. As a
     14  * result, slices may have one or more subslices.
     15  *
     16  * A thread contains one or more subrows of slices. Row 0 corresponds to
     17  * the "root" slices, e.g. the topmost slices. Row 1 contains slices that
     18  * are nested 1 deep in the stack, and so on. We use these subrows to draw
     19  * nesting tasks.
     20  *
     21  */
     22 cr.define('gpu', function() {
     23   /**
     24    * A TimelineSlice represents an interval of time on a given thread
     25    * associated with a specific trace event. For example,
     26    *   TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms
     27    *   TRACE_EVENT_END()                  at time=0.3ms
     28    * Results in a single timeline slice from 0.1 with duration 0.2.
     29    *
     30    * All time units are stored in milliseconds.
     31    * @constructor
     32    */
     33   function TimelineSlice(title, colorId, start, args) {
     34     this.title = title;
     35     this.start = start;
     36     this.colorId = colorId;
     37     this.args = args;
     38     this.subSlices = [];
     39   }
     40 
     41   TimelineSlice.prototype = {
     42     selected: false,
     43 
     44     duration: undefined,
     45 
     46     get end() {
     47       return this.start + this.duration;
     48     }
     49   };
     50 
     51   /**
     52    * A TimelineThread stores all the trace events collected for a particular
     53    * thread. We organize the slices on a thread by "subrows," where subrow 0
     54    * has all the root slices, subrow 1 those nested 1 deep, and so on.
     55    *
     56    * @constructor
     57    */
     58   function TimelineThread(parent, tid) {
     59     this.parent = parent;
     60     this.tid = tid;
     61     this.subRows = [[]];
     62   }
     63 
     64   TimelineThread.prototype = {
     65     getSubrow: function(i) {
     66       while (i >= this.subRows.length)
     67         this.subRows.push([]);
     68       return this.subRows[i];
     69     },
     70 
     71     updateBounds: function() {
     72       var slices = this.subRows[0];
     73       if (slices.length != 0) {
     74         this.minTimestamp = slices[0].start;
     75         this.maxTimestamp = slices[slices.length - 1].end;
     76       } else {
     77         this.minTimestamp = undefined;
     78         this.maxTimestamp = undefined;
     79       }
     80     }
     81 
     82   };
     83 
     84   /**
     85    * The TimelineProcess represents a single process in the
     86    * trace. Right now, we keep this around purely for bookkeeping
     87    * reasons.
     88    * @constructor
     89    */
     90   function TimelineProcess(pid) {
     91     this.pid = pid;
     92     this.threads = {};
     93   };
     94 
     95   TimelineProcess.prototype = {
     96     getThread: function(tid) {
     97       if (!this.threads[tid])
     98         this.threads[tid] = new TimelineThread(this, tid);
     99       return this.threads[tid];
    100     }
    101   };
    102 
    103   /**
    104    * Builds a model from an array of TraceEvent objects.
    105    * @param {Array} events An array of TraceEvents created by
    106    *     TraceEvent.ToJSON().
    107    * @constructor
    108    */
    109   function TimelineModel(events) {
    110     this.processes = {};
    111 
    112     if (events)
    113       this.importEvents(events);
    114   }
    115 
    116   TimelineModel.prototype = {
    117     __proto__: cr.EventTarget.prototype,
    118 
    119     getProcess: function(pid) {
    120       if (!this.processes[pid])
    121         this.processes[pid] = new TimelineProcess(pid);
    122       return this.processes[pid];
    123     },
    124 
    125     /**
    126      * The import takes an array of json-ified TraceEvents and adds them into
    127      * the TimelineModel as processes, threads, and slices.
    128      */
    129     importEvents: function(events) {
    130       // A ptid is a pid and tid joined together x:y fashion, eg 1024:130
    131       // The ptid is a unique key for a thread in the trace.
    132 
    133 
    134       // Threadstate
    135       const numColorIds = 12;
    136       function ThreadState(tid) {
    137         this.openSlices = [];
    138       }
    139       var threadStateByPTID = {};
    140 
    141       var nameToColorMap = {};
    142       function getColor(name) {
    143         if (!(name in nameToColorMap)) {
    144           // Compute a simplistic hashcode of the string so we get consistent
    145           // coloring across traces.
    146           var hash = 0;
    147           for (var i = 0; i < name.length; ++i)
    148             hash = (hash + 37 * hash + name.charCodeAt(i)) % 0xFFFFFFFF;
    149           nameToColorMap[name] = hash % numColorIds;
    150         }
    151         return nameToColorMap[name];
    152       }
    153 
    154       // Walk through events
    155       for (var eI = 0; eI < events.length; eI++) {
    156         var event = events[eI];
    157         var ptid = event.pid + ':' + event.tid;
    158         if (!(ptid in threadStateByPTID)) {
    159           threadStateByPTID[ptid] = new ThreadState();
    160         }
    161         var state = threadStateByPTID[ptid];
    162         if (event.ph == 'B') {
    163           var colorId = getColor(event.name);
    164           var slice = new TimelineSlice(event.name, colorId, event.ts,
    165                                         event.args);
    166           state.openSlices.push(slice);
    167         } else if (event.ph == 'E') {
    168           if (state.openSlices.length == 0) {
    169             // Ignore E events that that are unmatched.
    170             continue;
    171           }
    172           var slice = state.openSlices.pop();
    173           slice.duration = event.ts - slice.start;
    174 
    175           // Store the slice on the right subrow.
    176           var thread = this.getProcess(event.pid).getThread(event.tid);
    177           var subRowIndex = state.openSlices.length;
    178           thread.getSubrow(subRowIndex).push(slice);
    179 
    180           // Add the slice to the subSlices array of its parent.
    181           if (state.openSlices.length) {
    182             var parentSlice = state.openSlices[state.openSlices.length - 1];
    183             parentSlice.subSlices.push(slice);
    184           }
    185         } else if (event.ph == 'I') {
    186           // TODO(nduca): Implement parsing of immediate events.
    187           console.log('Parsing of I-type events not implemented.');
    188         } else {
    189           throw new Error('Unrecognized event phase: ' + event.ph +
    190                           '(' + event.name + ')');
    191         }
    192       }
    193       this.updateBounds();
    194 
    195       this.shiftWorldToMicroseconds();
    196 
    197       var boost = (this.maxTimestamp - this.minTimestamp) * 0.15;
    198       this.minTimestamp = this.minTimestamp - boost;
    199       this.maxTimestamp = this.maxTimestamp + boost;
    200     },
    201 
    202     updateBounds: function() {
    203       var wmin = Infinity;
    204       var wmax = -wmin;
    205       var threads = this.getAllThreads();
    206       for (var tI = 0; tI < threads.length; tI++) {
    207         var thread = threads[tI];
    208         thread.updateBounds();
    209         wmin = Math.min(wmin, thread.minTimestamp);
    210         wmax = Math.max(wmax, thread.maxTimestamp);
    211       }
    212       this.minTimestamp = wmin;
    213       this.maxTimestamp = wmax;
    214     },
    215 
    216     shiftWorldToMicroseconds: function() {
    217       var timeBase = this.minTimestamp;
    218       var threads = this.getAllThreads();
    219       for (var tI = 0; tI < threads.length; tI++) {
    220         var thread = threads[tI];
    221         for (var tSR = 0; tSR < thread.subRows.length; tSR++) {
    222           var subRow = thread.subRows[tSR];
    223           for (var tS = 0; tS < subRow.length; tS++) {
    224             var slice = subRow[tS];
    225             slice.start = (slice.start - timeBase) / 1000;
    226             slice.duration /= 1000;
    227           }
    228         }
    229       }
    230 
    231       this.updateBounds();
    232     },
    233 
    234     getAllThreads: function() {
    235       var threads = [];
    236       for (var pid in this.processes) {
    237         var process = this.processes[pid];
    238         for (var tid in process.threads) {
    239           threads.push(process.threads[tid]);
    240         }
    241       }
    242       return threads;
    243     }
    244 
    245   };
    246 
    247   return {
    248     TimelineSlice: TimelineSlice,
    249     TimelineThread: TimelineThread,
    250     TimelineProcess: TimelineProcess,
    251     TimelineModel: TimelineModel
    252   };
    253 });
    254