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 */
23base.require('base.range');
24base.require('base.events');
25base.require('tracing.trace_model.process');
26base.require('tracing.trace_model.kernel');
27base.require('tracing.filter');
28
29base.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