Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / trace_viewer / tracing / importer / trace_event_importer.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
8 <link rel="import" href="/base/quad.html">
9 <link rel="import" href="/tracing/trace_model.html">
10 <link rel="import" href="/tracing/color_scheme.html">
11 <link rel="import" href="/tracing/importer/importer.html">
12 <link rel="import" href="/tracing/trace_model/instant_event.html">
13 <link rel="import" href="/tracing/trace_model/flow_event.html">
14 <link rel="import" href="/tracing/trace_model/counter_series.html">
15 <link rel="import" href="/tracing/trace_model/slice_group.html">
16
17 <script>
18 'use strict';
19
20 /**
21  * @fileoverview TraceEventImporter imports TraceEvent-formatted data
22  * into the provided model.
23  */
24 tv.exportTo('tracing.importer', function() {
25
26   var Importer = tracing.importer.Importer;
27
28   function deepCopy(value) {
29     if (!(value instanceof Object)) {
30       if (value === undefined || value === null)
31         return value;
32       if (typeof value == 'string')
33         return value.substring();
34       if (typeof value == 'boolean')
35         return value;
36       if (typeof value == 'number')
37         return value;
38       throw new Error('Unrecognized: ' + typeof value);
39     }
40
41     var object = value;
42     if (object instanceof Array) {
43       var res = new Array(object.length);
44       for (var i = 0; i < object.length; i++)
45         res[i] = deepCopy(object[i]);
46       return res;
47     }
48
49     if (object.__proto__ != Object.prototype)
50       throw new Error('Can only clone simple types');
51     var res = {};
52     for (var key in object) {
53       res[key] = deepCopy(object[key]);
54     }
55     return res;
56   }
57
58   function TraceEventImporter(model, eventData) {
59     this.importPriority = 1;
60     this.model_ = model;
61     this.events_ = undefined;
62     this.sampleEvents_ = undefined;
63     this.stackFrameEvents_ = undefined;
64     this.systemTraceEvents_ = undefined;
65     this.eventsWereFromString_ = false;
66     this.softwareMeasuredCpuCount_ = undefined;
67     this.allAsyncEvents_ = [];
68     this.allFlowEvents_ = [];
69     this.allObjectEvents_ = [];
70
71     if (typeof(eventData) === 'string' || eventData instanceof String) {
72       // If the event data begins with a [, then we know it should end with a ].
73       // The reason we check for this is because some tracing implementations
74       // cannot guarantee that a ']' gets written to the trace file. So, we are
75       // forgiving and if this is obviously the case, we fix it up before
76       // throwing the string at JSON.parse.
77       if (eventData[0] === '[') {
78         eventData = eventData.replace(/[\r|\n]*$/, '')
79                              .replace(/\s*,\s*$/, '');
80         if (eventData[eventData.length - 1] !== ']')
81           eventData = eventData + ']';
82       }
83       this.events_ = JSON.parse(eventData);
84       this.eventsWereFromString_ = true;
85     } else {
86       this.events_ = eventData;
87     }
88
89     // Some trace_event implementations put the actual trace events
90     // inside a container. E.g { ... , traceEvents: [ ] }
91     // If we see that, just pull out the trace events.
92     if (this.events_.traceEvents) {
93       var container = this.events_;
94       this.events_ = this.events_.traceEvents;
95
96       // Some trace_event implementations put linux_perf_importer traces as a
97       // huge string inside container.systemTraceEvents. If we see that, pull it
98       // out. It will be picked up by extractSubtraces later on.
99       this.systemTraceEvents_ = container.systemTraceEvents;
100
101       // Sampling data.
102       this.sampleEvents_ = container.samples;
103       this.stackFrameEvents_ = container.stackFrames;
104
105       // Any other fields in the container should be treated as metadata.
106       for (var fieldName in container) {
107         if (fieldName === 'traceEvents' || fieldName === 'systemTraceEvents' ||
108             fieldName === 'samples' || fieldName === 'stackFrames')
109           continue;
110         this.model_.metadata.push({name: fieldName,
111           value: container[fieldName]});
112       }
113     }
114   }
115
116   /**
117    * @return {boolean} Whether obj is a TraceEvent array.
118    */
119   TraceEventImporter.canImport = function(eventData) {
120     // May be encoded JSON. But we dont want to parse it fully yet.
121     // Use a simple heuristic:
122     //   - eventData that starts with [ are probably trace_event
123     //   - eventData that starts with { are probably trace_event
124     // May be encoded JSON. Treat files that start with { as importable by us.
125     if (typeof(eventData) === 'string' || eventData instanceof String) {
126       return eventData[0] == '{' || eventData[0] == '[';
127     }
128
129     // Might just be an array of events
130     if (eventData instanceof Array && eventData.length && eventData[0].ph)
131       return true;
132
133     // Might be an object with a traceEvents field in it.
134     if (eventData.traceEvents) {
135       if (eventData.traceEvents instanceof Array) {
136         if (eventData.traceEvents.length && eventData.traceEvents[0].ph)
137           return true;
138         if (eventData.samples.length && eventData.stackFrames !== undefined)
139           return true;
140       }
141     }
142
143     return false;
144   };
145
146   TraceEventImporter.prototype = {
147
148     __proto__: Importer.prototype,
149
150     extractSubtraces: function() {
151       var tmp = this.systemTraceEvents_;
152       this.systemTraceEvents_ = undefined;
153       return tmp ? [tmp] : [];
154     },
155
156     /**
157      * Deep copying is only needed if the trace was given to us as events.
158      */
159     deepCopyIfNeeded_: function(obj) {
160       if (obj === undefined)
161         obj = {};
162       if (this.eventsWereFromString_)
163         return obj;
164       return deepCopy(obj);
165     },
166
167     /**
168      * Helper to process an async event.
169      */
170     processAsyncEvent: function(event) {
171       var thread = this.model_.getOrCreateProcess(event.pid).
172           getOrCreateThread(event.tid);
173       this.allAsyncEvents_.push({
174         sequenceNumber: this.allAsyncEvents_.length,
175         event: event,
176         thread: thread});
177     },
178
179     /**
180      * Helper to process a flow event.
181      */
182     processFlowEvent: function(event) {
183       var thread = this.model_.getOrCreateProcess(event.pid).
184           getOrCreateThread(event.tid);
185       this.allFlowEvents_.push({
186         sequenceNumber: this.allFlowEvents_.length,
187         event: event,
188         thread: thread
189       });
190     },
191
192     /**
193      * Helper that creates and adds samples to a Counter object based on
194      * 'C' phase events.
195      */
196     processCounterEvent: function(event) {
197       var ctr_name;
198       if (event.id !== undefined)
199         ctr_name = event.name + '[' + event.id + ']';
200       else
201         ctr_name = event.name;
202
203       var ctr = this.model_.getOrCreateProcess(event.pid)
204           .getOrCreateCounter(event.cat, ctr_name);
205
206       // Initialize the counter's series fields if needed.
207       if (ctr.numSeries === 0) {
208         for (var seriesName in event.args) {
209           ctr.addSeries(new tracing.trace_model.CounterSeries(seriesName,
210               tv.ui.getStringColorId(ctr.name + '.' + seriesName)));
211         }
212
213         if (ctr.numSeries === 0) {
214           this.model_.importWarning({
215             type: 'counter_parse_error',
216             message: 'Expected counter ' + event.name +
217                 ' to have at least one argument to use as a value.'
218           });
219
220           // Drop the counter.
221           delete ctr.parent.counters[ctr.name];
222           return;
223         }
224       }
225
226       var ts = event.ts / 1000;
227       ctr.series.forEach(function(series) {
228         var val = event.args[series.name] ? event.args[series.name] : 0;
229         series.addCounterSample(ts, val);
230       });
231     },
232
233     processObjectEvent: function(event) {
234       var thread = this.model_.getOrCreateProcess(event.pid).
235           getOrCreateThread(event.tid);
236       this.allObjectEvents_.push({
237         sequenceNumber: this.allObjectEvents_.length,
238         event: event,
239         thread: thread});
240     },
241
242     processDurationEvent: function(event) {
243       var thread = this.model_.getOrCreateProcess(event.pid)
244         .getOrCreateThread(event.tid);
245       if (!thread.sliceGroup.isTimestampValidForBeginOrEnd(event.ts / 1000)) {
246         this.model_.importWarning({
247           type: 'duration_parse_error',
248           message: 'Timestamps are moving backward.'
249         });
250         return;
251       }
252
253       if (event.ph == 'B') {
254         thread.sliceGroup.beginSlice(event.cat, event.name, event.ts / 1000,
255                                      this.deepCopyIfNeeded_(event.args),
256                                      event.tts / 1000);
257       } else {
258         if (!thread.sliceGroup.openSliceCount) {
259           this.model_.importWarning({
260             type: 'duration_parse_error',
261             message: 'E phase event without a matching B phase event.'
262           });
263           return;
264         }
265
266         var slice = thread.sliceGroup.endSlice(event.ts / 1000,
267                                                event.tts / 1000);
268         if (event.name && slice.title != event.name) {
269           this.model_.importWarning({
270             type: 'title_match_error',
271             message: 'Titles do not match. Title is ' +
272                 slice.title + ' in openSlice, and is ' +
273                 event.name + ' in endSlice'
274           });
275         }
276         for (var arg in event.args) {
277           if (slice.args[arg] !== undefined) {
278             this.model_.importWarning({
279               type: 'duration_parse_error',
280               message: 'Both the B and E phases of ' + slice.name +
281                   ' provided values for argument ' + arg + '.' +
282                   ' The value of the E phase event will be used.'
283             });
284           }
285           slice.args[arg] = this.deepCopyIfNeeded_(event.args[arg]);
286         }
287       }
288     },
289
290     processCompleteEvent: function(event) {
291       var thread = this.model_.getOrCreateProcess(event.pid)
292           .getOrCreateThread(event.tid);
293       thread.sliceGroup.pushCompleteSlice(event.cat, event.name,
294           event.ts / 1000,
295           event.dur === undefined ? undefined : event.dur / 1000,
296           event.tts === undefined ? undefined : event.tts / 1000,
297           event.tdur === undefined ? undefined : event.tdur / 1000,
298           this.deepCopyIfNeeded_(event.args));
299     },
300
301     processMetadataEvent: function(event) {
302       if (event.name == 'process_name') {
303         var process = this.model_.getOrCreateProcess(event.pid);
304         process.name = event.args.name;
305       } else if (event.name == 'process_labels') {
306         var process = this.model_.getOrCreateProcess(event.pid);
307         var labels = event.args.labels.split(',');
308         for (var i = 0; i < labels.length; i++)
309           process.addLabelIfNeeded(labels[i]);
310       } else if (event.name == 'process_sort_index') {
311         var process = this.model_.getOrCreateProcess(event.pid);
312         process.sortIndex = event.args.sort_index;
313       } else if (event.name == 'thread_name') {
314         var thread = this.model_.getOrCreateProcess(event.pid).
315             getOrCreateThread(event.tid);
316         thread.name = event.args.name;
317       } else if (event.name == 'thread_sort_index') {
318         var thread = this.model_.getOrCreateProcess(event.pid).
319             getOrCreateThread(event.tid);
320         thread.sortIndex = event.args.sort_index;
321       } else if (event.name == 'num_cpus') {
322         var n = event.args.number;
323         // Not all render processes agree on the cpu count in trace_event. Some
324         // processes will report 1, while others will report the actual cpu
325         // count. To deal with this, take the max of what is reported.
326         if (this.softwareMeasuredCpuCount_ !== undefined)
327           n = Math.max(n, this.softwareMeasuredCpuCount_);
328         this.softwareMeasuredCpuCount_ = n;
329       } else {
330         this.model_.importWarning({
331           type: 'metadata_parse_error',
332           message: 'Unrecognized metadata name: ' + event.name
333         });
334       }
335     },
336
337     // Treat an Instant event as a duration 0 slice.
338     // SliceTrack's redraw() knows how to handle this.
339     processInstantEvent: function(event) {
340       var constructor;
341       switch (event.s) {
342         case 'g':
343           constructor = tracing.trace_model.GlobalInstantEvent;
344           break;
345         case 'p':
346           constructor = tracing.trace_model.ProcessInstantEvent;
347           break;
348         case 't':
349           // fall through
350         default:
351           // Default to thread to support old style input files.
352           constructor = tracing.trace_model.ThreadInstantEvent;
353           break;
354       }
355
356       var colorId = tv.ui.getStringColorId(event.name);
357       var instantEvent = new constructor(event.cat, event.name,
358           colorId, event.ts / 1000, this.deepCopyIfNeeded_(event.args));
359
360       switch (instantEvent.type) {
361         case tracing.trace_model.InstantEventType.GLOBAL:
362           this.model_.pushInstantEvent(instantEvent);
363           break;
364
365         case tracing.trace_model.InstantEventType.PROCESS:
366           var process = this.model_.getOrCreateProcess(event.pid);
367           process.pushInstantEvent(instantEvent);
368           break;
369
370         case tracing.trace_model.InstantEventType.THREAD:
371           var thread = this.model_.getOrCreateProcess(event.pid)
372               .getOrCreateThread(event.tid);
373           thread.sliceGroup.pushInstantEvent(instantEvent);
374           break;
375
376         default:
377           throw new Error('Unknown instant event type: ' + event.s);
378       }
379     },
380
381     processTraceSampleEvent: function(event) {
382       var thread = this.model_.getOrCreateProcess(event.pid)
383         .getOrCreateThread(event.tid);
384
385       var id = 'te-' + tv.GUID.allocate();
386       var stackFrame = new tracing.trace_model.StackFrame(
387           undefined, id,
388           event.cat, event.name,
389           tv.ui.getStringColorId(event.name));
390       this.model_.addStackFrame(stackFrame);
391
392       var sample = new tracing.trace_model.Sample(
393           undefined, thread, 'TRACE_EVENT_SAMPLE',
394           event.ts / 1000, stackFrame, 1,
395           this.deepCopyIfNeeded_(event.args));
396       this.model_.samples.push(sample);
397     },
398
399     /**
400      * Walks through the events_ list and outputs the structures discovered to
401      * model_.
402      */
403     importEvents: function() {
404       var events = this.events_;
405       for (var eI = 0; eI < events.length; eI++) {
406         var event = events[eI];
407         if (event.ph === 'B' || event.ph === 'E') {
408           this.processDurationEvent(event);
409
410         } else if (event.ph === 'X') {
411           this.processCompleteEvent(event);
412
413         } else if (event.ph === 'b' || event.ph === 'e' || event.ph === 'n' ||
414                    event.ph === 'S' || event.ph === 'F' || event.ph === 'T' ||
415                    event.ph === 'p') {
416           this.processAsyncEvent(event);
417
418         // Note, I is historic. The instant event marker got changed, but we
419         // want to support loading old trace files so we have both I and i.
420         } else if (event.ph == 'I' || event.ph == 'i') {
421           this.processInstantEvent(event);
422
423         } else if (event.ph == 'P') {
424           this.processTraceSampleEvent(event);
425
426         } else if (event.ph == 'C') {
427           this.processCounterEvent(event);
428
429         } else if (event.ph == 'M') {
430           this.processMetadataEvent(event);
431
432         } else if (event.ph === 'N' || event.ph === 'D' || event.ph === 'O') {
433           this.processObjectEvent(event);
434
435         } else if (event.ph === 's' || event.ph === 't' || event.ph === 'f') {
436           this.processFlowEvent(event);
437
438         } else {
439           this.model_.importWarning({
440             type: 'parse_error',
441             message: 'Unrecognized event phase: ' +
442                 event.ph + ' (' + event.name + ')'
443           });
444         }
445       }
446
447       if (this.stackFrameEvents_)
448         this.importStackFrames_();
449     },
450
451     importStackFrames_: function() {
452       var m = this.model_;
453       var events = this.stackFrameEvents_;
454       for (var id in events) {
455         var event = events[id];
456         var textForColor = event.category ? event.category : event.name;
457         var frame = new tracing.trace_model.StackFrame(
458             undefined, id,
459             event.category, event.name,
460             tv.ui.getStringColorId(textForColor));
461         m.addStackFrame(frame);
462       }
463       for (var id in events) {
464         var event = events[id];
465         if (event.parent === undefined)
466           continue;
467
468         var frame = m.stackFrames[id];
469         if (frame === undefined)
470           throw new Error('omg');
471         var parentFrame;
472         if (event.parent === undefined) {
473           parentFrame = undefined;
474         } else {
475           parentFrame = m.stackFrames[event.parent];
476           if (parentFrame === undefined)
477             throw new Error('omg');
478         }
479         frame.parentFrame = parentFrame;
480       }
481     },
482
483     /**
484      * Called by the Model after all other importers have imported their
485      * events.
486      */
487     finalizeImport: function() {
488       if (this.softwareMeasuredCpuCount_ !== undefined) {
489         this.model_.kernel.softwareMeasuredCpuCount =
490             this.softwareMeasuredCpuCount_;
491       }
492       this.createAsyncSlices_();
493       this.createFlowSlices_();
494       this.createExplicitObjects_();
495       this.createImplicitObjects_();
496     },
497
498     importSampleData: function() {
499       if (!this.sampleEvents_)
500         return;
501       var m = this.model_;
502
503       // If this is the only importer, then fake-create the threads.
504       var events = this.sampleEvents_;
505       if (this.events_.length === 0) {
506         for (var i = 0; i < events.length; i++) {
507           var event = events[i];
508           m.getOrCreateProcess(event.tid).getOrCreateThread(event.tid);
509         }
510       }
511
512       var threadsByTid = {};
513       m.getAllThreads().forEach(function(t) {
514         threadsByTid[t.tid] = t;
515       });
516
517       for (var i = 0; i < events.length; i++) {
518         var event = events[i];
519         var thread = threadsByTid[event.tid];
520         if (thread === undefined) {
521           m.importWarning({
522             type: 'sample_import_error',
523             message: 'Thread ' + events.tid + 'not found'
524           });
525           continue;
526         }
527
528         var cpu;
529         if (event.cpu !== undefined)
530           cpu = m.kernel.getOrCreateCpu(event.cpu);
531
532         var stackFrame = m.stackFrames[event.sf];
533         if (stackFrame === undefined) {
534           m.importWarning({
535             type: 'sample_import_error',
536             message: 'No frame for ' + event.sf
537           });
538           continue;
539         }
540
541         var sample = new tracing.trace_model.Sample(
542             cpu, thread,
543             event.name, event.ts / 1000,
544             stackFrame,
545             event.weight);
546         m.samples.push(sample);
547       }
548     },
549
550     /**
551      * Called by the model to join references between objects, after final model
552      * bounds have been computed.
553      */
554     joinRefs: function() {
555       this.joinObjectRefs_();
556     },
557
558     createAsyncSlices_: function() {
559       if (this.allAsyncEvents_.length === 0)
560         return;
561
562       this.allAsyncEvents_.sort(function(x, y) {
563         var d = x.event.ts - y.event.ts;
564         if (d !== 0)
565           return d;
566         return x.sequenceNumber - y.sequenceNumber;
567       });
568
569       var legacyEvents = [];
570       // Group nestable async events by ID. Events with the same ID should
571       // belong to the same parent async event.
572       var nestableAsyncEventsByID = {};
573       for (var i = 0; i < this.allAsyncEvents_.length; i++) {
574         var asyncEventState = this.allAsyncEvents_[i];
575         var event = asyncEventState.event;
576         if (event.ph === 'S' || event.ph === 'F' || event.ph === 'T' ||
577             event.ph === 'p') {
578           legacyEvents.push(asyncEventState);
579           continue;
580         }
581         if (event.name === undefined) {
582           this.model_.importWarning({
583             type: 'async_slice_parse_error',
584             message: 'Nestable async events (ph: b, e, or n) require a ' +
585                 'name parameter.'
586           });
587           continue;
588         }
589
590         var id = event.id;
591         if (id === undefined) {
592           this.model_.importWarning({
593             type: 'async_slice_parse_error',
594             message: 'Nestable async events (ph: b, e, or n) require an ' +
595                 'id parameter.'
596           });
597           continue;
598         }
599         if (nestableAsyncEventsByID[id] === undefined)
600            nestableAsyncEventsByID[id] = [];
601         nestableAsyncEventsByID[id].push(asyncEventState);
602       }
603       // Handle legacy async events.
604       this.createLegacyAsyncSlices_(legacyEvents);
605
606       // Parse nestable async events into AsyncSlices.
607       for (var id in nestableAsyncEventsByID) {
608         var eventStateEntries = nestableAsyncEventsByID[id];
609         // Stack of enclosing BEGIN events.
610         var parentStack = [];
611         for (var i = 0; i < eventStateEntries.length; ++i) {
612           var eventStateEntry = eventStateEntries[i];
613           // If this is the end of an event, match it to the start.
614           if (eventStateEntry.event.ph === 'e') {
615             // Walk up the parent stack to find the corresponding BEGIN for
616             // this END.
617             var parentIndex = -1;
618             for (var k = parentStack.length - 1; k >= 0; --k) {
619               if (parentStack[k].event.name === eventStateEntry.event.name) {
620                 parentIndex = k;
621                 break;
622               }
623             }
624             if (parentIndex === -1) {
625               // Unmatched end.
626               eventStateEntry.finished = false;
627             } else {
628               parentStack[parentIndex].end = eventStateEntry;
629               // Pop off all enclosing unmatched BEGINs util parentIndex.
630               while (parentIndex < parentStack.length) {
631                 parentStack.pop();
632               }
633             }
634           }
635           // Inherit the current parent.
636           if (parentStack.length > 0)
637             eventStateEntry.parentEntry = parentStack[parentStack.length - 1];
638           if (eventStateEntry.event.ph === 'b')
639             parentStack.push(eventStateEntry);
640         }
641         var topLevelSlices = [];
642         for (var i = 0; i < eventStateEntries.length; ++i) {
643           var eventStateEntry = eventStateEntries[i];
644           // Skip matched END, as its slice will be created when we
645           // encounter its corresponding BEGIN.
646           if (eventStateEntry.event.ph === 'e' &&
647               eventStateEntry.finished === undefined) {
648             continue;
649           }
650           var startState = undefined;
651           var endState = undefined;
652           var sliceArgs = undefined;
653           var sliceError = undefined;
654           if (eventStateEntry.event.ph === 'n') {
655             startState = eventStateEntry;
656             endState = eventStateEntry;
657             sliceArgs = eventStateEntry.event.args;
658           } else if (eventStateEntry.event.ph === 'b'){
659             if (eventStateEntry.end === undefined) {
660               // Unmatched BEGIN. End it when last event with this ID ends.
661               eventStateEntry.end =
662                   eventStateEntries[eventStateEntries.length - 1];
663               sliceError =
664                   "Slice has no matching END. End time has been adjusted.";
665               this.model_.importWarning({
666                 type: 'async_slice_parse_error',
667                 message: 'Nestable async BEGIN event at ' +
668                     eventStateEntry.event.ts + ' with name=' +
669                     eventStateEntry.event.name +
670                     ' and id=' + eventStateEntry.event.id + ' was unmatched.'
671               });
672               sliceArgs = eventStateEntry.event.args;
673             } else {
674               // Include args for both END and BEGIN for a matched pair.
675               var concatenateArguments = function(args1, args2) {
676                 if (args1.params === undefined || args2.params === undefined)
677                   return tv.concatenateObjects(args1, args2);
678                 // Make an argument object to hold the combined params.
679                 var args3 = {};
680                 args3.params = tv.concatenateObjects(args1.params, args2.params);
681                 return tv.concatenateObjects(args1, args2, args3);
682               }
683               sliceArgs = concatenateArguments(eventStateEntry.event.args,
684                                                eventStateEntry.end.event.args);
685             }
686             startState = eventStateEntry;
687             endState = eventStateEntry.end;
688           } else {
689             // Unmatched END. Start it at the first event with this ID starts.
690             sliceError =
691                 "Slice has no matching BEGIN. Start time has been adjusted.";
692             this.model_.importWarning({
693               type: 'async_slice_parse_error',
694               message: 'Nestable async END event at ' +
695                   eventStateEntry.event.ts + ' with name=' +
696                   eventStateEntry.event.name +
697                   ' and id=' + eventStateEntry.event.id + ' was unmatched.'
698             });
699             startState = eventStateEntries[0];
700             endState = eventStateEntry;
701             sliceArgs = eventStateEntry.event.args;
702           }
703
704           var asyncSliceConstructor =
705              tracing.trace_model.AsyncSlice.getConstructor(
706                 eventStateEntry.event.cat,
707                 eventStateEntry.event.name);
708           var slice = new asyncSliceConstructor(
709               eventStateEntry.event.cat,
710               eventStateEntry.event.name,
711               tv.ui.getStringColorId(eventStateEntry.event.name),
712               startState.event.ts / 1000,
713               sliceArgs,
714               (endState.event.ts - startState.event.ts) / 1000);
715
716           slice.startThread = startState.thread;
717           slice.endThread = endState.thread;
718           slice.id = id;
719           if (sliceError !== undefined)
720             slice.error = sliceError;
721           eventStateEntry.slice = slice;
722           // Add slice to parent slice if there is a parent. Otherwise threat
723           // it as a top level slice.
724           if (eventStateEntry.parentEntry !== undefined &&
725               eventStateEntry.parentEntry.slice !== undefined) {
726             if (eventStateEntry.parentEntry.slice.subSlices === undefined)
727               eventStateEntry.parentEntry.slice.subSlices = [];
728             eventStateEntry.parentEntry.slice.subSlices.push(slice);
729           } else {
730             topLevelSlices.push(slice);
731           }
732         }
733         for (var si = 0; si < topLevelSlices.length; si++) {
734           topLevelSlices[si].startThread.asyncSliceGroup.push(topLevelSlices[si]);
735         }
736       }
737     },
738
739     createLegacyAsyncSlices_: function(legacyEvents) {
740       if (legacyEvents.length === 0)
741         return;
742
743       legacyEvents.sort(function(x, y) {
744         var d = x.event.ts - y.event.ts;
745         if (d != 0)
746           return d;
747         return x.sequenceNumber - y.sequenceNumber;
748       });
749
750       var asyncEventStatesByNameThenID = {};
751
752       for (var i = 0; i < legacyEvents.length; i++) {
753         var asyncEventState = legacyEvents[i];
754
755         var event = asyncEventState.event;
756         var name = event.name;
757         if (name === undefined) {
758           this.model_.importWarning({
759             type: 'async_slice_parse_error',
760             message: 'Async events (ph: S, T, p, or F) require a name ' +
761                 ' parameter.'
762           });
763           continue;
764         }
765
766         var id = event.id;
767         if (id === undefined) {
768           this.model_.importWarning({
769             type: 'async_slice_parse_error',
770             message: 'Async events (ph: S, T, p, or F) require an id parameter.'
771           });
772           continue;
773         }
774
775         // TODO(simonjam): Add a synchronous tick on the appropriate thread.
776
777         if (event.ph === 'S') {
778           if (asyncEventStatesByNameThenID[name] === undefined)
779             asyncEventStatesByNameThenID[name] = {};
780           if (asyncEventStatesByNameThenID[name][id]) {
781             this.model_.importWarning({
782               type: 'async_slice_parse_error',
783               message: 'At ' + event.ts + ', a slice of the same id ' + id +
784                   ' was alrady open.'
785             });
786             continue;
787           }
788           asyncEventStatesByNameThenID[name][id] = [];
789           asyncEventStatesByNameThenID[name][id].push(asyncEventState);
790         } else {
791           if (asyncEventStatesByNameThenID[name] === undefined) {
792             this.model_.importWarning({
793               type: 'async_slice_parse_error',
794               message: 'At ' + event.ts + ', no slice named ' + name +
795                   ' was open.'
796             });
797             continue;
798           }
799           if (asyncEventStatesByNameThenID[name][id] === undefined) {
800             this.model_.importWarning({
801               type: 'async_slice_parse_error',
802               message: 'At ' + event.ts + ', no slice named ' + name +
803                   ' with id=' + id + ' was open.'
804             });
805             continue;
806           }
807           var events = asyncEventStatesByNameThenID[name][id];
808           events.push(asyncEventState);
809
810           if (event.ph === 'F') {
811             // Create a slice from start to end.
812             var asyncSliceConstructor =
813                tracing.trace_model.AsyncSlice.getConstructor(
814                   events[0].event.cat,
815                   name);
816             var slice = new asyncSliceConstructor(
817                 events[0].event.cat,
818                 name,
819                 tv.ui.getStringColorId(name),
820                 events[0].event.ts / 1000,
821                 this.deepCopyIfNeeded_(events[0].event.args),
822                 (event.ts - events[0].event.ts) / 1000);
823             slice.startThread = events[0].thread;
824             slice.endThread = asyncEventState.thread;
825             slice.id = id;
826             slice.subSlices = [];
827
828             var stepType = events[1].event.ph;
829             var isValid = true;
830
831             // Create subSlices for each step.
832             for (var j = 1; j < events.length; ++j) {
833               var subName = name;
834               if (events[j].event.ph == 'T' || events[j].event.ph == 'p') {
835                 isValid = this.assertStepTypeMatches_(stepType, events[j]);
836                 if (!isValid)
837                   break;
838               }
839
840               var targetEvent;
841               if (stepType == 'T') {
842                 targetEvent = events[j - 1];
843               } else {
844                 targetEvent = events[j];
845               }
846
847               var subName = events[0].event.name;
848               if (targetEvent.event.ph == 'T' || targetEvent.event.ph == 'p')
849                 subName = subName + ':' + targetEvent.event.args.step;
850
851               var asyncSliceConstructor =
852                  tracing.trace_model.AsyncSlice.getConstructor(
853                     events[0].event.cat,
854                     subName);
855               var subSlice = new asyncSliceConstructor(
856                   events[0].event.cat,
857                   subName,
858                   tv.ui.getStringColorId(subName + j),
859                   events[j - 1].event.ts / 1000,
860                   tv.concatenateObjects(events[0].event.args,
861                                           targetEvent.event.args),
862                   (events[j].event.ts - events[j - 1].event.ts) / 1000);
863               subSlice.startThread = events[j - 1].thread;
864               subSlice.endThread = events[j].thread;
865               subSlice.id = id;
866
867               slice.subSlices.push(subSlice);
868
869               if (events[j].event.ph == 'F' && stepType == 'T') {
870                 // The args for the finish event go in the last subSlice.
871                 var lastSlice = slice.subSlices[slice.subSlices.length - 1];
872                 lastSlice.args = tv.concatenateObjects(lastSlice.args,
873                                                          event.args);
874               }
875             }
876
877             if (isValid) {
878               // Add |slice| to the start-thread's asyncSlices.
879               slice.startThread.asyncSliceGroup.push(slice);
880             }
881
882             delete asyncEventStatesByNameThenID[name][id];
883           }
884         }
885       }
886     },
887
888     assertStepTypeMatches_: function(stepType, event) {
889       if (stepType != event.event.ph) {
890         this.model_.importWarning({
891           type: 'async_slice_parse_error',
892           message: 'At ' + event.event.ts + ', a slice named ' +
893               event.event.name + ' with id=' + event.event.id +
894               ' had both begin and end steps, which is not allowed.'
895         });
896         return false;
897       }
898       return true;
899     },
900
901     createFlowSlices_: function() {
902       if (this.allFlowEvents_.length === 0)
903         return;
904
905       this.allFlowEvents_.sort(function(x, y) {
906         var d = x.event.ts - y.event.ts;
907         if (d != 0)
908           return d;
909         return x.sequenceNumber - y.sequenceNumber;
910       });
911
912       var flowIdToEvent = {};
913       for (var i = 0; i < this.allFlowEvents_.length; ++i) {
914         var data = this.allFlowEvents_[i];
915         var event = data.event;
916         var thread = data.thread;
917
918         if (event.name === undefined) {
919           this.model_.importWarning({
920             type: 'flow_slice_parse_error',
921             message: 'Flow events (ph: s, t or f) require a name parameter.'
922           });
923           continue;
924         }
925
926         if (event.id === undefined) {
927           this.model_.importWarning({
928             type: 'flow_slice_parse_error',
929             message: 'Flow events (ph: s, t or f) require an id parameter.'
930           });
931           continue;
932         }
933
934         var slice = new tracing.trace_model.FlowEvent(
935             event.cat,
936             event.id,
937             event.name,
938             tv.ui.getStringColorId(event.name),
939             event.ts / 1000,
940             this.deepCopyIfNeeded_(event.args));
941         thread.sliceGroup.pushSlice(slice);
942
943         if (event.ph === 's') {
944           if (flowIdToEvent[event.id] !== undefined) {
945             this.model_.importWarning({
946               type: 'flow_slice_start_error',
947               message: 'event id ' + event.id + ' already seen when ' +
948                   'encountering start of flow event.'});
949           }
950           flowIdToEvent[event.id] = slice;
951
952         } else if (event.ph === 't' || event.ph === 'f') {
953           var flowPosition = flowIdToEvent[event.id];
954           if (flowPosition === undefined) {
955             this.model_.importWarning({
956               type: 'flow_slice_ordering_error',
957               message: 'Found flow phase ' + event.ph + ' for id: ' + event.id +
958                   ' but no flow start found.'
959             });
960             continue;
961           }
962           this.model_.flowEvents.push([flowPosition, slice]);
963
964           if (flowPosition)
965             flowPosition.nextFlowEvent = slice;
966           if (slice)
967             slice.previousFlowEvent = flowPosition;
968
969           if (event.ph === 'f') {
970             flowIdToEvent[event.id] = undefined;
971           } else {
972             // Make this slice the next start event in this flow.
973             flowIdToEvent[event.id] = slice;
974           }
975         }
976       }
977     },
978
979     /**
980      * This function creates objects described via the N, D, and O phase
981      * events.
982      */
983     createExplicitObjects_: function() {
984       if (this.allObjectEvents_.length == 0)
985         return;
986
987       function processEvent(objectEventState) {
988         var event = objectEventState.event;
989         var thread = objectEventState.thread;
990         if (event.name === undefined) {
991           this.model_.importWarning({
992             type: 'object_parse_error',
993             message: 'While processing ' + JSON.stringify(event) + ': ' +
994                 'Object events require an name parameter.'
995           });
996         }
997
998         if (event.id === undefined) {
999           this.model_.importWarning({
1000             type: 'object_parse_error',
1001             message: 'While processing ' + JSON.stringify(event) + ': ' +
1002                 'Object events require an id parameter.'
1003           });
1004         }
1005         var process = thread.parent;
1006         var ts = event.ts / 1000;
1007         var instance;
1008         if (event.ph == 'N') {
1009           try {
1010             instance = process.objects.idWasCreated(
1011                 event.id, event.cat, event.name, ts);
1012           } catch (e) {
1013             this.model_.importWarning({
1014               type: 'object_parse_error',
1015               message: 'While processing create of ' +
1016                   event.id + ' at ts=' + ts + ': ' + e
1017             });
1018             return;
1019           }
1020         } else if (event.ph == 'O') {
1021           if (event.args.snapshot === undefined) {
1022             this.model_.importWarning({
1023               type: 'object_parse_error',
1024               message: 'While processing ' + event.id + ' at ts=' + ts + ': ' +
1025                   'Snapshots must have args: {snapshot: ...}'
1026             });
1027             return;
1028           }
1029           var snapshot;
1030           try {
1031             var args = this.deepCopyIfNeeded_(event.args.snapshot);
1032             var cat;
1033             if (args.cat) {
1034               cat = args.cat;
1035               delete args.cat;
1036             } else {
1037               cat = event.cat;
1038             }
1039
1040             var baseTypename;
1041             if (args.base_type) {
1042               baseTypename = args.base_type;
1043               delete args.base_type;
1044             } else {
1045               baseTypename = undefined;
1046             }
1047             snapshot = process.objects.addSnapshot(
1048                 event.id, cat, event.name, ts,
1049                 args, baseTypename);
1050             snapshot.snapshottedOnThread = thread;
1051           } catch (e) {
1052             this.model_.importWarning({
1053               type: 'object_parse_error',
1054               message: 'While processing snapshot of ' +
1055                   event.id + ' at ts=' + ts + ': ' + e
1056             });
1057             return;
1058           }
1059           instance = snapshot.objectInstance;
1060         } else if (event.ph == 'D') {
1061           try {
1062             instance = process.objects.idWasDeleted(
1063                 event.id, event.cat, event.name, ts);
1064           } catch (e) {
1065             this.model_.importWarning({
1066               type: 'object_parse_error',
1067               message: 'While processing delete of ' +
1068                   event.id + ' at ts=' + ts + ': ' + e
1069             });
1070             return;
1071           }
1072         }
1073
1074         if (instance)
1075           instance.colorId = tv.ui.getStringColorId(instance.typeName);
1076       }
1077
1078       this.allObjectEvents_.sort(function(x, y) {
1079         var d = x.event.ts - y.event.ts;
1080         if (d != 0)
1081           return d;
1082         return x.sequenceNumber - y.sequenceNumber;
1083       });
1084
1085       var allObjectEvents = this.allObjectEvents_;
1086       for (var i = 0; i < allObjectEvents.length; i++) {
1087         var objectEventState = allObjectEvents[i];
1088         try {
1089           processEvent.call(this, objectEventState);
1090         } catch (e) {
1091           this.model_.importWarning({
1092             type: 'object_parse_error',
1093             message: e.message
1094           });
1095         }
1096       }
1097     },
1098
1099     createImplicitObjects_: function() {
1100       tv.iterItems(this.model_.processes, function(pid, process) {
1101         this.createImplicitObjectsForProcess_(process);
1102       }, this);
1103     },
1104
1105     // Here, we collect all the snapshots that internally contain a
1106     // Javascript-level object inside their args list that has an "id" field,
1107     // and turn that into a snapshot of the instance referred to by id.
1108     createImplicitObjectsForProcess_: function(process) {
1109
1110       function processField(referencingObject,
1111                             referencingObjectFieldName,
1112                             referencingObjectFieldValue,
1113                             containingSnapshot) {
1114         if (!referencingObjectFieldValue)
1115           return;
1116
1117         if (referencingObjectFieldValue instanceof
1118             tracing.trace_model.ObjectSnapshot)
1119           return null;
1120         if (referencingObjectFieldValue.id === undefined)
1121           return;
1122
1123         var implicitSnapshot = referencingObjectFieldValue;
1124
1125         var rawId = implicitSnapshot.id;
1126         var m = /(.+)\/(.+)/.exec(rawId);
1127         if (!m)
1128           throw new Error('Implicit snapshots must have names.');
1129         delete implicitSnapshot.id;
1130         var name = m[1];
1131         var id = m[2];
1132         var res;
1133
1134         var cat;
1135         if (implicitSnapshot.cat !== undefined)
1136           cat = implicitSnapshot.cat;
1137         else
1138           cat = containingSnapshot.objectInstance.category;
1139
1140         var baseTypename;
1141         if (implicitSnapshot.base_type)
1142           baseTypename = implicitSnapshot.base_type;
1143         else
1144           baseTypename = undefined;
1145
1146         try {
1147           res = process.objects.addSnapshot(
1148               id, cat,
1149               name, containingSnapshot.ts,
1150               implicitSnapshot, baseTypename);
1151         } catch (e) {
1152           this.model_.importWarning({
1153             type: 'object_snapshot_parse_error',
1154             message: 'While processing implicit snapshot of ' +
1155                 rawId + ' at ts=' + containingSnapshot.ts + ': ' + e
1156           });
1157           return;
1158         }
1159         res.objectInstance.hasImplicitSnapshots = true;
1160         res.containingSnapshot = containingSnapshot;
1161         res.snapshottedOnThread = containingSnapshot.snapshottedOnThread;
1162         referencingObject[referencingObjectFieldName] = res;
1163         if (!(res instanceof tracing.trace_model.ObjectSnapshot))
1164           throw new Error('Created object must be instanceof snapshot');
1165         return res.args;
1166       }
1167
1168       /**
1169        * Iterates over the fields in the object, calling func for every
1170        * field/value found.
1171        *
1172        * @return {object} If the function does not want the field's value to be
1173        * iterated, return null. If iteration of the field value is desired, then
1174        * return either undefined (if the field value did not change) or the new
1175        * field value if it was changed.
1176        */
1177       function iterObject(object, func, containingSnapshot, thisArg) {
1178         if (!(object instanceof Object))
1179           return;
1180
1181         if (object instanceof Array) {
1182           for (var i = 0; i < object.length; i++) {
1183             var res = func.call(thisArg, object, i, object[i],
1184                                 containingSnapshot);
1185             if (res === null)
1186               continue;
1187             if (res)
1188               iterObject(res, func, containingSnapshot, thisArg);
1189             else
1190               iterObject(object[i], func, containingSnapshot, thisArg);
1191           }
1192           return;
1193         }
1194
1195         for (var key in object) {
1196           var res = func.call(thisArg, object, key, object[key],
1197                               containingSnapshot);
1198           if (res === null)
1199             continue;
1200           if (res)
1201             iterObject(res, func, containingSnapshot, thisArg);
1202           else
1203             iterObject(object[key], func, containingSnapshot, thisArg);
1204         }
1205       }
1206
1207       // TODO(nduca): We may need to iterate the instances in sorted order by
1208       // creationTs.
1209       process.objects.iterObjectInstances(function(instance) {
1210         instance.snapshots.forEach(function(snapshot) {
1211           if (snapshot.args.id !== undefined)
1212             throw new Error('args cannot have an id field inside it');
1213           iterObject(snapshot.args, processField, snapshot, this);
1214         }, this);
1215       }, this);
1216     },
1217
1218     joinObjectRefs_: function() {
1219       tv.iterItems(this.model_.processes, function(pid, process) {
1220         this.joinObjectRefsForProcess_(process);
1221       }, this);
1222     },
1223
1224     joinObjectRefsForProcess_: function(process) {
1225       // Iterate the world, looking for id_refs
1226       var patchupsToApply = [];
1227       tv.iterItems(process.threads, function(tid, thread) {
1228         thread.asyncSliceGroup.slices.forEach(function(item) {
1229           this.searchItemForIDRefs_(
1230               patchupsToApply, process.objects, 'start', item);
1231         }, this);
1232         thread.sliceGroup.slices.forEach(function(item) {
1233           this.searchItemForIDRefs_(
1234               patchupsToApply, process.objects, 'start', item);
1235         }, this);
1236       }, this);
1237       process.objects.iterObjectInstances(function(instance) {
1238         instance.snapshots.forEach(function(item) {
1239           this.searchItemForIDRefs_(
1240               patchupsToApply, process.objects, 'ts', item);
1241         }, this);
1242       }, this);
1243
1244       // Change all the fields pointing at id_refs to their real values.
1245       patchupsToApply.forEach(function(patchup) {
1246         patchup.object[patchup.field] = patchup.value;
1247       });
1248     },
1249
1250     searchItemForIDRefs_: function(patchupsToApply, objectCollection,
1251                                    itemTimestampField, item) {
1252       if (!item.args)
1253         throw new Error('item is missing its args');
1254
1255       function handleField(object, fieldName, fieldValue) {
1256         if (!fieldValue || (!fieldValue.id_ref && !fieldValue.idRef))
1257           return;
1258
1259         var id = fieldValue.id_ref || fieldValue.idRef;
1260         var ts = item[itemTimestampField];
1261         var snapshot = objectCollection.getSnapshotAt(id, ts);
1262         if (!snapshot)
1263           return;
1264
1265         // We have to delay the actual change to the new value until after all
1266         // refs have been located. Otherwise, we could end up recursing in
1267         // ways we definitely didn't intend.
1268         patchupsToApply.push({object: object,
1269           field: fieldName,
1270           value: snapshot});
1271       }
1272       function iterObjectFieldsRecursively(object) {
1273         if (!(object instanceof Object))
1274           return;
1275
1276         if ((object instanceof tracing.trace_model.ObjectSnapshot) ||
1277             (object instanceof Float32Array) ||
1278             (object instanceof tv.Quad))
1279           return;
1280
1281         if (object instanceof Array) {
1282           for (var i = 0; i < object.length; i++) {
1283             handleField(object, i, object[i]);
1284             iterObjectFieldsRecursively(object[i]);
1285           }
1286           return;
1287         }
1288
1289         for (var key in object) {
1290           var value = object[key];
1291           handleField(object, key, value);
1292           iterObjectFieldsRecursively(value);
1293         }
1294       }
1295
1296       iterObjectFieldsRecursively(item.args);
1297     }
1298   };
1299
1300   tracing.TraceModel.registerImporter(TraceEventImporter);
1301
1302   return {
1303     TraceEventImporter: TraceEventImporter
1304   };
1305 });
1306 </script>