Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / src / tracing / trace_model.js
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  */
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.process');
29 tvcm.require('tracing.trace_model.kernel');
30 tvcm.require('tracing.filter');
31 tvcm.require('tvcm.ui.overlay');
32
33 tvcm.exportTo('tracing', function() {
34
35   var Importer = tracing.importer.Importer;
36   var Process = tracing.trace_model.Process;
37   var Kernel = tracing.trace_model.Kernel;
38
39   /**
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.
45    * Defaults to true.
46    * @constructor
47    */
48   function TraceModel(opt_eventData, opt_shiftWorldToZero) {
49     this.kernel = new Kernel(this);
50     this.processes = {};
51     this.metadata = [];
52     this.categories = [];
53     this.bounds = new tvcm.Range();
54     this.instantEvents = [];
55     this.flowEvents = [];
56
57     this.flowIntervalTree = new tvcm.IntervalTree(
58         function(s) { return s.start; },
59         function(e) { return e.start; });
60
61     this.importWarnings_ = [];
62     this.reportedImportWarnings_ = {};
63
64     if (opt_eventData)
65       this.importTraces([opt_eventData], opt_shiftWorldToZero);
66   }
67
68   TraceModel.importerConstructors_ = [];
69
70   /**
71    * Registers an importer. All registered importers are considered
72    * when processing an import request.
73    *
74    * @param {Function} importerConstructor The importer's constructor function.
75    */
76   TraceModel.registerImporter = function(importerConstructor) {
77     TraceModel.importerConstructors_.push(importerConstructor);
78   };
79
80   TraceModel.prototype = {
81     __proto__: tvcm.EventTarget.prototype,
82
83     get numProcesses() {
84       var n = 0;
85       for (var p in this.processes)
86         n++;
87       return n;
88     },
89
90     /**
91      * @return {Process} Gets a TimelineProcess for a specified pid or
92      * creates one if it does not exist.
93      */
94     getOrCreateProcess: function(pid) {
95       if (!this.processes[pid])
96         this.processes[pid] = new Process(this, pid);
97       return this.processes[pid];
98     },
99
100     pushInstantEvent: function(instantEvent) {
101       this.instantEvents.push(instantEvent);
102     },
103
104     /**
105      * Generates the set of categories from the slices and counters.
106      */
107     updateCategories_: function() {
108       var categoriesDict = {};
109       this.kernel.addCategoriesToDict(categoriesDict);
110       for (var pid in this.processes)
111         this.processes[pid].addCategoriesToDict(categoriesDict);
112
113       this.categories = [];
114       for (var category in categoriesDict)
115         if (category != '')
116           this.categories.push(category);
117     },
118
119     updateBounds: function() {
120       this.bounds.reset();
121
122       this.kernel.updateBounds();
123       this.bounds.addRange(this.kernel.bounds);
124
125       for (var pid in this.processes) {
126         this.processes[pid].updateBounds();
127         this.bounds.addRange(this.processes[pid].bounds);
128       }
129     },
130
131     shiftWorldToZero: function() {
132       if (this.bounds.isEmpty)
133         return;
134       var timeBase = this.bounds.min;
135       this.kernel.shiftTimestampsForward(-timeBase);
136
137       for (var id in this.instantEvents)
138         this.instantEvents[id].start -= timeBase;
139
140       for (var pid in this.processes)
141         this.processes[pid].shiftTimestampsForward(-timeBase);
142
143       this.updateBounds();
144     },
145
146     getAllThreads: function() {
147       var threads = [];
148       for (var tid in this.kernel.threads) {
149         threads.push(process.threads[tid]);
150       }
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]);
155         }
156       }
157       return threads;
158     },
159
160     /**
161      * @return {Array} An array of all processes in the model.
162      */
163     getAllProcesses: function() {
164       var processes = [];
165       for (var pid in this.processes)
166         processes.push(this.processes[pid]);
167       return processes;
168     },
169
170     /**
171      * @return {Array} An array of all the counters in the model.
172      */
173     getAllCounters: function() {
174       var counters = [];
175       counters.push.apply(
176           counters, tvcm.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]);
181         }
182       }
183       return counters;
184     },
185
186     /**
187      * @param {String} The name of the thread to find.
188      * @return {Array} An array of all the matched threads.
189      */
190     findAllThreadsNamed: function(name) {
191       var namedThreads = [];
192       namedThreads.push.apply(
193           namedThreads,
194           this.kernel.findAllThreadsNamed(name));
195       for (var pid in this.processes) {
196         namedThreads.push.apply(
197             namedThreads,
198             this.processes[pid].findAllThreadsNamed(name));
199       }
200       return namedThreads;
201     },
202
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];
208           break;
209         }
210       }
211       if (!importerConstructor)
212         throw new Error(
213             'Could not find an importer for the provided eventData.');
214
215       var importer = new importerConstructor(
216           this, eventData);
217       return importer;
218     },
219
220     /**
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.
225      *
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.
229      *
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.
234      * Defaults to true.
235      * @param {bool=} opt_pruneEmptyContainers Whether to prune empty
236      * containers. Defaults to true.
237      */
238     importTraces: function(traces,
239                            opt_shiftWorldToZero,
240                            opt_pruneEmptyContainers) {
241       var progressMeter = {
242         update: function(msg) {}
243       };
244       var task = this.createImportTracesTask(
245           progressMeter,
246           traces,
247           opt_shiftWorldToZero,
248           opt_pruneEmptyContainers);
249       tracing.importer.Task.RunSynchronously(task);
250     },
251
252     /**
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.
256      */
257     importTracesWithProgressDialog: function(traces,
258                                              opt_shiftWorldToZero,
259                                              opt_pruneEmptyContainers) {
260       var overlay = tvcm.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;
268       }
269       overlay.visible = true;
270
271       var task = this.createImportTracesTask(
272           overlay,
273           traces,
274           opt_shiftWorldToZero,
275           opt_pruneEmptyContainers);
276       var promise = tracing.importer.Task.RunWhenIdle(task);
277       promise.then(
278           function() {
279             overlay.visible = false;
280           }, function(err) {
281             overlay.visible = false;
282           });
283       return promise;
284     },
285
286     /**
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
289      * importTraces.
290      */
291     createImportTracesTask: function(progressMeter,
292                                      traces,
293                                      opt_shiftWorldToZero,
294                                      opt_pruneEmptyContainers) {
295       if (this.importing_)
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;
302
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...');
308       }, this);
309       var lastTask = importTask;
310
311       var importers = [];
312
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]));
320
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]));
329           }
330         }
331
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;
337         });
338       }, this);
339
340       // Run the import.
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);
347           }, this);
348         }, this);
349       }, this);
350
351       // Autoclose open slices.
352       lastTask = lastTask.after(function() {
353         progressMeter.update('Autoclosing open slices...');
354         this.updateBounds();
355         this.kernel.autoCloseOpenSlices(this.bounds.max);
356         for (var pid in this.processes)
357           this.processes[pid].autoCloseOpenSlices(this.bounds.max);
358       }, this);
359
360       // Finalize import.
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();
366         }, this);
367       }, this);
368
369       // Run preinit.
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();
374       }, this);
375
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();
383           }
384         }, this);
385       }
386
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();
392       }, this);
393
394       lastTask = lastTask.after(function() {
395         progressMeter.update('Computing final world bounds...');
396         this.updateBounds();
397         this.updateCategories_();
398
399         if (opt_shiftWorldToZero)
400           this.shiftWorldToZero();
401       }, this);
402
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]);
409         }
410         this.flowIntervalTree.updateHighValues();
411       }, this);
412
413       // Join refs.
414       lastTask = lastTask.after(function() {
415         progressMeter.update('Joining object refs...');
416         for (var i = 0; i < importers.length; i++)
417           importers[i].joinRefs();
418       }, this);
419
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);
425       }, this);
426
427       // Run initializers.
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();
432       }, this);
433
434       // Cleanup.
435       lastTask.after(function() {
436         this.importing_ = false;
437       }, this);
438       return importTask;
439     },
440
441     /**
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.
446      */
447     importWarning: function(data) {
448       this.importWarnings_.push(data);
449
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)
453         return;
454
455       console.warn(data.message);
456       this.reportedImportWarnings_[data.type] = true;
457     },
458
459     get hasImportWarnings() {
460       return (this.importWarnings_.length > 0);
461     },
462
463     get importWarnings() {
464       return this.importWarnings_;
465     },
466
467     /**
468      * Iterates all events in the model and calls callback on each event.
469      * @param {function(event)} callback The callback called for every event.
470      */
471     iterateAllEvents: function(callback) {
472       this.instantEvents.forEach(callback);
473
474       this.kernel.iterateAllEvents(callback);
475
476       for (var pid in this.processes)
477         this.processes[pid].iterateAllEvents(callback);
478     }
479   };
480
481   /**
482    * Importer for empty strings and arrays.
483    * @constructor
484    */
485   function TraceModelEmptyImporter(events) {
486     this.importPriority = 0;
487   };
488
489   TraceModelEmptyImporter.canImport = function(eventData) {
490     if (eventData instanceof Array && eventData.length == 0)
491       return true;
492     if (typeof(eventData) === 'string' || eventData instanceof String) {
493       return eventData.length == 0;
494     }
495     return false;
496   };
497
498   TraceModelEmptyImporter.prototype = {
499     __proto__: Importer.prototype
500   };
501
502   TraceModel.registerImporter(TraceModelEmptyImporter);
503
504   return {
505     TraceModel: TraceModel
506   };
507 });