Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / trace_viewer / tracing / trace_model.html
1 <!DOCTYPE html>
2 <!--
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.
6 -->
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">
19
20 <script>
21 'use strict';
22
23 /**
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.
28  *
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.
32  *
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
36  * nesting tasks.
37  *
38  */
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;
43
44   /**
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.
50    * Defaults to true.
51    * @constructor
52    */
53   function TraceModel(opt_eventData, opt_shiftWorldToZero) {
54     this.kernel = new Kernel(this);
55     this.processes = {};
56     this.metadata = [];
57     this.categories = [];
58     this.bounds = new tv.Range();
59     this.instantEvents = [];
60     this.flowEvents = [];
61
62     this.stackFrames = {};
63     this.samples = [];
64
65     this.flowIntervalTree = new tv.IntervalTree(
66         function(s) { return s.start; },
67         function(e) { return e.start; });
68
69     this.importWarnings_ = [];
70     this.reportedImportWarnings_ = {};
71
72     if (opt_eventData)
73       this.importTraces([opt_eventData], opt_shiftWorldToZero);
74   }
75
76   TraceModel.importerConstructors_ = [];
77
78   /**
79    * Registers an importer. All registered importers are considered
80    * when processing an import request.
81    *
82    * @param {Function} importerConstructor The importer's constructor function.
83    */
84   TraceModel.registerImporter = function(importerConstructor) {
85     TraceModel.importerConstructors_.push(importerConstructor);
86   };
87
88   TraceModel.prototype = {
89     __proto__: tv.EventTarget.prototype,
90
91     get numProcesses() {
92       var n = 0;
93       for (var p in this.processes)
94         n++;
95       return n;
96     },
97
98     /**
99      * @return {Process} Gets a TimelineProcess for a specified pid. Returns
100      * undefined if the process doesn't exist.
101      */
102     getProcess: function(pid) {
103       return this.processes[pid];
104     },
105
106     /**
107      * @return {Process} Gets a TimelineProcess for a specified pid or
108      * creates one if it does not exist.
109      */
110     getOrCreateProcess: function(pid) {
111       if (!this.processes[pid])
112         this.processes[pid] = new Process(this, pid);
113       return this.processes[pid];
114     },
115
116     pushInstantEvent: function(instantEvent) {
117       this.instantEvents.push(instantEvent);
118     },
119
120     addStackFrame: function(stackFrame) {
121       if (this.stackFrames[stackFrame.id])
122         throw new Error('Stack frame already exists');
123       this.stackFrames[stackFrame.id] = stackFrame;
124       return stackFrame;
125     },
126
127     /**
128      * Generates the set of categories from the slices and counters.
129      */
130     updateCategories_: function() {
131       var categoriesDict = {};
132       this.kernel.addCategoriesToDict(categoriesDict);
133       for (var pid in this.processes)
134         this.processes[pid].addCategoriesToDict(categoriesDict);
135
136       this.categories = [];
137       for (var category in categoriesDict)
138         if (category != '')
139           this.categories.push(category);
140     },
141
142     updateBounds: function() {
143       this.bounds.reset();
144
145       this.kernel.updateBounds();
146       this.bounds.addRange(this.kernel.bounds);
147
148       for (var pid in this.processes) {
149         this.processes[pid].updateBounds();
150         this.bounds.addRange(this.processes[pid].bounds);
151       }
152     },
153
154     shiftWorldToZero: function() {
155       if (this.bounds.isEmpty)
156         return;
157       var timeBase = this.bounds.min;
158       this.kernel.shiftTimestampsForward(-timeBase);
159
160       for (var id in this.instantEvents)
161         this.instantEvents[id].start -= timeBase;
162
163       for (var pid in this.processes)
164         this.processes[pid].shiftTimestampsForward(-timeBase);
165
166       for (var i = 0; i < this.samples.length; i++) {
167         var sample = this.samples[i];
168         sample.start -= timeBase;
169       }
170
171       this.updateBounds();
172     },
173
174     getAllThreads: function() {
175       var threads = [];
176       for (var tid in this.kernel.threads) {
177         threads.push(process.threads[tid]);
178       }
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]);
183         }
184       }
185       return threads;
186     },
187
188     /**
189      * @return {Array} An array of all processes in the model.
190      */
191     getAllProcesses: function() {
192       var processes = [];
193       for (var pid in this.processes)
194         processes.push(this.processes[pid]);
195       return processes;
196     },
197
198     /**
199      * @return {Array} An array of all the counters in the model.
200      */
201     getAllCounters: function() {
202       var counters = [];
203       counters.push.apply(
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]);
209         }
210       }
211       return counters;
212     },
213
214     /**
215      * @param {String} The name of the thread to find.
216      * @return {Array} An array of all the matched threads.
217      */
218     findAllThreadsNamed: function(name) {
219       var namedThreads = [];
220       namedThreads.push.apply(
221           namedThreads,
222           this.kernel.findAllThreadsNamed(name));
223       for (var pid in this.processes) {
224         namedThreads.push.apply(
225             namedThreads,
226             this.processes[pid].findAllThreadsNamed(name));
227       }
228       return namedThreads;
229     },
230
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];
236           break;
237         }
238       }
239       // TODO(kphanee): Throwing same Error at 2 places. Lets try to avoid it!
240       if (!importerConstructor)
241         throw new Error(
242             'Could not find an importer for the provided eventData.');
243
244       var importer = new importerConstructor(
245           this, eventData);
246       return importer;
247     },
248
249     /**
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.
254      *
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.
258      *
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.
263      * Defaults to true.
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
268      * finalized.
269      */
270     importTraces: function(traces,
271                            opt_shiftWorldToZero,
272                            opt_pruneEmptyContainers,
273                            opt_customizeModelCallback) {
274       var progressMeter = {
275         update: function(msg) {}
276       };
277       var task = this.createImportTracesTask(
278           progressMeter,
279           traces,
280           opt_shiftWorldToZero,
281           opt_pruneEmptyContainers,
282           opt_customizeModelCallback);
283       tracing.importer.Task.RunSynchronously(task);
284     },
285
286     /**
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.
290      */
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;
303       }
304       overlay.visible = true;
305
306       var task = this.createImportTracesTask(
307           overlay,
308           traces,
309           opt_shiftWorldToZero,
310           opt_pruneEmptyContainers,
311           opt_customizeModelCallback);
312       var promise = tracing.importer.Task.RunWhenIdle(task);
313       promise.then(
314           function() {
315             overlay.visible = false;
316           }, function(err) {
317             overlay.visible = false;
318           });
319       return promise;
320     },
321
322     hasEventDataDecoder_: function(importers) {
323       if (importers.length === 0)
324         return false;
325
326       for (var i = 0; i < importers.length; ++i) {
327         if (!importers[i].isTraceDataContainer())
328           return true;
329       }
330       return false;
331     },
332
333     /**
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
336      * importTraces.
337      */
338     createImportTracesTask: function(progressMeter,
339                                      traces,
340                                      opt_shiftWorldToZero,
341                                      opt_pruneEmptyContainers,
342                                      opt_customizeModelCallback) {
343       if (this.importing_)
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;
350
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...');
356       }, this);
357       var lastTask = importTask;
358
359       var importers = [];
360
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]));
368
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++) {
375             try {
376               traces.push(subtraces[j]);
377               importers.push(this.createImporter_(subtraces[j]));
378             } catch (error) {
379               // TODO(kphanee): Log the subtrace file which has failed.
380               console.warn(error.name + ': ' + error.message);
381               continue;
382             }
383           }
384         }
385
386         if (traces.length && !this.hasEventDataDecoder_(importers))
387           throw new Error('Could not find an importer for the provided eventData.');
388
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;
394         });
395       }, this);
396
397       // Run the import.
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);
404           }, this);
405         }, this);
406       }, this);
407
408       // Run the cusomizeModelCallback if needed.
409       if (opt_customizeModelCallback) {
410         lastTask = lastTask.after(function(task) {
411           opt_customizeModelCallback(this);
412         }, this);
413       }
414
415       // Finalize import.
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();
421         }, this);
422       }, this);
423
424       // Autoclose open slices and create subSlices.
425       lastTask = lastTask.after(function() {
426         progressMeter.update('Autoclosing open slices...');
427         // Sort the samples.
428         this.samples.sort(function(x, y) {
429           return x.start - y.start;
430         });
431
432         this.updateBounds();
433         this.kernel.autoCloseOpenSlices(this.bounds.max);
434         for (var pid in this.processes)
435           this.processes[pid].autoCloseOpenSlices(this.bounds.max);
436
437         this.kernel.createSubSlices();
438         for (var pid in this.processes)
439           this.processes[pid].createSubSlices();
440       }, this);
441
442       // Finalize import.
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();
448         }, this);
449       }, this);
450
451       // Run preinit.
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();
456       }, this);
457
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();
465           }
466         }, this);
467       }
468
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();
474       }, this);
475
476       lastTask = lastTask.after(function() {
477         progressMeter.update('Computing final world bounds...');
478         this.updateBounds();
479         this.updateCategories_();
480
481         if (opt_shiftWorldToZero)
482           this.shiftWorldToZero();
483       }, this);
484
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]);
491         }
492         this.flowIntervalTree.updateHighValues();
493       }, this);
494
495       // Join refs.
496       lastTask = lastTask.after(function() {
497         progressMeter.update('Joining object refs...');
498         for (var i = 0; i < importers.length; i++)
499           importers[i].joinRefs();
500       }, this);
501
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);
507       }, this);
508
509       // Run initializers.
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();
514       }, this);
515
516       // Cleanup.
517       lastTask.after(function() {
518         this.importing_ = false;
519       }, this);
520       return importTask;
521     },
522
523     /**
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.
528      */
529     importWarning: function(data) {
530       this.importWarnings_.push(data);
531
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)
535         return;
536
537       console.warn(data.message);
538       this.reportedImportWarnings_[data.type] = true;
539     },
540
541     get hasImportWarnings() {
542       return (this.importWarnings_.length > 0);
543     },
544
545     get importWarnings() {
546       return this.importWarnings_;
547     },
548
549     /**
550      * Iterates all events in the model and calls callback on each event.
551      * @param {function(event)} callback The callback called for every event.
552      */
553     iterateAllEvents: function(callback, opt_this) {
554       this.instantEvents.forEach(callback, opt_this);
555
556       this.kernel.iterateAllEvents(callback, opt_this);
557
558       for (var pid in this.processes)
559         this.processes[pid].iterateAllEvents(callback, opt_this);
560
561       this.samples.forEach(callback, opt_this);
562     },
563
564     /**
565      * Some objects in the model can persist their state in TraceModelSettings.
566      *
567      * This iterates through them.
568      */
569     iterateAllPersistableObjects: function(cb) {
570       this.kernel.iterateAllPersistableObjects(cb);
571       for (var pid in this.processes)
572         this.processes[pid].iterateAllPersistableObjects(cb);
573     }
574   };
575
576   /**
577    * Importer for empty strings and arrays.
578    * @constructor
579    */
580   function TraceModelEmptyImporter(events) {
581     this.importPriority = 0;
582   };
583
584   TraceModelEmptyImporter.canImport = function(eventData) {
585     if (eventData instanceof Array && eventData.length == 0)
586       return true;
587     if (typeof(eventData) === 'string' || eventData instanceof String) {
588       return eventData.length == 0;
589     }
590     return false;
591   };
592
593   TraceModelEmptyImporter.prototype = {
594     __proto__: Importer.prototype
595   };
596
597   TraceModel.registerImporter(TraceModelEmptyImporter);
598
599   return {
600     TraceModel: TraceModel
601   };
602 });
603 </script>