3 Copyright (c) 2012 The Chromium Authors. All rights reserved.
4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file.
7 <link rel="import" href="/base.html">
8 <link rel="import" href="/base/range.html">
9 <link rel="import" href="/base/events.html">
10 <link rel="import" href="/base/interval_tree.html">
11 <link rel="import" href="/base/ui/overlay.html">
12 <link rel="import" href="/tracing/filter.html">
13 <link rel="import" href="/tracing/importer/importer.html">
14 <link rel="import" href="/tracing/importer/task.html">
15 <link rel="import" href="/tracing/trace_model/kernel.html">
16 <link rel="import" href="/tracing/trace_model/process.html">
17 <link rel="import" href="/tracing/trace_model/sample.html">
18 <link rel="import" href="/tracing/trace_model/stack_frame.html">
24 * @fileoverview TraceModel is a parsed representation of the
25 * TraceEvents obtained from base/trace_event in which the begin-end
26 * tokens are converted into a hierarchy of processes, threads,
27 * subrows, and slices.
29 * The building block of the model is a slice. A slice is roughly
30 * equivalent to function call executing on a specific thread. As a
31 * result, slices may have one or more subslices.
33 * A thread contains one or more subrows of slices. Row 0 corresponds to
34 * the "root" slices, e.g. the topmost slices. Row 1 contains slices that
35 * are nested 1 deep in the stack, and so on. We use these subrows to draw
39 tv.exportTo('tracing', function() {
40 var Importer = tracing.importer.Importer;
41 var Process = tracing.trace_model.Process;
42 var Kernel = tracing.trace_model.Kernel;
45 * Builds a model from an array of TraceEvent objects.
46 * @param {Object=} opt_eventData Data from a single trace to be imported into
47 * the new model. See TraceModel.importTraces for details on how to
48 * import multiple traces at once.
49 * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero.
53 function TraceModel(opt_eventData, opt_shiftWorldToZero) {
54 this.kernel = new Kernel(this);
58 this.bounds = new tv.Range();
59 this.instantEvents = [];
62 this.stackFrames = {};
65 this.flowIntervalTree = new tv.IntervalTree(
66 function(s) { return s.start; },
67 function(e) { return e.start; });
69 this.importWarnings_ = [];
70 this.reportedImportWarnings_ = {};
73 this.importTraces([opt_eventData], opt_shiftWorldToZero);
76 TraceModel.importerConstructors_ = [];
79 * Registers an importer. All registered importers are considered
80 * when processing an import request.
82 * @param {Function} importerConstructor The importer's constructor function.
84 TraceModel.registerImporter = function(importerConstructor) {
85 TraceModel.importerConstructors_.push(importerConstructor);
88 TraceModel.prototype = {
89 __proto__: tv.EventTarget.prototype,
93 for (var p in this.processes)
99 * @return {Process} Gets a TimelineProcess for a specified pid. Returns
100 * undefined if the process doesn't exist.
102 getProcess: function(pid) {
103 return this.processes[pid];
107 * @return {Process} Gets a TimelineProcess for a specified pid or
108 * creates one if it does not exist.
110 getOrCreateProcess: function(pid) {
111 if (!this.processes[pid])
112 this.processes[pid] = new Process(this, pid);
113 return this.processes[pid];
116 pushInstantEvent: function(instantEvent) {
117 this.instantEvents.push(instantEvent);
120 addStackFrame: function(stackFrame) {
121 if (this.stackFrames[stackFrame.id])
122 throw new Error('Stack frame already exists');
123 this.stackFrames[stackFrame.id] = stackFrame;
128 * Generates the set of categories from the slices and counters.
130 updateCategories_: function() {
131 var categoriesDict = {};
132 this.kernel.addCategoriesToDict(categoriesDict);
133 for (var pid in this.processes)
134 this.processes[pid].addCategoriesToDict(categoriesDict);
136 this.categories = [];
137 for (var category in categoriesDict)
139 this.categories.push(category);
142 updateBounds: function() {
145 this.kernel.updateBounds();
146 this.bounds.addRange(this.kernel.bounds);
148 for (var pid in this.processes) {
149 this.processes[pid].updateBounds();
150 this.bounds.addRange(this.processes[pid].bounds);
154 shiftWorldToZero: function() {
155 if (this.bounds.isEmpty)
157 var timeBase = this.bounds.min;
158 this.kernel.shiftTimestampsForward(-timeBase);
160 for (var id in this.instantEvents)
161 this.instantEvents[id].start -= timeBase;
163 for (var pid in this.processes)
164 this.processes[pid].shiftTimestampsForward(-timeBase);
166 for (var i = 0; i < this.samples.length; i++) {
167 var sample = this.samples[i];
168 sample.start -= timeBase;
174 getAllThreads: function() {
176 for (var tid in this.kernel.threads) {
177 threads.push(process.threads[tid]);
179 for (var pid in this.processes) {
180 var process = this.processes[pid];
181 for (var tid in process.threads) {
182 threads.push(process.threads[tid]);
189 * @return {Array} An array of all processes in the model.
191 getAllProcesses: function() {
193 for (var pid in this.processes)
194 processes.push(this.processes[pid]);
199 * @return {Array} An array of all the counters in the model.
201 getAllCounters: function() {
204 counters, tv.dictionaryValues(this.kernel.counters));
205 for (var pid in this.processes) {
206 var process = this.processes[pid];
207 for (var tid in process.counters) {
208 counters.push(process.counters[tid]);
215 * @param {String} The name of the thread to find.
216 * @return {Array} An array of all the matched threads.
218 findAllThreadsNamed: function(name) {
219 var namedThreads = [];
220 namedThreads.push.apply(
222 this.kernel.findAllThreadsNamed(name));
223 for (var pid in this.processes) {
224 namedThreads.push.apply(
226 this.processes[pid].findAllThreadsNamed(name));
231 createImporter_: function(eventData) {
232 var importerConstructor;
233 for (var i = 0; i < TraceModel.importerConstructors_.length; ++i) {
234 if (TraceModel.importerConstructors_[i].canImport(eventData)) {
235 importerConstructor = TraceModel.importerConstructors_[i];
239 // TODO(kphanee): Throwing same Error at 2 places. Lets try to avoid it!
240 if (!importerConstructor)
242 'Could not find an importer for the provided eventData.');
244 var importer = new importerConstructor(
250 * Imports the provided traces into the model. The eventData type
251 * is undefined and will be passed to all the importers registered
252 * via TraceModel.registerImporter. The first importer that returns true
253 * for canImport(events) will be used to import the events.
255 * The primary trace is provided via the eventData variable. If multiple
256 * traces are to be imported, specify the first one as events, and the
257 * remainder in the opt_additionalEventData array.
259 * @param {Array} traces An array of eventData to be imported. Each
260 * eventData should correspond to a single trace file and will be handled by
261 * a separate importer.
262 * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero.
264 * @param {bool=} opt_pruneEmptyContainers Whether to prune empty
265 * containers. Defaults to true.
266 * @param {Function=} opt_customizeModelCallback Callback called after
267 * importers run in which more data can be added to the model, before it is
270 importTraces: function(traces,
271 opt_shiftWorldToZero,
272 opt_pruneEmptyContainers,
273 opt_customizeModelCallback) {
274 var progressMeter = {
275 update: function(msg) {}
277 var task = this.createImportTracesTask(
280 opt_shiftWorldToZero,
281 opt_pruneEmptyContainers,
282 opt_customizeModelCallback);
283 tracing.importer.Task.RunSynchronously(task);
287 * Imports a trace with the usual options from importTraces, but
288 * does so using idle callbacks, putting up an import dialog
289 * during the import process.
291 importTracesWithProgressDialog: function(traces,
292 opt_shiftWorldToZero,
293 opt_pruneEmptyContainers,
294 opt_customizeModelCallback) {
295 var overlay = tv.ui.Overlay();
296 overlay.title = 'Importing...';
297 overlay.userCanClose = false;
298 overlay.msgEl = document.createElement('div');
299 overlay.appendChild(overlay.msgEl);
300 overlay.msgEl.style.margin = '20px';
301 overlay.update = function(msg) {
302 this.msgEl.textContent = msg;
304 overlay.visible = true;
306 var task = this.createImportTracesTask(
309 opt_shiftWorldToZero,
310 opt_pruneEmptyContainers,
311 opt_customizeModelCallback);
312 var promise = tracing.importer.Task.RunWhenIdle(task);
315 overlay.visible = false;
317 overlay.visible = false;
322 hasEventDataDecoder_: function(importers) {
323 if (importers.length === 0)
326 for (var i = 0; i < importers.length; ++i) {
327 if (!importers[i].isTraceDataContainer())
334 * Creates a task that will import the provided traces into the model,
335 * updating the progressMeter as it goes. Parameters are as defined in
338 createImportTracesTask: function(progressMeter,
340 opt_shiftWorldToZero,
341 opt_pruneEmptyContainers,
342 opt_customizeModelCallback) {
344 throw new Error('Already importing.');
345 if (opt_shiftWorldToZero === undefined)
346 opt_shiftWorldToZero = true;
347 if (opt_pruneEmptyContainers === undefined)
348 opt_pruneEmptyContainers = true;
349 this.importing_ = true;
351 // Just some simple setup. It is useful to have a nop first
352 // task so that we can set up the lastTask = lastTask.after()
353 // pattern that follows.
354 var importTask = new tracing.importer.Task(function() {
355 progressMeter.update('I will now import your traces for you...');
357 var lastTask = importTask;
361 lastTask = lastTask.after(function() {
362 // Copy the traces array, we may mutate it.
363 traces = traces.slice(0);
364 progressMeter.update('Creating importers...');
365 // Figure out which importers to use.
366 for (var i = 0; i < traces.length; ++i)
367 importers.push(this.createImporter_(traces[i]));
369 // Some traces have other traces inside them. Before doing the full
370 // import, ask the importer if it has any subtraces, and if so, create
371 // importers for them, also.
372 for (var i = 0; i < importers.length; i++) {
373 var subtraces = importers[i].extractSubtraces();
374 for (var j = 0; j < subtraces.length; j++) {
376 traces.push(subtraces[j]);
377 importers.push(this.createImporter_(subtraces[j]));
379 // TODO(kphanee): Log the subtrace file which has failed.
380 console.warn(error.name + ': ' + error.message);
386 if (traces.length && !this.hasEventDataDecoder_(importers))
387 throw new Error('Could not find an importer for the provided eventData.');
389 // Sort them on priority. This ensures importing happens in a
390 // predictable order, e.g. linux_perf_importer before
391 // trace_event_importer.
392 importers.sort(function(x, y) {
393 return x.importPriority - y.importPriority;
398 lastTask = lastTask.after(function(task) {
399 importers.forEach(function(importer, index) {
400 task.subTask(function() {
401 progressMeter.update(
402 'Importing ' + (index + 1) + ' of ' + importers.length);
403 importer.importEvents(index > 0);
408 // Run the cusomizeModelCallback if needed.
409 if (opt_customizeModelCallback) {
410 lastTask = lastTask.after(function(task) {
411 opt_customizeModelCallback(this);
416 lastTask = lastTask.after(function(task) {
417 importers.forEach(function(importer, index) {
418 progressMeter.update(
419 'Importing sample data ' + (index + 1) + '/' + importers.length);
420 importer.importSampleData();
424 // Autoclose open slices and create subSlices.
425 lastTask = lastTask.after(function() {
426 progressMeter.update('Autoclosing open slices...');
428 this.samples.sort(function(x, y) {
429 return x.start - y.start;
433 this.kernel.autoCloseOpenSlices(this.bounds.max);
434 for (var pid in this.processes)
435 this.processes[pid].autoCloseOpenSlices(this.bounds.max);
437 this.kernel.createSubSlices();
438 for (var pid in this.processes)
439 this.processes[pid].createSubSlices();
443 lastTask = lastTask.after(function(task) {
444 importers.forEach(function(importer, index) {
445 progressMeter.update(
446 'Finalizing import ' + (index + 1) + '/' + importers.length);
447 importer.finalizeImport();
452 lastTask = lastTask.after(function() {
453 progressMeter.update('Initializing objects (step 1/2)...');
454 for (var pid in this.processes)
455 this.processes[pid].preInitializeObjects();
458 // Prune empty containers.
459 if (opt_pruneEmptyContainers) {
460 lastTask = lastTask.after(function() {
461 progressMeter.update('Pruning empty containers...');
462 this.kernel.pruneEmptyContainers();
463 for (var pid in this.processes) {
464 this.processes[pid].pruneEmptyContainers();
469 // Merge kernel and userland slices on each thread.
470 lastTask = lastTask.after(function() {
471 progressMeter.update('Merging kernel with userland...');
472 for (var pid in this.processes)
473 this.processes[pid].mergeKernelWithUserland();
476 lastTask = lastTask.after(function() {
477 progressMeter.update('Computing final world bounds...');
479 this.updateCategories_();
481 if (opt_shiftWorldToZero)
482 this.shiftWorldToZero();
485 // Build the flow event interval tree.
486 lastTask = lastTask.after(function() {
487 progressMeter.update('Building flow event map...');
488 for (var i = 0; i < this.flowEvents.length; ++i) {
489 var pair = this.flowEvents[i];
490 this.flowIntervalTree.insert(pair[0], pair[1]);
492 this.flowIntervalTree.updateHighValues();
496 lastTask = lastTask.after(function() {
497 progressMeter.update('Joining object refs...');
498 for (var i = 0; i < importers.length; i++)
499 importers[i].joinRefs();
502 // Delete any undeleted objects.
503 lastTask = lastTask.after(function() {
504 progressMeter.update('Cleaning up undeleted objects...');
505 for (var pid in this.processes)
506 this.processes[pid].autoDeleteObjects(this.bounds.max);
510 lastTask = lastTask.after(function() {
511 progressMeter.update('Initializing objects (step 2/2)...');
512 for (var pid in this.processes)
513 this.processes[pid].initializeObjects();
517 lastTask.after(function() {
518 this.importing_ = false;
524 * @param {Object} data The import warning data. Data must provide two
525 * accessors: type, message. The types are used to determine if we
526 * should output the message, we'll only output one message of each type.
527 * The message is the actual warning content.
529 importWarning: function(data) {
530 this.importWarnings_.push(data);
532 // Only log each warning type once. We may want to add some kind of
533 // flag to allow reporting all importer warnings.
534 if (this.reportedImportWarnings_[data.type] === true)
537 console.warn(data.message);
538 this.reportedImportWarnings_[data.type] = true;
541 get hasImportWarnings() {
542 return (this.importWarnings_.length > 0);
545 get importWarnings() {
546 return this.importWarnings_;
550 * Iterates all events in the model and calls callback on each event.
551 * @param {function(event)} callback The callback called for every event.
553 iterateAllEvents: function(callback, opt_this) {
554 this.instantEvents.forEach(callback, opt_this);
556 this.kernel.iterateAllEvents(callback, opt_this);
558 for (var pid in this.processes)
559 this.processes[pid].iterateAllEvents(callback, opt_this);
561 this.samples.forEach(callback, opt_this);
565 * Some objects in the model can persist their state in TraceModelSettings.
567 * This iterates through them.
569 iterateAllPersistableObjects: function(cb) {
570 this.kernel.iterateAllPersistableObjects(cb);
571 for (var pid in this.processes)
572 this.processes[pid].iterateAllPersistableObjects(cb);
577 * Importer for empty strings and arrays.
580 function TraceModelEmptyImporter(events) {
581 this.importPriority = 0;
584 TraceModelEmptyImporter.canImport = function(eventData) {
585 if (eventData instanceof Array && eventData.length == 0)
587 if (typeof(eventData) === 'string' || eventData instanceof String) {
588 return eventData.length == 0;
593 TraceModelEmptyImporter.prototype = {
594 __proto__: Importer.prototype
597 TraceModel.registerImporter(TraceModelEmptyImporter);
600 TraceModel: TraceModel