Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / trace_viewer / 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.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');
34
35 tvcm.exportTo('tracing', function() {
36
37   var Importer = tracing.importer.Importer;
38   var Process = tracing.trace_model.Process;
39   var Kernel = tracing.trace_model.Kernel;
40
41   /**
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.
47    * Defaults to true.
48    * @constructor
49    */
50   function TraceModel(opt_eventData, opt_shiftWorldToZero) {
51     this.kernel = new Kernel(this);
52     this.processes = {};
53     this.metadata = [];
54     this.categories = [];
55     this.bounds = new tvcm.Range();
56     this.instantEvents = [];
57     this.flowEvents = [];
58
59     this.stackFrames = {};
60     this.samples = [];
61
62     this.flowIntervalTree = new tvcm.IntervalTree(
63         function(s) { return s.start; },
64         function(e) { return e.start; });
65
66     this.importWarnings_ = [];
67     this.reportedImportWarnings_ = {};
68
69     if (opt_eventData)
70       this.importTraces([opt_eventData], opt_shiftWorldToZero);
71   }
72
73   TraceModel.importerConstructors_ = [];
74
75   /**
76    * Registers an importer. All registered importers are considered
77    * when processing an import request.
78    *
79    * @param {Function} importerConstructor The importer's constructor function.
80    */
81   TraceModel.registerImporter = function(importerConstructor) {
82     TraceModel.importerConstructors_.push(importerConstructor);
83   };
84
85   TraceModel.prototype = {
86     __proto__: tvcm.EventTarget.prototype,
87
88     get numProcesses() {
89       var n = 0;
90       for (var p in this.processes)
91         n++;
92       return n;
93     },
94
95     /**
96      * @return {Process} Gets a TimelineProcess for a specified pid or
97      * creates one if it does not exist.
98      */
99     getOrCreateProcess: function(pid) {
100       if (!this.processes[pid])
101         this.processes[pid] = new Process(this, pid);
102       return this.processes[pid];
103     },
104
105     pushInstantEvent: function(instantEvent) {
106       this.instantEvents.push(instantEvent);
107     },
108
109     addStackFrame: function(stackFrame) {
110       if (this.stackFrames[stackFrame.id])
111         throw new Error('Stack frame already exists');
112       this.stackFrames[stackFrame.id] = stackFrame;
113       return stackFrame;
114     },
115
116     /**
117      * Generates the set of categories from the slices and counters.
118      */
119     updateCategories_: function() {
120       var categoriesDict = {};
121       this.kernel.addCategoriesToDict(categoriesDict);
122       for (var pid in this.processes)
123         this.processes[pid].addCategoriesToDict(categoriesDict);
124
125       this.categories = [];
126       for (var category in categoriesDict)
127         if (category != '')
128           this.categories.push(category);
129     },
130
131     updateBounds: function() {
132       this.bounds.reset();
133
134       this.kernel.updateBounds();
135       this.bounds.addRange(this.kernel.bounds);
136
137       for (var pid in this.processes) {
138         this.processes[pid].updateBounds();
139         this.bounds.addRange(this.processes[pid].bounds);
140       }
141     },
142
143     shiftWorldToZero: function() {
144       if (this.bounds.isEmpty)
145         return;
146       var timeBase = this.bounds.min;
147       this.kernel.shiftTimestampsForward(-timeBase);
148
149       for (var id in this.instantEvents)
150         this.instantEvents[id].start -= timeBase;
151
152       for (var pid in this.processes)
153         this.processes[pid].shiftTimestampsForward(-timeBase);
154
155       for (var i = 0; i < this.samples.length; i++) {
156         var sample = this.samples[i];
157         sample.start -= timeBase;
158       }
159
160       this.updateBounds();
161     },
162
163     getAllThreads: function() {
164       var threads = [];
165       for (var tid in this.kernel.threads) {
166         threads.push(process.threads[tid]);
167       }
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]);
172         }
173       }
174       return threads;
175     },
176
177     /**
178      * @return {Array} An array of all processes in the model.
179      */
180     getAllProcesses: function() {
181       var processes = [];
182       for (var pid in this.processes)
183         processes.push(this.processes[pid]);
184       return processes;
185     },
186
187     /**
188      * @return {Array} An array of all the counters in the model.
189      */
190     getAllCounters: function() {
191       var counters = [];
192       counters.push.apply(
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]);
198         }
199       }
200       return counters;
201     },
202
203     /**
204      * @param {String} The name of the thread to find.
205      * @return {Array} An array of all the matched threads.
206      */
207     findAllThreadsNamed: function(name) {
208       var namedThreads = [];
209       namedThreads.push.apply(
210           namedThreads,
211           this.kernel.findAllThreadsNamed(name));
212       for (var pid in this.processes) {
213         namedThreads.push.apply(
214             namedThreads,
215             this.processes[pid].findAllThreadsNamed(name));
216       }
217       return namedThreads;
218     },
219
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];
225           break;
226         }
227       }
228       if (!importerConstructor)
229         throw new Error(
230             'Could not find an importer for the provided eventData.');
231
232       var importer = new importerConstructor(
233           this, eventData);
234       return importer;
235     },
236
237     /**
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.
242      *
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.
246      *
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.
251      * Defaults to true.
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
256      * finalized.
257      */
258     importTraces: function(traces,
259                            opt_shiftWorldToZero,
260                            opt_pruneEmptyContainers,
261                            opt_customizeModelCallback) {
262       var progressMeter = {
263         update: function(msg) {}
264       };
265       var task = this.createImportTracesTask(
266           progressMeter,
267           traces,
268           opt_shiftWorldToZero,
269           opt_pruneEmptyContainers,
270           opt_customizeModelCallback);
271       tracing.importer.Task.RunSynchronously(task);
272     },
273
274     /**
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.
278      */
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;
291       }
292       overlay.visible = true;
293
294       var task = this.createImportTracesTask(
295           overlay,
296           traces,
297           opt_shiftWorldToZero,
298           opt_pruneEmptyContainers,
299           opt_customizeModelCallback);
300       var promise = tracing.importer.Task.RunWhenIdle(task);
301       promise.then(
302           function() {
303             overlay.visible = false;
304           }, function(err) {
305             overlay.visible = false;
306           });
307       return promise;
308     },
309
310     /**
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
313      * importTraces.
314      */
315     createImportTracesTask: function(progressMeter,
316                                      traces,
317                                      opt_shiftWorldToZero,
318                                      opt_pruneEmptyContainers,
319                                      opt_customizeModelCallback) {
320       if (this.importing_)
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;
327
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...');
333       }, this);
334       var lastTask = importTask;
335
336       var importers = [];
337
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]));
345
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]));
354           }
355         }
356
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;
362         });
363       }, this);
364
365       // Run the import.
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);
372           }, this);
373         }, this);
374       }, this);
375
376       // Run the cusomizeModelCallback if needed.
377       if (opt_customizeModelCallback) {
378         lastTask = lastTask.after(function(task) {
379           opt_customizeModelCallback(this);
380         }, this);
381       }
382
383       // Finalize import.
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();
389         }, this);
390       }, this);
391
392       // Autoclose open slices and create subSlices.
393       lastTask = lastTask.after(function() {
394         progressMeter.update('Autoclosing open slices...');
395         // Sort the samples.
396         this.samples.sort(function(x, y) {
397           return x.ts - y.ts;
398         });
399
400         this.updateBounds();
401         this.kernel.autoCloseOpenSlices(this.bounds.max);
402         for (var pid in this.processes)
403           this.processes[pid].autoCloseOpenSlices(this.bounds.max);
404
405         this.kernel.createSubSlices();
406         for (var pid in this.processes)
407           this.processes[pid].createSubSlices();
408       }, this);
409
410       // Finalize import.
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();
416         }, this);
417       }, this);
418
419       // Run preinit.
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();
424       }, this);
425
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();
433           }
434         }, this);
435       }
436
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();
442       }, this);
443
444       lastTask = lastTask.after(function() {
445         progressMeter.update('Computing final world bounds...');
446         this.updateBounds();
447         this.updateCategories_();
448
449         if (opt_shiftWorldToZero)
450           this.shiftWorldToZero();
451       }, this);
452
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]);
459         }
460         this.flowIntervalTree.updateHighValues();
461       }, this);
462
463       // Join refs.
464       lastTask = lastTask.after(function() {
465         progressMeter.update('Joining object refs...');
466         for (var i = 0; i < importers.length; i++)
467           importers[i].joinRefs();
468       }, this);
469
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);
475       }, this);
476
477       // Run initializers.
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();
482       }, this);
483
484       // Cleanup.
485       lastTask.after(function() {
486         this.importing_ = false;
487       }, this);
488       return importTask;
489     },
490
491     /**
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.
496      */
497     importWarning: function(data) {
498       this.importWarnings_.push(data);
499
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)
503         return;
504
505       console.warn(data.message);
506       this.reportedImportWarnings_[data.type] = true;
507     },
508
509     get hasImportWarnings() {
510       return (this.importWarnings_.length > 0);
511     },
512
513     get importWarnings() {
514       return this.importWarnings_;
515     },
516
517     /**
518      * Iterates all events in the model and calls callback on each event.
519      * @param {function(event)} callback The callback called for every event.
520      */
521     iterateAllEvents: function(callback, opt_this) {
522       this.instantEvents.forEach(callback, opt_this);
523
524       this.kernel.iterateAllEvents(callback, opt_this);
525
526       for (var pid in this.processes)
527         this.processes[pid].iterateAllEvents(callback, opt_this);
528
529       this.samples.forEach(callback, opt_this);
530     }
531   };
532
533   /**
534    * Importer for empty strings and arrays.
535    * @constructor
536    */
537   function TraceModelEmptyImporter(events) {
538     this.importPriority = 0;
539   };
540
541   TraceModelEmptyImporter.canImport = function(eventData) {
542     if (eventData instanceof Array && eventData.length == 0)
543       return true;
544     if (typeof(eventData) === 'string' || eventData instanceof String) {
545       return eventData.length == 0;
546     }
547     return false;
548   };
549
550   TraceModelEmptyImporter.prototype = {
551     __proto__: Importer.prototype
552   };
553
554   TraceModel.registerImporter(TraceModelEmptyImporter);
555
556   return {
557     TraceModel: TraceModel
558   };
559 });