Upstream version 10.39.225.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="/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">
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 tvcm.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 tvcm.Range();
59     this.instantEvents = [];
60     this.flowEvents = [];
61
62     this.stackFrames = {};
63     this.samples = [];
64
65     this.flowIntervalTree = new tvcm.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__: tvcm.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, 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]);
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       if (!importerConstructor)
240         throw new Error(
241             'Could not find an importer for the provided eventData.');
242
243       var importer = new importerConstructor(
244           this, eventData);
245       return importer;
246     },
247
248     /**
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.
253      *
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.
257      *
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.
262      * Defaults to true.
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
267      * finalized.
268      */
269     importTraces: function(traces,
270                            opt_shiftWorldToZero,
271                            opt_pruneEmptyContainers,
272                            opt_customizeModelCallback) {
273       var progressMeter = {
274         update: function(msg) {}
275       };
276       var task = this.createImportTracesTask(
277           progressMeter,
278           traces,
279           opt_shiftWorldToZero,
280           opt_pruneEmptyContainers,
281           opt_customizeModelCallback);
282       tracing.importer.Task.RunSynchronously(task);
283     },
284
285     /**
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.
289      */
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;
302       }
303       overlay.visible = true;
304
305       var task = this.createImportTracesTask(
306           overlay,
307           traces,
308           opt_shiftWorldToZero,
309           opt_pruneEmptyContainers,
310           opt_customizeModelCallback);
311       var promise = tracing.importer.Task.RunWhenIdle(task);
312       promise.then(
313           function() {
314             overlay.visible = false;
315           }, function(err) {
316             overlay.visible = false;
317           });
318       return promise;
319     },
320
321     /**
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
324      * importTraces.
325      */
326     createImportTracesTask: function(progressMeter,
327                                      traces,
328                                      opt_shiftWorldToZero,
329                                      opt_pruneEmptyContainers,
330                                      opt_customizeModelCallback) {
331       if (this.importing_)
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;
338
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...');
344       }, this);
345       var lastTask = importTask;
346
347       var importers = [];
348
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]));
356
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]));
365           }
366         }
367
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;
373         });
374       }, this);
375
376       // Run the import.
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);
383           }, this);
384         }, this);
385       }, this);
386
387       // Run the cusomizeModelCallback if needed.
388       if (opt_customizeModelCallback) {
389         lastTask = lastTask.after(function(task) {
390           opt_customizeModelCallback(this);
391         }, this);
392       }
393
394       // Finalize import.
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();
400         }, this);
401       }, this);
402
403       // Autoclose open slices and create subSlices.
404       lastTask = lastTask.after(function() {
405         progressMeter.update('Autoclosing open slices...');
406         // Sort the samples.
407         this.samples.sort(function(x, y) {
408           return x.start - y.start;
409         });
410
411         this.updateBounds();
412         this.kernel.autoCloseOpenSlices(this.bounds.max);
413         for (var pid in this.processes)
414           this.processes[pid].autoCloseOpenSlices(this.bounds.max);
415
416         this.kernel.createSubSlices();
417         for (var pid in this.processes)
418           this.processes[pid].createSubSlices();
419       }, this);
420
421       // Finalize import.
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();
427         }, this);
428       }, this);
429
430       // Run preinit.
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();
435       }, this);
436
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();
444           }
445         }, this);
446       }
447
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();
453       }, this);
454
455       lastTask = lastTask.after(function() {
456         progressMeter.update('Computing final world bounds...');
457         this.updateBounds();
458         this.updateCategories_();
459
460         if (opt_shiftWorldToZero)
461           this.shiftWorldToZero();
462       }, this);
463
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]);
470         }
471         this.flowIntervalTree.updateHighValues();
472       }, this);
473
474       // Join refs.
475       lastTask = lastTask.after(function() {
476         progressMeter.update('Joining object refs...');
477         for (var i = 0; i < importers.length; i++)
478           importers[i].joinRefs();
479       }, this);
480
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);
486       }, this);
487
488       // Run initializers.
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();
493       }, this);
494
495       // Cleanup.
496       lastTask.after(function() {
497         this.importing_ = false;
498       }, this);
499       return importTask;
500     },
501
502     /**
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.
507      */
508     importWarning: function(data) {
509       this.importWarnings_.push(data);
510
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)
514         return;
515
516       console.warn(data.message);
517       this.reportedImportWarnings_[data.type] = true;
518     },
519
520     get hasImportWarnings() {
521       return (this.importWarnings_.length > 0);
522     },
523
524     get importWarnings() {
525       return this.importWarnings_;
526     },
527
528     /**
529      * Iterates all events in the model and calls callback on each event.
530      * @param {function(event)} callback The callback called for every event.
531      */
532     iterateAllEvents: function(callback, opt_this) {
533       this.instantEvents.forEach(callback, opt_this);
534
535       this.kernel.iterateAllEvents(callback, opt_this);
536
537       for (var pid in this.processes)
538         this.processes[pid].iterateAllEvents(callback, opt_this);
539
540       this.samples.forEach(callback, opt_this);
541     },
542
543     /**
544      * Some objects in the model can persist their state in TraceModelSettings.
545      *
546      * This iterates through them.
547      */
548     iterateAllPersistableObjects: function(cb) {
549       this.kernel.iterateAllPersistableObjects(cb);
550       for (var pid in this.processes)
551         this.processes[pid].iterateAllPersistableObjects(cb);
552     }
553   };
554
555   /**
556    * Importer for empty strings and arrays.
557    * @constructor
558    */
559   function TraceModelEmptyImporter(events) {
560     this.importPriority = 0;
561   };
562
563   TraceModelEmptyImporter.canImport = function(eventData) {
564     if (eventData instanceof Array && eventData.length == 0)
565       return true;
566     if (typeof(eventData) === 'string' || eventData instanceof String) {
567       return eventData.length == 0;
568     }
569     return false;
570   };
571
572   TraceModelEmptyImporter.prototype = {
573     __proto__: Importer.prototype
574   };
575
576   TraceModel.registerImporter(TraceModelEmptyImporter);
577
578   return {
579     TraceModel: TraceModel
580   };
581 });
582 </script>