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="/tvcm.html">
8 <link rel="import" href="/tracing/filter.html">
9 <link rel="import" href="/tracing/importer/importer.html">
10 <link rel="import" href="/tracing/importer/task.html">
11 <link rel="import" href="/tracing/trace_model/kernel.html">
12 <link rel="import" href="/tracing/trace_model/process.html">
13 <link rel="import" href="/tracing/trace_model/sample.html">
14 <link rel="import" href="/tracing/trace_model/stack_frame.html">
15 <link rel="import" href="/tvcm/range.html">
16 <link rel="import" href="/tvcm/events.html">
17 <link rel="import" href="/tvcm/interval_tree.html">
18 <link rel="import" href="/tvcm/ui/overlay.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 tvcm.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 tvcm.Range();
59 this.instantEvents = [];
62 this.stackFrames = {};
65 this.flowIntervalTree = new tvcm.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__: tvcm.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, tvcm.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 if (!importerConstructor)
241 'Could not find an importer for the provided eventData.');
243 var importer = new importerConstructor(
249 * Imports the provided traces into the model. The eventData type
250 * is undefined and will be passed to all the importers registered
251 * via TraceModel.registerImporter. The first importer that returns true
252 * for canImport(events) will be used to import the events.
254 * The primary trace is provided via the eventData variable. If multiple
255 * traces are to be imported, specify the first one as events, and the
256 * remainder in the opt_additionalEventData array.
258 * @param {Array} traces An array of eventData to be imported. Each
259 * eventData should correspond to a single trace file and will be handled by
260 * a separate importer.
261 * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero.
263 * @param {bool=} opt_pruneEmptyContainers Whether to prune empty
264 * containers. Defaults to true.
265 * @param {Function=} opt_customizeModelCallback Callback called after
266 * importers run in which more data can be added to the model, before it is
269 importTraces: function(traces,
270 opt_shiftWorldToZero,
271 opt_pruneEmptyContainers,
272 opt_customizeModelCallback) {
273 var progressMeter = {
274 update: function(msg) {}
276 var task = this.createImportTracesTask(
279 opt_shiftWorldToZero,
280 opt_pruneEmptyContainers,
281 opt_customizeModelCallback);
282 tracing.importer.Task.RunSynchronously(task);
286 * Imports a trace with the usual options from importTraces, but
287 * does so using idle callbacks, putting up an import dialog
288 * during the import process.
290 importTracesWithProgressDialog: function(traces,
291 opt_shiftWorldToZero,
292 opt_pruneEmptyContainers,
293 opt_customizeModelCallback) {
294 var overlay = tvcm.ui.Overlay();
295 overlay.title = 'Importing...';
296 overlay.userCanClose = false;
297 overlay.msgEl = document.createElement('div');
298 overlay.appendChild(overlay.msgEl);
299 overlay.msgEl.style.margin = '20px';
300 overlay.update = function(msg) {
301 this.msgEl.textContent = msg;
303 overlay.visible = true;
305 var task = this.createImportTracesTask(
308 opt_shiftWorldToZero,
309 opt_pruneEmptyContainers,
310 opt_customizeModelCallback);
311 var promise = tracing.importer.Task.RunWhenIdle(task);
314 overlay.visible = false;
316 overlay.visible = false;
322 * Creates a task that will import the provided traces into the model,
323 * updating the progressMeter as it goes. Parameters are as defined in
326 createImportTracesTask: function(progressMeter,
328 opt_shiftWorldToZero,
329 opt_pruneEmptyContainers,
330 opt_customizeModelCallback) {
332 throw new Error('Already importing.');
333 if (opt_shiftWorldToZero === undefined)
334 opt_shiftWorldToZero = true;
335 if (opt_pruneEmptyContainers === undefined)
336 opt_pruneEmptyContainers = true;
337 this.importing_ = true;
339 // Just some simple setup. It is useful to have a nop first
340 // task so that we can set up the lastTask = lastTask.after()
341 // pattern that follows.
342 var importTask = new tracing.importer.Task(function() {
343 progressMeter.update('I will now import your traces for you...');
345 var lastTask = importTask;
349 lastTask = lastTask.after(function() {
350 // Copy the traces array, we may mutate it.
351 traces = traces.slice(0);
352 progressMeter.update('Creating importers...');
353 // Figure out which importers to use.
354 for (var i = 0; i < traces.length; ++i)
355 importers.push(this.createImporter_(traces[i]));
357 // Some traces have other traces inside them. Before doing the full
358 // import, ask the importer if it has any subtraces, and if so, create
359 // importers for them, also.
360 for (var i = 0; i < importers.length; i++) {
361 var subtraces = importers[i].extractSubtraces();
362 for (var j = 0; j < subtraces.length; j++) {
363 traces.push(subtraces[j]);
364 importers.push(this.createImporter_(subtraces[j]));
368 // Sort them on priority. This ensures importing happens in a
369 // predictable order, e.g. linux_perf_importer before
370 // trace_event_importer.
371 importers.sort(function(x, y) {
372 return x.importPriority - y.importPriority;
377 lastTask = lastTask.after(function(task) {
378 importers.forEach(function(importer, index) {
379 task.subTask(function() {
380 progressMeter.update(
381 'Importing ' + (index + 1) + ' of ' + importers.length);
382 importer.importEvents(index > 0);
387 // Run the cusomizeModelCallback if needed.
388 if (opt_customizeModelCallback) {
389 lastTask = lastTask.after(function(task) {
390 opt_customizeModelCallback(this);
395 lastTask = lastTask.after(function(task) {
396 importers.forEach(function(importer, index) {
397 progressMeter.update(
398 'Importing sample data ' + (index + 1) + '/' + importers.length);
399 importer.importSampleData();
403 // Autoclose open slices and create subSlices.
404 lastTask = lastTask.after(function() {
405 progressMeter.update('Autoclosing open slices...');
407 this.samples.sort(function(x, y) {
408 return x.start - y.start;
412 this.kernel.autoCloseOpenSlices(this.bounds.max);
413 for (var pid in this.processes)
414 this.processes[pid].autoCloseOpenSlices(this.bounds.max);
416 this.kernel.createSubSlices();
417 for (var pid in this.processes)
418 this.processes[pid].createSubSlices();
422 lastTask = lastTask.after(function(task) {
423 importers.forEach(function(importer, index) {
424 progressMeter.update(
425 'Finalizing import ' + (index + 1) + '/' + importers.length);
426 importer.finalizeImport();
431 lastTask = lastTask.after(function() {
432 progressMeter.update('Initializing objects (step 1/2)...');
433 for (var pid in this.processes)
434 this.processes[pid].preInitializeObjects();
437 // Prune empty containers.
438 if (opt_pruneEmptyContainers) {
439 lastTask = lastTask.after(function() {
440 progressMeter.update('Pruning empty containers...');
441 this.kernel.pruneEmptyContainers();
442 for (var pid in this.processes) {
443 this.processes[pid].pruneEmptyContainers();
448 // Merge kernel and userland slices on each thread.
449 lastTask = lastTask.after(function() {
450 progressMeter.update('Merging kernel with userland...');
451 for (var pid in this.processes)
452 this.processes[pid].mergeKernelWithUserland();
455 lastTask = lastTask.after(function() {
456 progressMeter.update('Computing final world bounds...');
458 this.updateCategories_();
460 if (opt_shiftWorldToZero)
461 this.shiftWorldToZero();
464 // Build the flow event interval tree.
465 lastTask = lastTask.after(function() {
466 progressMeter.update('Building flow event map...');
467 for (var i = 0; i < this.flowEvents.length; ++i) {
468 var pair = this.flowEvents[i];
469 this.flowIntervalTree.insert(pair[0], pair[1]);
471 this.flowIntervalTree.updateHighValues();
475 lastTask = lastTask.after(function() {
476 progressMeter.update('Joining object refs...');
477 for (var i = 0; i < importers.length; i++)
478 importers[i].joinRefs();
481 // Delete any undeleted objects.
482 lastTask = lastTask.after(function() {
483 progressMeter.update('Cleaning up undeleted objects...');
484 for (var pid in this.processes)
485 this.processes[pid].autoDeleteObjects(this.bounds.max);
489 lastTask = lastTask.after(function() {
490 progressMeter.update('Initializing objects (step 2/2)...');
491 for (var pid in this.processes)
492 this.processes[pid].initializeObjects();
496 lastTask.after(function() {
497 this.importing_ = false;
503 * @param {Object} data The import warning data. Data must provide two
504 * accessors: type, message. The types are used to determine if we
505 * should output the message, we'll only output one message of each type.
506 * The message is the actual warning content.
508 importWarning: function(data) {
509 this.importWarnings_.push(data);
511 // Only log each warning type once. We may want to add some kind of
512 // flag to allow reporting all importer warnings.
513 if (this.reportedImportWarnings_[data.type] === true)
516 console.warn(data.message);
517 this.reportedImportWarnings_[data.type] = true;
520 get hasImportWarnings() {
521 return (this.importWarnings_.length > 0);
524 get importWarnings() {
525 return this.importWarnings_;
529 * Iterates all events in the model and calls callback on each event.
530 * @param {function(event)} callback The callback called for every event.
532 iterateAllEvents: function(callback, opt_this) {
533 this.instantEvents.forEach(callback, opt_this);
535 this.kernel.iterateAllEvents(callback, opt_this);
537 for (var pid in this.processes)
538 this.processes[pid].iterateAllEvents(callback, opt_this);
540 this.samples.forEach(callback, opt_this);
544 * Some objects in the model can persist their state in TraceModelSettings.
546 * This iterates through them.
548 iterateAllPersistableObjects: function(cb) {
549 this.kernel.iterateAllPersistableObjects(cb);
550 for (var pid in this.processes)
551 this.processes[pid].iterateAllPersistableObjects(cb);
556 * Importer for empty strings and arrays.
559 function TraceModelEmptyImporter(events) {
560 this.importPriority = 0;
563 TraceModelEmptyImporter.canImport = function(eventData) {
564 if (eventData instanceof Array && eventData.length == 0)
566 if (typeof(eventData) === 'string' || eventData instanceof String) {
567 return eventData.length == 0;
572 TraceModelEmptyImporter.prototype = {
573 __proto__: Importer.prototype
576 TraceModel.registerImporter(TraceModelEmptyImporter);
579 TraceModel: TraceModel