• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
23base.require('range');
24base.require('event_target');
25base.require('model.process');
26base.require('model.kernel');
27base.require('model.cpu');
28base.require('filter');
29
30base.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