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