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.
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.
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.
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
23 base.require('base.range');
24 base.require('base.events');
25 base.require('base.interval_tree');
26 base.require('tracing.importer.importer');
27 base.require('tracing.importer.task');
28 base.require('tracing.trace_model.process');
29 base.require('tracing.trace_model.kernel');
30 base.require('tracing.filter');
31 base.require('ui.overlay');
33 base.exportTo('tracing', function() {
35 var Importer = tracing.importer.Importer;
36 var Process = tracing.trace_model.Process;
37 var Kernel = tracing.trace_model.Kernel;
40 * Builds a model from an array of TraceEvent objects.
41 * @param {Object=} opt_eventData Data from a single trace to be imported into
42 * the new model. See TraceModel.importTraces for details on how to
43 * import multiple traces at once.
44 * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero.
48 function TraceModel(opt_eventData, opt_shiftWorldToZero) {
49 this.kernel = new Kernel(this);
53 this.bounds = new base.Range();
54 this.instantEvents = [];
57 this.flowIntervalTree = new base.IntervalTree(
58 function(s) { return s.start; },
59 function(e) { return e.start; });
61 this.importWarnings_ = [];
62 this.reportedImportWarnings_ = {};
65 this.importTraces([opt_eventData], opt_shiftWorldToZero);
68 TraceModel.importerConstructors_ = [];
71 * Registers an importer. All registered importers are considered
72 * when processing an import request.
74 * @param {Function} importerConstructor The importer's constructor function.
76 TraceModel.registerImporter = function(importerConstructor) {
77 TraceModel.importerConstructors_.push(importerConstructor);
80 TraceModel.prototype = {
81 __proto__: base.EventTarget.prototype,
85 for (var p in this.processes)
91 * @return {Process} Gets a TimelineProcess for a specified pid or
92 * creates one if it does not exist.
94 getOrCreateProcess: function(pid) {
95 if (!this.processes[pid])
96 this.processes[pid] = new Process(this, pid);
97 return this.processes[pid];
100 pushInstantEvent: function(instantEvent) {
101 this.instantEvents.push(instantEvent);
105 * Generates the set of categories from the slices and counters.
107 updateCategories_: function() {
108 var categoriesDict = {};
109 this.kernel.addCategoriesToDict(categoriesDict);
110 for (var pid in this.processes)
111 this.processes[pid].addCategoriesToDict(categoriesDict);
113 this.categories = [];
114 for (var category in categoriesDict)
116 this.categories.push(category);
119 updateBounds: function() {
122 this.kernel.updateBounds();
123 this.bounds.addRange(this.kernel.bounds);
125 for (var pid in this.processes) {
126 this.processes[pid].updateBounds();
127 this.bounds.addRange(this.processes[pid].bounds);
131 shiftWorldToZero: function() {
132 if (this.bounds.isEmpty)
134 var timeBase = this.bounds.min;
135 this.kernel.shiftTimestampsForward(-timeBase);
137 for (var id in this.instantEvents)
138 this.instantEvents[id].start -= timeBase;
140 for (var pid in this.processes)
141 this.processes[pid].shiftTimestampsForward(-timeBase);
146 getAllThreads: function() {
148 for (var tid in this.kernel.threads) {
149 threads.push(process.threads[tid]);
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]);
161 * @return {Array} An array of all processes in the model.
163 getAllProcesses: function() {
165 for (var pid in this.processes)
166 processes.push(this.processes[pid]);
171 * @return {Array} An array of all the counters in the model.
173 getAllCounters: function() {
176 counters, base.dictionaryValues(this.kernel.counters));
177 for (var pid in this.processes) {
178 var process = this.processes[pid];
179 for (var tid in process.counters) {
180 counters.push(process.counters[tid]);
187 * @param {String} The name of the thread to find.
188 * @return {Array} An array of all the matched threads.
190 findAllThreadsNamed: function(name) {
191 var namedThreads = [];
192 namedThreads.push.apply(
194 this.kernel.findAllThreadsNamed(name));
195 for (var pid in this.processes) {
196 namedThreads.push.apply(
198 this.processes[pid].findAllThreadsNamed(name));
203 createImporter_: function(eventData) {
204 var importerConstructor;
205 for (var i = 0; i < TraceModel.importerConstructors_.length; ++i) {
206 if (TraceModel.importerConstructors_[i].canImport(eventData)) {
207 importerConstructor = TraceModel.importerConstructors_[i];
211 if (!importerConstructor)
213 'Could not find an importer for the provided eventData.');
215 var importer = new importerConstructor(
221 * Imports the provided traces into the model. The eventData type
222 * is undefined and will be passed to all the importers registered
223 * via TraceModel.registerImporter. The first importer that returns true
224 * for canImport(events) will be used to import the events.
226 * The primary trace is provided via the eventData variable. If multiple
227 * traces are to be imported, specify the first one as events, and the
228 * remainder in the opt_additionalEventData array.
230 * @param {Array} traces An array of eventData to be imported. Each
231 * eventData should correspond to a single trace file and will be handled by
232 * a separate importer.
233 * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero.
235 * @param {bool=} opt_pruneEmptyContainers Whether to prune empty
236 * containers. Defaults to true.
238 importTraces: function(traces,
239 opt_shiftWorldToZero,
240 opt_pruneEmptyContainers) {
241 var progressMeter = {
242 update: function(msg) {}
244 var task = this.createImportTracesTask(
247 opt_shiftWorldToZero,
248 opt_pruneEmptyContainers);
249 tracing.importer.Task.RunSynchronously(task);
253 * Imports a trace with the usual options from importTraces, but
254 * does so using idle callbacks, putting up an import dialog
255 * during the import process.
257 importTracesWithProgressDialog: function(traces,
258 opt_shiftWorldToZero,
259 opt_pruneEmptyContainers) {
260 var overlay = ui.Overlay();
261 overlay.title = 'Importing...';
262 overlay.userCanClose = false;
263 overlay.msgEl = document.createElement('div');
264 overlay.appendChild(overlay.msgEl);
265 overlay.msgEl.style.margin = '20px';
266 overlay.update = function(msg) {
267 this.msgEl.textContent = msg;
269 overlay.visible = true;
271 var task = this.createImportTracesTask(
274 opt_shiftWorldToZero,
275 opt_pruneEmptyContainers);
276 var promise = tracing.importer.Task.RunWhenIdle(task);
279 overlay.visible = false;
281 overlay.visible = false;
287 * Creates a task that will import the provided traces into the model,
288 * updating the progressMeter as it goes. Parameters are as defined in
291 createImportTracesTask: function(progressMeter,
293 opt_shiftWorldToZero,
294 opt_pruneEmptyContainers) {
296 throw new Error('Already importing.');
297 if (opt_shiftWorldToZero === undefined)
298 opt_shiftWorldToZero = true;
299 if (opt_pruneEmptyContainers === undefined)
300 opt_pruneEmptyContainers = true;
301 this.importing_ = true;
303 // Just some simple setup. It is useful to have a nop first
304 // task so that we can set up the lastTask = lastTask.after()
305 // pattern that follows.
306 var importTask = new tracing.importer.Task(function() {
307 progressMeter.update('I will now import your traces for you...');
309 var lastTask = importTask;
313 lastTask = lastTask.after(function() {
314 // Copy the traces array, we may mutate it.
315 traces = traces.slice(0);
316 progressMeter.update('Creating importers...');
317 // Figure out which importers to use.
318 for (var i = 0; i < traces.length; ++i)
319 importers.push(this.createImporter_(traces[i]));
321 // Some traces have other traces inside them. Before doing the full
322 // import, ask the importer if it has any subtraces, and if so, create
323 // importers for them, also.
324 for (var i = 0; i < importers.length; i++) {
325 var subtraces = importers[i].extractSubtraces();
326 for (var j = 0; j < subtraces.length; j++) {
327 traces.push(subtraces[j]);
328 importers.push(this.createImporter_(subtraces[j]));
332 // Sort them on priority. This ensures importing happens in a
333 // predictable order, e.g. linux_perf_importer before
334 // trace_event_importer.
335 importers.sort(function(x, y) {
336 return x.importPriority - y.importPriority;
341 lastTask = lastTask.after(function(task) {
342 importers.forEach(function(importer, index) {
343 task.subTask(function() {
344 progressMeter.update(
345 'Importing ' + (index + 1) + ' of ' + importers.length);
346 importer.importEvents(index > 0);
351 // Autoclose open slices.
352 lastTask = lastTask.after(function() {
353 progressMeter.update('Autoclosing open slices...');
355 this.kernel.autoCloseOpenSlices(this.bounds.max);
356 for (var pid in this.processes)
357 this.processes[pid].autoCloseOpenSlices(this.bounds.max);
361 lastTask = lastTask.after(function(task) {
362 importers.forEach(function(importer, index) {
363 progressMeter.update(
364 'Finalizing import ' + (index + 1) + '/' + importers.length);
365 importer.finalizeImport();
370 lastTask = lastTask.after(function() {
371 progressMeter.update('Initializing objects (step 1/2)...');
372 for (var pid in this.processes)
373 this.processes[pid].preInitializeObjects();
376 // Prune empty containers.
377 if (opt_pruneEmptyContainers) {
378 lastTask = lastTask.after(function() {
379 progressMeter.update('Pruning empty containers...');
380 this.kernel.pruneEmptyContainers();
381 for (var pid in this.processes) {
382 this.processes[pid].pruneEmptyContainers();
387 // Merge kernel and userland slices on each thread.
388 lastTask = lastTask.after(function() {
389 progressMeter.update('Merging kernel with userland...');
390 for (var pid in this.processes)
391 this.processes[pid].mergeKernelWithUserland();
394 lastTask = lastTask.after(function() {
395 progressMeter.update('Computing final world bounds...');
397 this.updateCategories_();
399 if (opt_shiftWorldToZero)
400 this.shiftWorldToZero();
403 // Build the flow event interval tree.
404 lastTask = lastTask.after(function() {
405 progressMeter.update('Building flow event map...');
406 for (var i = 0; i < this.flowEvents.length; ++i) {
407 var pair = this.flowEvents[i];
408 this.flowIntervalTree.insert(pair[0], pair[1]);
410 this.flowIntervalTree.updateHighValues();
414 lastTask = lastTask.after(function() {
415 progressMeter.update('Joining object refs...');
416 for (var i = 0; i < importers.length; i++)
417 importers[i].joinRefs();
420 // Delete any undeleted objects.
421 lastTask = lastTask.after(function() {
422 progressMeter.update('Cleaning up undeleted objects...');
423 for (var pid in this.processes)
424 this.processes[pid].autoDeleteObjects(this.bounds.max);
428 lastTask = lastTask.after(function() {
429 progressMeter.update('Initializing objects (step 2/2)...');
430 for (var pid in this.processes)
431 this.processes[pid].initializeObjects();
435 lastTask.after(function() {
436 this.importing_ = false;
442 * @param {Object} data The import warning data. Data must provide two
443 * accessors: type, message. The types are used to determine if we
444 * should output the message, we'll only output one message of each type.
445 * The message is the actual warning content.
447 importWarning: function(data) {
448 this.importWarnings_.push(data);
450 // Only log each warning type once. We may want to add some kind of
451 // flag to allow reporting all importer warnings.
452 if (this.reportedImportWarnings_[data.type] === true)
455 console.warn(data.message);
456 this.reportedImportWarnings_[data.type] = true;
459 get hasImportWarnings() {
460 return (this.importWarnings_.length > 0);
463 get importWarnings() {
464 return this.importWarnings_;
468 * Iterates all events in the model and calls callback on each event.
469 * @param {function(event)} callback The callback called for every event.
471 iterateAllEvents: function(callback) {
472 this.instantEvents.forEach(callback);
474 this.kernel.iterateAllEvents(callback);
476 for (var pid in this.processes)
477 this.processes[pid].iterateAllEvents(callback);
482 * Importer for empty strings and arrays.
485 function TraceModelEmptyImporter(events) {
486 this.importPriority = 0;
489 TraceModelEmptyImporter.canImport = function(eventData) {
490 if (eventData instanceof Array && eventData.length == 0)
492 if (typeof(eventData) === 'string' || eventData instanceof String) {
493 return eventData.length == 0;
498 TraceModelEmptyImporter.prototype = {
499 __proto__: Importer.prototype
502 TraceModel.registerImporter(TraceModelEmptyImporter);
505 TraceModel: TraceModel