Home | History | Annotate | Download | only in tracing
      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 TraceModel 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('base.range');
     24 base.require('base.events');
     25 base.require('tracing.trace_model.process');
     26 base.require('tracing.trace_model.kernel');
     27 base.require('tracing.filter');
     28 
     29 base.exportTo('tracing', function() {
     30 
     31   var Process = tracing.trace_model.Process;
     32   var Kernel = tracing.trace_model.Kernel;
     33 
     34   /**
     35    * Builds a model from an array of TraceEvent objects.
     36    * @param {Object=} opt_eventData Data from a single trace to be imported into
     37    *     the new model. See TraceModel.importTraces for details on how to
     38    *     import multiple traces at once.
     39    * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero.
     40    * Defaults to true.
     41    * @constructor
     42    */
     43   function TraceModel(opt_eventData, opt_shiftWorldToZero) {
     44     this.kernel = new Kernel(this);
     45     this.processes = {};
     46     this.importErrors = [];
     47     this.metadata = [];
     48     this.categories = [];
     49     this.bounds = new base.Range();
     50     this.instantEvents = [];
     51 
     52     if (opt_eventData)
     53       this.importTraces([opt_eventData], opt_shiftWorldToZero);
     54   }
     55 
     56   TraceModel.importerConstructors_ = [];
     57 
     58   /**
     59    * Registers an importer. All registered importers are considered
     60    * when processing an import request.
     61    *
     62    * @param {Function} importerConstructor The importer's constructor function.
     63    */
     64   TraceModel.registerImporter = function(importerConstructor) {
     65     TraceModel.importerConstructors_.push(importerConstructor);
     66   };
     67 
     68   TraceModel.prototype = {
     69     __proto__: base.EventTarget.prototype,
     70 
     71     get numProcesses() {
     72       var n = 0;
     73       for (var p in this.processes)
     74         n++;
     75       return n;
     76     },
     77 
     78     /**
     79      * @return {Process} Gets a TimlineProcess for a specified pid or
     80      * creates one if it does not exist.
     81      */
     82     getOrCreateProcess: function(pid) {
     83       if (!this.processes[pid])
     84         this.processes[pid] = new Process(this, pid);
     85       return this.processes[pid];
     86     },
     87 
     88     pushInstantEvent: function(instantEvent) {
     89       this.instantEvents.push(instantEvent);
     90     },
     91 
     92     /**
     93      * Generates the set of categories from the slices and counters.
     94      */
     95     updateCategories_: function() {
     96       var categoriesDict = {};
     97       this.kernel.addCategoriesToDict(categoriesDict);
     98       for (var pid in this.processes)
     99         this.processes[pid].addCategoriesToDict(categoriesDict);
    100 
    101       this.categories = [];
    102       for (var category in categoriesDict)
    103         if (category != '')
    104           this.categories.push(category);
    105     },
    106 
    107     updateBounds: function() {
    108       this.bounds.reset();
    109 
    110       this.kernel.updateBounds();
    111       this.bounds.addRange(this.kernel.bounds);
    112 
    113       for (var pid in this.processes) {
    114         this.processes[pid].updateBounds();
    115         this.bounds.addRange(this.processes[pid].bounds);
    116       }
    117     },
    118 
    119     shiftWorldToZero: function() {
    120       if (this.bounds.isEmpty)
    121         return;
    122       var timeBase = this.bounds.min;
    123       this.kernel.shiftTimestampsForward(-timeBase);
    124       for (var id in this.instantEvents)
    125         this.instantEvents[id].start -= timeBase;
    126       for (var pid in this.processes)
    127         this.processes[pid].shiftTimestampsForward(-timeBase);
    128       this.updateBounds();
    129     },
    130 
    131     getAllThreads: function() {
    132       var threads = [];
    133       for (var tid in this.kernel.threads) {
    134         threads.push(process.threads[tid]);
    135       }
    136       for (var pid in this.processes) {
    137         var process = this.processes[pid];
    138         for (var tid in process.threads) {
    139           threads.push(process.threads[tid]);
    140         }
    141       }
    142       return threads;
    143     },
    144 
    145     /**
    146      * @return {Array} An array of all processes in the model.
    147      */
    148     getAllProcesses: function() {
    149       var processes = [];
    150       for (var pid in this.processes)
    151         processes.push(this.processes[pid]);
    152       return processes;
    153     },
    154 
    155     /**
    156      * @return {Array} An array of all the counters in the model.
    157      */
    158     getAllCounters: function() {
    159       var counters = [];
    160       counters.push.apply(
    161           counters, base.dictionaryValues(this.kernel.counters));
    162       for (var pid in this.processes) {
    163         var process = this.processes[pid];
    164         for (var tid in process.counters) {
    165           counters.push(process.counters[tid]);
    166         }
    167       }
    168       return counters;
    169     },
    170 
    171     /**
    172      * @param {String} The name of the thread to find.
    173      * @return {Array} An array of all the matched threads.
    174      */
    175     findAllThreadsNamed: function(name) {
    176       var namedThreads = [];
    177       namedThreads.push.apply(
    178           namedThreads,
    179           this.kernel.findAllThreadsNamed(name));
    180       for (var pid in this.processes) {
    181         namedThreads.push.apply(
    182             namedThreads,
    183             this.processes[pid].findAllThreadsNamed(name));
    184       }
    185       return namedThreads;
    186     },
    187 
    188     createImporter_: function(eventData) {
    189       var importerConstructor;
    190       for (var i = 0; i < TraceModel.importerConstructors_.length; ++i) {
    191         if (TraceModel.importerConstructors_[i].canImport(eventData)) {
    192           importerConstructor = TraceModel.importerConstructors_[i];
    193           break;
    194         }
    195       }
    196       if (!importerConstructor)
    197         throw new Error(
    198             'Could not find an importer for the provided eventData.');
    199 
    200       var importer = new importerConstructor(
    201           this, eventData);
    202       return importer;
    203     },
    204 
    205     /**
    206      * Imports the provided traces into the model. The eventData type
    207      * is undefined and will be passed to all the  importers registered
    208      * via TraceModel.registerImporter. The first importer that returns true
    209      * for canImport(events) will be used to import the events.
    210      *
    211      * The primary trace is provided via the eventData variable. If multiple
    212      * traces are to be imported, specify the first one as events, and the
    213      * remainder in the opt_additionalEventData array.
    214      *
    215      * @param {Array} traces An array of eventData to be imported. Each
    216      * eventData should correspond to a single trace file and will be handled by
    217      * a separate importer.
    218      * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero.
    219      * Defaults to true.
    220      * @param {bool=} opt_pruneEmptyContainers Whether to prune empty
    221      * containers. Defaults to true.
    222      */
    223     importTraces: function(traces,
    224                            opt_shiftWorldToZero,
    225                            opt_pruneEmptyContainers) {
    226       if (opt_shiftWorldToZero === undefined)
    227         opt_shiftWorldToZero = true;
    228       if (opt_pruneEmptyContainers === undefined)
    229         opt_pruneEmptyContainers = true;
    230 
    231       // Copy the traces array, we may mutate it.
    232       traces = traces.slice(0);
    233 
    234       // Figure out which importers to use.
    235       var importers = [];
    236       for (var i = 0; i < traces.length; ++i)
    237         importers.push(this.createImporter_(traces[i]));
    238 
    239       // Some traces have other traces inside them. Before doing the full
    240       // import, ask the importer if it has any subtraces, and if so, create an
    241       // importer for that, also.
    242       for (var i = 0; i < importers.length; i++) {
    243         var subTrace = importers[i].extractSubtrace();
    244         if (!subTrace)
    245           continue;
    246         traces.push(subTrace);
    247         importers.push(this.createImporter_(subTrace));
    248       }
    249 
    250       // Sort them on priority. This ensures importing happens in a predictable
    251       // order, e.g. linux_perf_importer before trace_event_importer.
    252       importers.sort(function(x, y) {
    253         return x.importPriority - y.importPriority;
    254       });
    255 
    256       // Run the import.
    257       for (var i = 0; i < importers.length; i++)
    258         importers[i].importEvents(i > 0);
    259 
    260       // Autoclose open slices.
    261       this.updateBounds();
    262       this.kernel.autoCloseOpenSlices(this.bounds.max);
    263       for (var pid in this.processes)
    264         this.processes[pid].autoCloseOpenSlices(this.bounds.max);
    265 
    266       // Finalize import.
    267       for (var i = 0; i < importers.length; i++)
    268         importers[i].finalizeImport();
    269 
    270       // Run preinit.
    271       for (var pid in this.processes)
    272         this.processes[pid].preInitializeObjects();
    273 
    274       // Prune empty containers.
    275       if (opt_pruneEmptyContainers) {
    276         this.kernel.pruneEmptyContainers();
    277         for (var pid in this.processes) {
    278           this.processes[pid].pruneEmptyContainers();
    279         }
    280       }
    281 
    282       // Merge kernel and userland slices on each thread.
    283       for (var pid in this.processes) {
    284         this.processes[pid].mergeKernelWithUserland();
    285       }
    286 
    287       this.updateBounds();
    288 
    289       this.updateCategories_();
    290 
    291       if (opt_shiftWorldToZero)
    292         this.shiftWorldToZero();
    293 
    294       // Join refs.
    295       for (var i = 0; i < importers.length; i++)
    296         importers[i].joinRefs();
    297 
    298       // Delete any undeleted objects.
    299       for (var pid in this.processes)
    300         this.processes[pid].autoDeleteObjects(this.bounds.max);
    301 
    302       // Run initializers.
    303       for (var pid in this.processes)
    304         this.processes[pid].initializeObjects();
    305     }
    306   };
    307 
    308   /**
    309    * Importer for empty strings and arrays.
    310    * @constructor
    311    */
    312   function TraceModelEmptyImporter(events) {
    313     this.importPriority = 0;
    314   };
    315 
    316   TraceModelEmptyImporter.canImport = function(eventData) {
    317     if (eventData instanceof Array && eventData.length == 0)
    318       return true;
    319     if (typeof(eventData) === 'string' || eventData instanceof String) {
    320       return eventData.length == 0;
    321     }
    322     return false;
    323   };
    324 
    325   TraceModelEmptyImporter.prototype = {
    326     __proto__: Object.prototype,
    327 
    328     extractSubtrace: function() {
    329       return undefined;
    330     },
    331     importEvents: function() {
    332     },
    333     finalizeImport: function() {
    334     },
    335     joinRefs: function() {
    336     }
    337   };
    338 
    339   TraceModel.registerImporter(TraceModelEmptyImporter);
    340 
    341   return {
    342     TraceModel: TraceModel
    343   };
    344 });
    345