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 tvcm.require('tvcm.range');
24 tvcm.require('tvcm.events');
25 tvcm.require('tvcm.interval_tree');
26 tvcm.require('tracing.importer.importer');
27 tvcm.require('tracing.importer.task');
28 tvcm.require('tracing.trace_model.kernel');
29 tvcm.require('tracing.trace_model.process');
30 tvcm.require('tracing.trace_model.sample');
31 tvcm.require('tracing.trace_model.stack_frame');
32 tvcm.require('tracing.filter');
33 tvcm.require('tvcm.ui.overlay');
35 tvcm.exportTo('tracing', function() {
37 var Importer = tracing.importer.Importer;
38 var Process = tracing.trace_model.Process;
39 var Kernel = tracing.trace_model.Kernel;
42 * Builds a model from an array of TraceEvent objects.
43 * @param {Object=} opt_eventData Data from a single trace to be imported into
44 * the new model. See TraceModel.importTraces for details on how to
45 * import multiple traces at once.
46 * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero.
50 function TraceModel(opt_eventData, opt_shiftWorldToZero) {
51 this.kernel = new Kernel(this);
55 this.bounds = new tvcm.Range();
56 this.instantEvents = [];
59 this.stackFrames = {};
62 this.flowIntervalTree = new tvcm.IntervalTree(
63 function(s) { return s.start; },
64 function(e) { return e.start; });
66 this.importWarnings_ = [];
67 this.reportedImportWarnings_ = {};
70 this.importTraces([opt_eventData], opt_shiftWorldToZero);
73 TraceModel.importerConstructors_ = [];
76 * Registers an importer. All registered importers are considered
77 * when processing an import request.
79 * @param {Function} importerConstructor The importer's constructor function.
81 TraceModel.registerImporter = function(importerConstructor) {
82 TraceModel.importerConstructors_.push(importerConstructor);
85 TraceModel.prototype = {
86 __proto__: tvcm.EventTarget.prototype,
90 for (var p in this.processes)
96 * @return {Process} Gets a TimelineProcess for a specified pid or
97 * creates one if it does not exist.
99 getOrCreateProcess: function(pid) {
100 if (!this.processes[pid])
101 this.processes[pid] = new Process(this, pid);
102 return this.processes[pid];
105 pushInstantEvent: function(instantEvent) {
106 this.instantEvents.push(instantEvent);
109 addStackFrame: function(stackFrame) {
110 if (this.stackFrames[stackFrame.id])
111 throw new Error('Stack frame already exists');
112 this.stackFrames[stackFrame.id] = stackFrame;
117 * Generates the set of categories from the slices and counters.
119 updateCategories_: function() {
120 var categoriesDict = {};
121 this.kernel.addCategoriesToDict(categoriesDict);
122 for (var pid in this.processes)
123 this.processes[pid].addCategoriesToDict(categoriesDict);
125 this.categories = [];
126 for (var category in categoriesDict)
128 this.categories.push(category);
131 updateBounds: function() {
134 this.kernel.updateBounds();
135 this.bounds.addRange(this.kernel.bounds);
137 for (var pid in this.processes) {
138 this.processes[pid].updateBounds();
139 this.bounds.addRange(this.processes[pid].bounds);
143 shiftWorldToZero: function() {
144 if (this.bounds.isEmpty)
146 var timeBase = this.bounds.min;
147 this.kernel.shiftTimestampsForward(-timeBase);
149 for (var id in this.instantEvents)
150 this.instantEvents[id].start -= timeBase;
152 for (var pid in this.processes)
153 this.processes[pid].shiftTimestampsForward(-timeBase);
155 for (var i = 0; i < this.samples.length; i++) {
156 var sample = this.samples[i];
157 sample.start -= timeBase;
163 getAllThreads: function() {
165 for (var tid in this.kernel.threads) {
166 threads.push(process.threads[tid]);
168 for (var pid in this.processes) {
169 var process = this.processes[pid];
170 for (var tid in process.threads) {
171 threads.push(process.threads[tid]);
178 * @return {Array} An array of all processes in the model.
180 getAllProcesses: function() {
182 for (var pid in this.processes)
183 processes.push(this.processes[pid]);
188 * @return {Array} An array of all the counters in the model.
190 getAllCounters: function() {
193 counters, tvcm.dictionaryValues(this.kernel.counters));
194 for (var pid in this.processes) {
195 var process = this.processes[pid];
196 for (var tid in process.counters) {
197 counters.push(process.counters[tid]);
204 * @param {String} The name of the thread to find.
205 * @return {Array} An array of all the matched threads.
207 findAllThreadsNamed: function(name) {
208 var namedThreads = [];
209 namedThreads.push.apply(
211 this.kernel.findAllThreadsNamed(name));
212 for (var pid in this.processes) {
213 namedThreads.push.apply(
215 this.processes[pid].findAllThreadsNamed(name));
220 createImporter_: function(eventData) {
221 var importerConstructor;
222 for (var i = 0; i < TraceModel.importerConstructors_.length; ++i) {
223 if (TraceModel.importerConstructors_[i].canImport(eventData)) {
224 importerConstructor = TraceModel.importerConstructors_[i];
228 if (!importerConstructor)
230 'Could not find an importer for the provided eventData.');
232 var importer = new importerConstructor(
238 * Imports the provided traces into the model. The eventData type
239 * is undefined and will be passed to all the importers registered
240 * via TraceModel.registerImporter. The first importer that returns true
241 * for canImport(events) will be used to import the events.
243 * The primary trace is provided via the eventData variable. If multiple
244 * traces are to be imported, specify the first one as events, and the
245 * remainder in the opt_additionalEventData array.
247 * @param {Array} traces An array of eventData to be imported. Each
248 * eventData should correspond to a single trace file and will be handled by
249 * a separate importer.
250 * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero.
252 * @param {bool=} opt_pruneEmptyContainers Whether to prune empty
253 * containers. Defaults to true.
254 * @param {Function=} opt_customizeModelCallback Callback called after
255 * importers run in which more data can be added to the model, before it is
258 importTraces: function(traces,
259 opt_shiftWorldToZero,
260 opt_pruneEmptyContainers,
261 opt_customizeModelCallback) {
262 var progressMeter = {
263 update: function(msg) {}
265 var task = this.createImportTracesTask(
268 opt_shiftWorldToZero,
269 opt_pruneEmptyContainers,
270 opt_customizeModelCallback);
271 tracing.importer.Task.RunSynchronously(task);
275 * Imports a trace with the usual options from importTraces, but
276 * does so using idle callbacks, putting up an import dialog
277 * during the import process.
279 importTracesWithProgressDialog: function(traces,
280 opt_shiftWorldToZero,
281 opt_pruneEmptyContainers,
282 opt_customizeModelCallback) {
283 var overlay = tvcm.ui.Overlay();
284 overlay.title = 'Importing...';
285 overlay.userCanClose = false;
286 overlay.msgEl = document.createElement('div');
287 overlay.appendChild(overlay.msgEl);
288 overlay.msgEl.style.margin = '20px';
289 overlay.update = function(msg) {
290 this.msgEl.textContent = msg;
292 overlay.visible = true;
294 var task = this.createImportTracesTask(
297 opt_shiftWorldToZero,
298 opt_pruneEmptyContainers,
299 opt_customizeModelCallback);
300 var promise = tracing.importer.Task.RunWhenIdle(task);
303 overlay.visible = false;
305 overlay.visible = false;
311 * Creates a task that will import the provided traces into the model,
312 * updating the progressMeter as it goes. Parameters are as defined in
315 createImportTracesTask: function(progressMeter,
317 opt_shiftWorldToZero,
318 opt_pruneEmptyContainers,
319 opt_customizeModelCallback) {
321 throw new Error('Already importing.');
322 if (opt_shiftWorldToZero === undefined)
323 opt_shiftWorldToZero = true;
324 if (opt_pruneEmptyContainers === undefined)
325 opt_pruneEmptyContainers = true;
326 this.importing_ = true;
328 // Just some simple setup. It is useful to have a nop first
329 // task so that we can set up the lastTask = lastTask.after()
330 // pattern that follows.
331 var importTask = new tracing.importer.Task(function() {
332 progressMeter.update('I will now import your traces for you...');
334 var lastTask = importTask;
338 lastTask = lastTask.after(function() {
339 // Copy the traces array, we may mutate it.
340 traces = traces.slice(0);
341 progressMeter.update('Creating importers...');
342 // Figure out which importers to use.
343 for (var i = 0; i < traces.length; ++i)
344 importers.push(this.createImporter_(traces[i]));
346 // Some traces have other traces inside them. Before doing the full
347 // import, ask the importer if it has any subtraces, and if so, create
348 // importers for them, also.
349 for (var i = 0; i < importers.length; i++) {
350 var subtraces = importers[i].extractSubtraces();
351 for (var j = 0; j < subtraces.length; j++) {
352 traces.push(subtraces[j]);
353 importers.push(this.createImporter_(subtraces[j]));
357 // Sort them on priority. This ensures importing happens in a
358 // predictable order, e.g. linux_perf_importer before
359 // trace_event_importer.
360 importers.sort(function(x, y) {
361 return x.importPriority - y.importPriority;
366 lastTask = lastTask.after(function(task) {
367 importers.forEach(function(importer, index) {
368 task.subTask(function() {
369 progressMeter.update(
370 'Importing ' + (index + 1) + ' of ' + importers.length);
371 importer.importEvents(index > 0);
376 // Run the cusomizeModelCallback if needed.
377 if (opt_customizeModelCallback) {
378 lastTask = lastTask.after(function(task) {
379 opt_customizeModelCallback(this);
384 lastTask = lastTask.after(function(task) {
385 importers.forEach(function(importer, index) {
386 progressMeter.update(
387 'Importing sample data ' + (index + 1) + '/' + importers.length);
388 importer.importSampleData();
392 // Autoclose open slices and create subSlices.
393 lastTask = lastTask.after(function() {
394 progressMeter.update('Autoclosing open slices...');
396 this.samples.sort(function(x, y) {
401 this.kernel.autoCloseOpenSlices(this.bounds.max);
402 for (var pid in this.processes)
403 this.processes[pid].autoCloseOpenSlices(this.bounds.max);
405 this.kernel.createSubSlices();
406 for (var pid in this.processes)
407 this.processes[pid].createSubSlices();
411 lastTask = lastTask.after(function(task) {
412 importers.forEach(function(importer, index) {
413 progressMeter.update(
414 'Finalizing import ' + (index + 1) + '/' + importers.length);
415 importer.finalizeImport();
420 lastTask = lastTask.after(function() {
421 progressMeter.update('Initializing objects (step 1/2)...');
422 for (var pid in this.processes)
423 this.processes[pid].preInitializeObjects();
426 // Prune empty containers.
427 if (opt_pruneEmptyContainers) {
428 lastTask = lastTask.after(function() {
429 progressMeter.update('Pruning empty containers...');
430 this.kernel.pruneEmptyContainers();
431 for (var pid in this.processes) {
432 this.processes[pid].pruneEmptyContainers();
437 // Merge kernel and userland slices on each thread.
438 lastTask = lastTask.after(function() {
439 progressMeter.update('Merging kernel with userland...');
440 for (var pid in this.processes)
441 this.processes[pid].mergeKernelWithUserland();
444 lastTask = lastTask.after(function() {
445 progressMeter.update('Computing final world bounds...');
447 this.updateCategories_();
449 if (opt_shiftWorldToZero)
450 this.shiftWorldToZero();
453 // Build the flow event interval tree.
454 lastTask = lastTask.after(function() {
455 progressMeter.update('Building flow event map...');
456 for (var i = 0; i < this.flowEvents.length; ++i) {
457 var pair = this.flowEvents[i];
458 this.flowIntervalTree.insert(pair[0], pair[1]);
460 this.flowIntervalTree.updateHighValues();
464 lastTask = lastTask.after(function() {
465 progressMeter.update('Joining object refs...');
466 for (var i = 0; i < importers.length; i++)
467 importers[i].joinRefs();
470 // Delete any undeleted objects.
471 lastTask = lastTask.after(function() {
472 progressMeter.update('Cleaning up undeleted objects...');
473 for (var pid in this.processes)
474 this.processes[pid].autoDeleteObjects(this.bounds.max);
478 lastTask = lastTask.after(function() {
479 progressMeter.update('Initializing objects (step 2/2)...');
480 for (var pid in this.processes)
481 this.processes[pid].initializeObjects();
485 lastTask.after(function() {
486 this.importing_ = false;
492 * @param {Object} data The import warning data. Data must provide two
493 * accessors: type, message. The types are used to determine if we
494 * should output the message, we'll only output one message of each type.
495 * The message is the actual warning content.
497 importWarning: function(data) {
498 this.importWarnings_.push(data);
500 // Only log each warning type once. We may want to add some kind of
501 // flag to allow reporting all importer warnings.
502 if (this.reportedImportWarnings_[data.type] === true)
505 console.warn(data.message);
506 this.reportedImportWarnings_[data.type] = true;
509 get hasImportWarnings() {
510 return (this.importWarnings_.length > 0);
513 get importWarnings() {
514 return this.importWarnings_;
518 * Iterates all events in the model and calls callback on each event.
519 * @param {function(event)} callback The callback called for every event.
521 iterateAllEvents: function(callback, opt_this) {
522 this.instantEvents.forEach(callback, opt_this);
524 this.kernel.iterateAllEvents(callback, opt_this);
526 for (var pid in this.processes)
527 this.processes[pid].iterateAllEvents(callback, opt_this);
529 this.samples.forEach(callback, opt_this);
534 * Importer for empty strings and arrays.
537 function TraceModelEmptyImporter(events) {
538 this.importPriority = 0;
541 TraceModelEmptyImporter.canImport = function(eventData) {
542 if (eventData instanceof Array && eventData.length == 0)
544 if (typeof(eventData) === 'string' || eventData instanceof String) {
545 return eventData.length == 0;
550 TraceModelEmptyImporter.prototype = {
551 __proto__: Importer.prototype
554 TraceModel.registerImporter(TraceModelEmptyImporter);
557 TraceModel: TraceModel