Home | History | Annotate | Download | only in src
      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 TimelineModel is a parsed representation of the
      9  * TraceEvents obtained from base/trace_event in which the begin-end
     10  * tokens are converted into a hierarchy of processes, threads,
     11  * subrows, and slices.
     12  *
     13  * The building block of the model is a slice. A slice is roughly
     14  * equivalent to function call executing on a specific thread. As a
     15  * result, slices may have one or more subslices.
     16  *
     17  * A thread contains one or more subrows of slices. Row 0 corresponds to
     18  * the "root" slices, e.g. the topmost slices. Row 1 contains slices that
     19  * are nested 1 deep in the stack, and so on. We use these subrows to draw
     20  * nesting tasks.
     21  *
     22  */
     23 base.require('event_target');
     24 base.require('timeline_process');
     25 base.require('timeline_cpu');
     26 base.require('timeline_filter');
     27 base.exportTo('tracing', function() {
     28 
     29   var TimelineProcess = tracing.TimelineProcess;
     30   var TimelineCpu = tracing.TimelineCpu;
     31 
     32   /**
     33    * Builds a model from an array of TraceEvent objects.
     34    * @param {Object=} opt_eventData Data from a single trace to be imported into
     35    *     the new model. See TimelineModel.importTraces for details on how to
     36    *     import multiple traces at once.
     37    * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero.
     38    * Defaults to true.
     39    * @constructor
     40    */
     41   function TimelineModel(opt_eventData, opt_shiftWorldToZero) {
     42     this.cpus = {};
     43     this.processes = {};
     44     this.importErrors = [];
     45     this.metadata = [];
     46     this.categories = [];
     47 
     48     if (opt_eventData)
     49       this.importTraces([opt_eventData], opt_shiftWorldToZero);
     50   }
     51 
     52   var importerConstructors = [];
     53 
     54   /**
     55    * Registers an importer. All registered importers are considered
     56    * when processing an import request.
     57    *
     58    * @param {Function} importerConstructor The importer's constructor function.
     59    */
     60   TimelineModel.registerImporter = function(importerConstructor) {
     61     importerConstructors.push(importerConstructor);
     62   };
     63 
     64   function TimelineModelEmptyImporter(events) {
     65     this.importPriority = 0;
     66   };
     67 
     68   TimelineModelEmptyImporter.canImport = function(eventData) {
     69     if (eventData instanceof Array && eventData.length == 0)
     70       return true;
     71     if (typeof(eventData) === 'string' || eventData instanceof String) {
     72       return eventData.length == 0;
     73     }
     74     return false;
     75   };
     76 
     77   TimelineModelEmptyImporter.prototype = {
     78     __proto__: Object.prototype,
     79 
     80     importEvents: function() {
     81     },
     82     finalizeImport: function() {
     83     }
     84   };
     85 
     86   TimelineModel.registerImporter(TimelineModelEmptyImporter);
     87 
     88   TimelineModel.prototype = {
     89     __proto__: base.EventTarget.prototype,
     90 
     91     get numProcesses() {
     92       var n = 0;
     93       for (var p in this.processes)
     94         n++;
     95       return n;
     96     },
     97 
     98     /**
     99      * @return {TimelineProcess} Gets a specific TimelineCpu or creates one if
    100      * it does not exist.
    101      */
    102     getOrCreateCpu: function(cpuNumber) {
    103       if (!this.cpus[cpuNumber])
    104         this.cpus[cpuNumber] = new TimelineCpu(cpuNumber);
    105       return this.cpus[cpuNumber];
    106     },
    107 
    108     /**
    109      * @return {TimelineProcess} Gets a TimlineProcess for a specified pid or
    110      * creates one if it does not exist.
    111      */
    112     getOrCreateProcess: function(pid) {
    113       if (!this.processes[pid])
    114         this.processes[pid] = new TimelineProcess(pid);
    115       return this.processes[pid];
    116     },
    117 
    118     /**
    119      * Closes any slices that need closing
    120      */
    121     autoCloseOpenSlices_: function() {
    122       this.updateBounds();
    123       var maxTimestamp = this.maxTimestamp;
    124       for (var pid in this.processes) {
    125         var process = this.processes[pid];
    126         for (var tid in process.threads) {
    127           var thread = process.threads[tid];
    128           thread.autoCloseOpenSlices(maxTimestamp);
    129         }
    130       }
    131     },
    132 
    133     /**
    134      * Generates the set of categories from the slices.
    135      */
    136     updateCategories_: function() {
    137       // TODO(sullivan): Is there a way to do this more cleanly?
    138       for (var pid in this.processes) {
    139         var process = this.processes[pid];
    140         for (var tid in process.threads) {
    141           var slices = process.threads[tid].slices;
    142           for (var i = 0; i < slices.length; i++) {
    143             var category = slices[i].category;
    144             if (category && this.categories.indexOf(category) == -1) {
    145               this.categories.push(category);
    146             }
    147           }
    148         }
    149       }
    150       for (var cpu in this.cpus) {
    151         var slices = this.cpus[cpu].slices;
    152         for (var i = 0; i < slices.length; i++) {
    153           var category = slices[i].category;
    154           if (category && this.categories.indexOf(category) == -1) {
    155             this.categories.push(category);
    156           }
    157         }
    158       }
    159     },
    160 
    161     /**
    162      * Removes threads from the model that are fully empty.
    163      */
    164     pruneEmptyThreads_: function() {
    165       for (var pid in this.processes) {
    166         var process = this.processes[pid];
    167         var threadsToKeep = {};
    168         for (var tid in process.threads) {
    169           var thread = process.threads[tid];
    170           if (!thread.isEmpty)
    171             threadsToKeep[tid] = thread;
    172         }
    173         process.threads = threadsToKeep;
    174       }
    175     },
    176 
    177     updateBounds: function() {
    178       var wmin = Infinity;
    179       var wmax = -wmin;
    180       var hasData = false;
    181 
    182       var threads = this.getAllThreads();
    183       for (var tI = 0; tI < threads.length; tI++) {
    184         var thread = threads[tI];
    185         thread.updateBounds();
    186         if (thread.minTimestamp != undefined &&
    187             thread.maxTimestamp != undefined) {
    188           wmin = Math.min(wmin, thread.minTimestamp);
    189           wmax = Math.max(wmax, thread.maxTimestamp);
    190           hasData = true;
    191         }
    192       }
    193       var counters = this.getAllCounters();
    194       for (var tI = 0; tI < counters.length; tI++) {
    195         var counter = counters[tI];
    196         counter.updateBounds();
    197         if (counter.minTimestamp != undefined &&
    198             counter.maxTimestamp != undefined) {
    199           hasData = true;
    200           wmin = Math.min(wmin, counter.minTimestamp);
    201           wmax = Math.max(wmax, counter.maxTimestamp);
    202         }
    203       }
    204 
    205       for (var cpuNumber in this.cpus) {
    206         var cpu = this.cpus[cpuNumber];
    207         cpu.updateBounds();
    208         if (cpu.minTimestamp != undefined &&
    209             cpu.maxTimestamp != undefined) {
    210           hasData = true;
    211           wmin = Math.min(wmin, cpu.minTimestamp);
    212           wmax = Math.max(wmax, cpu.maxTimestamp);
    213         }
    214       }
    215 
    216       if (hasData) {
    217         this.minTimestamp = wmin;
    218         this.maxTimestamp = wmax;
    219       } else {
    220         this.maxTimestamp = undefined;
    221         this.minTimestamp = undefined;
    222       }
    223     },
    224 
    225     shiftWorldToZero: function() {
    226       if (this.minTimestamp === undefined)
    227         return;
    228       var timeBase = this.minTimestamp;
    229       for (var pid in this.processes)
    230         this.processes[pid].shiftTimestampsForward(-timeBase);
    231       for (var cpuNumber in this.cpus)
    232         this.cpus[cpuNumber].shiftTimestampsForward(-timeBase);
    233       this.updateBounds();
    234     },
    235 
    236     getAllThreads: function() {
    237       var threads = [];
    238       for (var pid in this.processes) {
    239         var process = this.processes[pid];
    240         for (var tid in process.threads) {
    241           threads.push(process.threads[tid]);
    242         }
    243       }
    244       return threads;
    245     },
    246 
    247     /**
    248      * @return {Array} An array of all cpus in the model.
    249      */
    250     getAllCpus: function() {
    251       var cpus = [];
    252       for (var cpu in this.cpus)
    253         cpus.push(this.cpus[cpu]);
    254       return cpus;
    255     },
    256 
    257     /**
    258      * @return {Array} An array of all processes in the model.
    259      */
    260     getAllProcesses: function() {
    261       var processes = [];
    262       for (var pid in this.processes)
    263         processes.push(this.processes[pid]);
    264       return processes;
    265     },
    266 
    267     /**
    268      * @return {Array} An array of all the counters in the model.
    269      */
    270     getAllCounters: function() {
    271       var counters = [];
    272       for (var pid in this.processes) {
    273         var process = this.processes[pid];
    274         for (var tid in process.counters) {
    275           counters.push(process.counters[tid]);
    276         }
    277       }
    278       for (var cpuNumber in this.cpus) {
    279         var cpu = this.cpus[cpuNumber];
    280         for (var counterName in cpu.counters)
    281           counters.push(cpu.counters[counterName]);
    282       }
    283       return counters;
    284     },
    285 
    286     /**
    287      * @param {String} The name of the thread to find.
    288      * @return {Array} An array of all the matched threads.
    289      */
    290     findAllThreadsNamed: function(name) {
    291       var namedThreads = [];
    292       var threads = this.getAllThreads();
    293       for (var i = 0; i < threads.length; i++) {
    294         var thread = threads[i];
    295         if (thread.name == name)
    296           namedThreads.push(thread);
    297       }
    298       return namedThreads;
    299     },
    300 
    301     createImporter_: function(eventData) {
    302       var importerConstructor;
    303       for (var i = 0; i < importerConstructors.length; ++i) {
    304         if (importerConstructors[i].canImport(eventData)) {
    305           importerConstructor = importerConstructors[i];
    306           break;
    307         }
    308       }
    309       if (!importerConstructor)
    310         throw new Error(
    311             'Could not find an importer for the provided eventData.');
    312 
    313       var importer = new importerConstructor(
    314           this, eventData);
    315       return importer;
    316     },
    317 
    318     /**
    319      * Imports the provided traces into the model. The eventData type
    320      * is undefined and will be passed to all the timeline importers registered
    321      * via TimelineModel.registerImporter. The first importer that returns true
    322      * for canImport(events) will be used to import the events.
    323      *
    324      * The primary trace is provided via the eventData variable. If multiple
    325      * traces are to be imported, specify the first one as events, and the
    326      * remainder in the opt_additionalEventData array.
    327      *
    328      * @param {Array} traces An array of eventData to be imported. Each
    329      * eventData should correspond to a single trace file and will be handled by
    330      * a separate importer.
    331      * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero.
    332      * Defaults to true.
    333      */
    334     importTraces: function(traces,
    335                            opt_shiftWorldToZero) {
    336       if (opt_shiftWorldToZero === undefined)
    337         opt_shiftWorldToZero = true;
    338 
    339       // Figure out which importers to use.
    340       var importers = [];
    341       for (var i = 0; i < traces.length; ++i)
    342         importers.push(this.createImporter_(traces[i]));
    343 
    344       // Sort them on priority. This ensures importing happens in a predictable
    345       // order, e.g. linux_perf_importer before trace_event_importer.
    346       importers.sort(function(x, y) {
    347         return x.importPriority - y.importPriority;
    348       });
    349 
    350       // Run the import.
    351       for (var i = 0; i < importers.length; i++)
    352         importers[i].importEvents(i > 0);
    353 
    354       this.autoCloseOpenSlices_();
    355 
    356       for (var i = 0; i < importers.length; i++)
    357         importers[i].finalizeImport();
    358 
    359       this.pruneEmptyThreads_();
    360       this.updateBounds();
    361 
    362       this.updateCategories_();
    363 
    364       if (opt_shiftWorldToZero)
    365         this.shiftWorldToZero();
    366     }
    367   };
    368 
    369   return {
    370     TimelineModel: TimelineModel
    371   };
    372 
    373 });
    374