120765494933e015916eb8261c9c0d3afb45f7cc
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / trace_viewer / tracing / importer / trace_event_importer.js
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 'use strict';
6
7 /**
8  * @fileoverview TraceEventImporter imports TraceEvent-formatted data
9  * into the provided model.
10  */
11 tvcm.require('tvcm.quad');
12 tvcm.require('tracing.trace_model');
13 tvcm.require('tracing.color_scheme');
14 tvcm.require('tracing.importer.importer');
15 tvcm.require('tracing.trace_model.instant_event');
16 tvcm.require('tracing.trace_model.flow_event');
17 tvcm.require('tracing.trace_model.counter_series');
18
19 tvcm.exportTo('tracing.importer', function() {
20
21   var Importer = tracing.importer.Importer;
22
23   function deepCopy(value) {
24     if (!(value instanceof Object)) {
25       if (value === undefined || value === null)
26         return value;
27       if (typeof value == 'string')
28         return value.substring();
29       if (typeof value == 'boolean')
30         return value;
31       if (typeof value == 'number')
32         return value;
33       throw new Error('Unrecognized: ' + typeof value);
34     }
35
36     var object = value;
37     if (object instanceof Array) {
38       var res = new Array(object.length);
39       for (var i = 0; i < object.length; i++)
40         res[i] = deepCopy(object[i]);
41       return res;
42     }
43
44     if (object.__proto__ != Object.prototype)
45       throw new Error('Can only clone simple types');
46     var res = {};
47     for (var key in object) {
48       res[key] = deepCopy(object[key]);
49     }
50     return res;
51   }
52
53   function TraceEventImporter(model, eventData) {
54     this.importPriority = 1;
55     this.model_ = model;
56     this.events_ = undefined;
57     this.systemTraceEvents_ = undefined;
58     this.eventsWereFromString_ = false;
59     this.allAsyncEvents_ = [];
60     this.allFlowEvents_ = [];
61     this.allObjectEvents_ = [];
62
63     if (typeof(eventData) === 'string' || eventData instanceof String) {
64       // If the event data begins with a [, then we know it should end with a ].
65       // The reason we check for this is because some tracing implementations
66       // cannot guarantee that a ']' gets written to the trace file. So, we are
67       // forgiving and if this is obviously the case, we fix it up before
68       // throwing the string at JSON.parse.
69       if (eventData[0] === '[') {
70         eventData = eventData.replace(/[\r|\n]*$/, '')
71                              .replace(/\s*,\s*$/, '');
72         if (eventData[eventData.length - 1] !== ']')
73           eventData = eventData + ']';
74       }
75       this.events_ = JSON.parse(eventData);
76       this.eventsWereFromString_ = true;
77     } else {
78       this.events_ = eventData;
79     }
80
81     // Some trace_event implementations put the actual trace events
82     // inside a container. E.g { ... , traceEvents: [ ] }
83     // If we see that, just pull out the trace events.
84     if (this.events_.traceEvents) {
85       var container = this.events_;
86       this.events_ = this.events_.traceEvents;
87
88       // Some trace_event implementations put linux_perf_importer traces as a
89       // huge string inside container.systemTraceEvents. If we see that, pull it
90       // out. It will be picked up by extractSubtraces later on.
91       this.systemTraceEvents_ = container.systemTraceEvents;
92
93       // Any other fields in the container should be treated as metadata.
94       for (var fieldName in container) {
95         if (fieldName === 'traceEvents' || fieldName === 'systemTraceEvents')
96           continue;
97         this.model_.metadata.push({name: fieldName,
98           value: container[fieldName]});
99       }
100     }
101   }
102
103   /**
104    * @return {boolean} Whether obj is a TraceEvent array.
105    */
106   TraceEventImporter.canImport = function(eventData) {
107     // May be encoded JSON. But we dont want to parse it fully yet.
108     // Use a simple heuristic:
109     //   - eventData that starts with [ are probably trace_event
110     //   - eventData that starts with { are probably trace_event
111     // May be encoded JSON. Treat files that start with { as importable by us.
112     if (typeof(eventData) === 'string' || eventData instanceof String) {
113       return eventData[0] == '{' || eventData[0] == '[';
114     }
115
116     // Might just be an array of events
117     if (eventData instanceof Array && eventData.length && eventData[0].ph)
118       return true;
119
120     // Might be an object with a traceEvents field in it.
121     if (eventData.traceEvents)
122       return eventData.traceEvents instanceof Array &&
123           eventData.traceEvents[0].ph;
124
125     return false;
126   };
127
128   TraceEventImporter.prototype = {
129
130     __proto__: Importer.prototype,
131
132     extractSubtraces: function() {
133       var tmp = this.systemTraceEvents_;
134       this.systemTraceEvents_ = undefined;
135       return tmp ? [tmp] : [];
136     },
137
138     /**
139      * Deep copying is only needed if the trace was given to us as events.
140      */
141     deepCopyIfNeeded_: function(obj) {
142       if (obj === undefined)
143         obj = {};
144       if (this.eventsWereFromString_)
145         return obj;
146       return deepCopy(obj);
147     },
148
149     /**
150      * Helper to process an async event.
151      */
152     processAsyncEvent: function(event) {
153       var thread = this.model_.getOrCreateProcess(event.pid).
154           getOrCreateThread(event.tid);
155       this.allAsyncEvents_.push({
156         sequenceNumber: this.allAsyncEvents_.length,
157         event: event,
158         thread: thread});
159     },
160
161     /**
162      * Helper to process a flow event.
163      */
164     processFlowEvent: function(event) {
165       var thread = this.model_.getOrCreateProcess(event.pid).
166           getOrCreateThread(event.tid);
167       this.allFlowEvents_.push({
168         sequenceNumber: this.allFlowEvents_.length,
169         event: event,
170         thread: thread
171       });
172     },
173
174     /**
175      * Helper that creates and adds samples to a Counter object based on
176      * 'C' phase events.
177      */
178     processCounterEvent: function(event) {
179       var ctr_name;
180       if (event.id !== undefined)
181         ctr_name = event.name + '[' + event.id + ']';
182       else
183         ctr_name = event.name;
184
185       var ctr = this.model_.getOrCreateProcess(event.pid)
186           .getOrCreateCounter(event.cat, ctr_name);
187
188       // Initialize the counter's series fields if needed.
189       if (ctr.numSeries === 0) {
190         for (var seriesName in event.args) {
191           ctr.addSeries(new tracing.trace_model.CounterSeries(seriesName,
192               tvcm.ui.getStringColorId(ctr.name + '.' + seriesName)));
193         }
194
195         if (ctr.numSeries === 0) {
196           this.model_.importWarning({
197             type: 'counter_parse_error',
198             message: 'Expected counter ' + event.name +
199                 ' to have at least one argument to use as a value.'
200           });
201
202           // Drop the counter.
203           delete ctr.parent.counters[ctr.name];
204           return;
205         }
206       }
207
208       var ts = event.ts / 1000;
209       ctr.series.forEach(function(series) {
210         var val = event.args[series.name] ? event.args[series.name] : 0;
211         series.addSample(ts, val);
212       });
213     },
214
215     processObjectEvent: function(event) {
216       var thread = this.model_.getOrCreateProcess(event.pid).
217           getOrCreateThread(event.tid);
218       this.allObjectEvents_.push({
219         sequenceNumber: this.allObjectEvents_.length,
220         event: event,
221         thread: thread});
222     },
223
224     processDurationEvent: function(event) {
225       var thread = this.model_.getOrCreateProcess(event.pid)
226         .getOrCreateThread(event.tid);
227       if (!thread.sliceGroup.isTimestampValidForBeginOrEnd(event.ts / 1000)) {
228         this.model_.importWarning({
229           type: 'duration_parse_error',
230           message: 'Timestamps are moving backward.'
231         });
232         return;
233       }
234
235       if (event.ph == 'B') {
236         thread.sliceGroup.beginSlice(event.cat, event.name, event.ts / 1000,
237                                      this.deepCopyIfNeeded_(event.args),
238                                      event.tts / 1000);
239       } else {
240         if (!thread.sliceGroup.openSliceCount) {
241           this.model_.importWarning({
242             type: 'duration_parse_error',
243             message: 'E phase event without a matching B phase event.'
244           });
245           return;
246         }
247
248         var slice = thread.sliceGroup.endSlice(event.ts / 1000,
249                                                event.tts / 1000);
250         if (event.name && slice.title != event.name) {
251           this.model_.importWarning({
252             type: 'title_match_error',
253             message: 'Titles do not match. Title is ' +
254                 slice.title + ' in openSlice, and is ' +
255                 event.name + ' in endSlice'
256           });
257         }
258         for (var arg in event.args) {
259           if (slice.args[arg] !== undefined) {
260             this.model_.importWarning({
261               type: 'duration_parse_error',
262               message: 'Both the B and E phases of ' + slice.name +
263                   ' provided values for argument ' + arg + '.' +
264                   ' The value of the E phase event will be used.'
265             });
266           }
267           slice.args[arg] = this.deepCopyIfNeeded_(event.args[arg]);
268         }
269       }
270     },
271
272     processCompleteEvent: function(event) {
273       var thread = this.model_.getOrCreateProcess(event.pid)
274           .getOrCreateThread(event.tid);
275       thread.sliceGroup.pushCompleteSlice(event.cat, event.name,
276           event.ts / 1000,
277           event.dur === undefined ? undefined : event.dur / 1000,
278           event.tts === undefined ? undefined : event.tts / 1000,
279           event.tdur === undefined ? undefined : event.tdur / 1000,
280           this.deepCopyIfNeeded_(event.args));
281     },
282
283     processMetadataEvent: function(event) {
284       if (event.name == 'process_name') {
285         var process = this.model_.getOrCreateProcess(event.pid);
286         process.name = event.args.name;
287       } else if (event.name == 'process_labels') {
288         var process = this.model_.getOrCreateProcess(event.pid);
289         var labels = event.args.labels.split(',');
290         for (var i = 0; i < labels.length; i++)
291           process.addLabelIfNeeded(labels[i]);
292       } else if (event.name == 'process_sort_index') {
293         var process = this.model_.getOrCreateProcess(event.pid);
294         process.sortIndex = event.args.sort_index;
295       } else if (event.name == 'thread_name') {
296         var thread = this.model_.getOrCreateProcess(event.pid).
297             getOrCreateThread(event.tid);
298         thread.name = event.args.name;
299       } else if (event.name == 'thread_sort_index') {
300         var thread = this.model_.getOrCreateProcess(event.pid).
301             getOrCreateThread(event.tid);
302         thread.sortIndex = event.args.sort_index;
303       } else {
304         this.model_.importWarning({
305           type: 'metadata_parse_error',
306           message: 'Unrecognized metadata name: ' + event.name
307         });
308       }
309     },
310
311     // Treat an Instant event as a duration 0 slice.
312     // SliceTrack's redraw() knows how to handle this.
313     processInstantEvent: function(event) {
314       var constructor;
315       switch (event.s) {
316         case 'g':
317           constructor = tracing.trace_model.GlobalInstantEvent;
318           break;
319         case 'p':
320           constructor = tracing.trace_model.ProcessInstantEvent;
321           break;
322         case 't':
323           // fall through
324         default:
325           // Default to thread to support old style input files.
326           constructor = tracing.trace_model.ThreadInstantEvent;
327           break;
328       }
329
330       var colorId = tvcm.ui.getStringColorId(event.name);
331       var instantEvent = new constructor(event.cat, event.name,
332           colorId, event.ts / 1000, this.deepCopyIfNeeded_(event.args));
333
334       switch (instantEvent.type) {
335         case tracing.trace_model.InstantEventType.GLOBAL:
336           this.model_.pushInstantEvent(instantEvent);
337           break;
338
339         case tracing.trace_model.InstantEventType.PROCESS:
340           var process = this.model_.getOrCreateProcess(event.pid);
341           process.pushInstantEvent(instantEvent);
342           break;
343
344         case tracing.trace_model.InstantEventType.THREAD:
345           var thread = this.model_.getOrCreateProcess(event.pid)
346               .getOrCreateThread(event.tid);
347           thread.sliceGroup.pushInstantEvent(instantEvent);
348           break;
349         default:
350           throw new Error('Unknown instant event type: ' + event.s);
351       }
352     },
353
354     processSampleEvent: function(event) {
355       var thread = this.model_.getOrCreateProcess(event.pid)
356         .getOrCreateThread(event.tid);
357       thread.addSample(event.cat, event.name, event.ts / 1000,
358                        this.deepCopyIfNeeded_(event.args));
359     },
360
361     /**
362      * Walks through the events_ list and outputs the structures discovered to
363      * model_.
364      */
365     importEvents: function() {
366       var events = this.events_;
367       for (var eI = 0; eI < events.length; eI++) {
368         var event = events[eI];
369         if (event.ph === 'B' || event.ph === 'E') {
370           this.processDurationEvent(event);
371
372         } else if (event.ph === 'X') {
373           this.processCompleteEvent(event);
374
375         } else if (event.ph === 'S' || event.ph === 'F' || event.ph === 'T' ||
376                    event.ph === 'p') {
377           this.processAsyncEvent(event);
378
379         // Note, I is historic. The instant event marker got changed, but we
380         // want to support loading old trace files so we have both I and i.
381         } else if (event.ph == 'I' || event.ph == 'i') {
382           this.processInstantEvent(event);
383
384         } else if (event.ph == 'P') {
385           this.processSampleEvent(event);
386
387         } else if (event.ph == 'C') {
388           this.processCounterEvent(event);
389
390         } else if (event.ph == 'M') {
391           this.processMetadataEvent(event);
392
393         } else if (event.ph === 'N' || event.ph === 'D' || event.ph === 'O') {
394           this.processObjectEvent(event);
395
396         } else if (event.ph === 's' || event.ph === 't' || event.ph === 'f') {
397           this.processFlowEvent(event);
398
399         } else {
400           this.model_.importWarning({
401             type: 'parse_error',
402             message: 'Unrecognized event phase: ' +
403                 event.ph + ' (' + event.name + ')'
404           });
405         }
406       }
407     },
408
409     /**
410      * Called by the Model after all other importers have imported their
411      * events.
412      */
413     finalizeImport: function() {
414       this.createSubSlices_();
415       this.createAsyncSlices_();
416       this.createFlowSlices_();
417       this.createExplicitObjects_();
418       this.createImplicitObjects_();
419     },
420
421     /**
422      * Called by the model to join references between objects, after final model
423      * bounds have been computed.
424      */
425     joinRefs: function() {
426       this.joinObjectRefs_();
427     },
428
429     createSubSlices_: function() {
430       tvcm.iterItems(this.model_.processes, function(pid, process) {
431         tvcm.iterItems(process.threads, function(tid, thread) {
432           thread.createSubSlices();
433         }, this);
434       }, this);
435     },
436
437     createAsyncSlices_: function() {
438       if (this.allAsyncEvents_.length === 0)
439         return;
440
441       this.allAsyncEvents_.sort(function(x, y) {
442         var d = x.event.ts - y.event.ts;
443         if (d != 0)
444           return d;
445         return x.sequenceNumber - y.sequenceNumber;
446       });
447
448       var asyncEventStatesByNameThenID = {};
449
450       var allAsyncEvents = this.allAsyncEvents_;
451       for (var i = 0; i < allAsyncEvents.length; i++) {
452         var asyncEventState = allAsyncEvents[i];
453
454         var event = asyncEventState.event;
455         var name = event.name;
456         if (name === undefined) {
457           this.model_.importWarning({
458             type: 'async_slice_parse_error',
459             message: 'Async events (ph: S, T, p, or F) require a name ' +
460                 ' parameter.'
461           });
462           continue;
463         }
464
465         var id = event.id;
466         if (id === undefined) {
467           this.model_.importWarning({
468             type: 'async_slice_parse_error',
469             message: 'Async events (ph: S, T, p, or F) require an id parameter.'
470           });
471           continue;
472         }
473
474         // TODO(simonjam): Add a synchronous tick on the appropriate thread.
475
476         if (event.ph === 'S') {
477           if (asyncEventStatesByNameThenID[name] === undefined)
478             asyncEventStatesByNameThenID[name] = {};
479           if (asyncEventStatesByNameThenID[name][id]) {
480             this.model_.importWarning({
481               type: 'async_slice_parse_error',
482               message: 'At ' + event.ts + ', a slice of the same id ' + id +
483                   ' was alrady open.'
484             });
485             continue;
486           }
487           asyncEventStatesByNameThenID[name][id] = [];
488           asyncEventStatesByNameThenID[name][id].push(asyncEventState);
489         } else {
490           if (asyncEventStatesByNameThenID[name] === undefined) {
491             this.model_.importWarning({
492               type: 'async_slice_parse_error',
493               message: 'At ' + event.ts + ', no slice named ' + name +
494                   ' was open.'
495             });
496             continue;
497           }
498           if (asyncEventStatesByNameThenID[name][id] === undefined) {
499             this.model_.importWarning({
500               type: 'async_slice_parse_error',
501               message: 'At ' + event.ts + ', no slice named ' + name +
502                   ' with id=' + id + ' was open.'
503             });
504             continue;
505           }
506           var events = asyncEventStatesByNameThenID[name][id];
507           events.push(asyncEventState);
508
509           if (event.ph === 'F') {
510             // Create a slice from start to end.
511             var slice = new tracing.trace_model.AsyncSlice(
512                 events[0].event.cat,
513                 name,
514                 tvcm.ui.getStringColorId(name),
515                 events[0].event.ts / 1000);
516
517             slice.duration = (event.ts / 1000) - (events[0].event.ts / 1000);
518
519             slice.startThread = events[0].thread;
520             slice.endThread = asyncEventState.thread;
521             slice.id = id;
522             slice.args = this.deepCopyIfNeeded_(events[0].event.args);
523             slice.subSlices = [];
524
525             var stepType = events[1].event.ph;
526             var isValid = true;
527
528             // Create subSlices for each step.
529             for (var j = 1; j < events.length; ++j) {
530               var subName = name;
531               if (events[j].event.ph == 'T' || events[j].event.ph == 'p') {
532                 isValid = this.assertStepTypeMatches_(stepType, events[j]);
533                 if (!isValid)
534                   break;
535               }
536
537               var targetEvent;
538               if (stepType == 'T') {
539                 targetEvent = events[j - 1];
540               } else {
541                 targetEvent = events[j];
542               }
543
544               var subName = events[0].event.name;
545               if (targetEvent.event.ph == 'T' || targetEvent.event.ph == 'p')
546                 subName = subName + ':' + targetEvent.event.args.step;
547
548               var subSlice = new tracing.trace_model.AsyncSlice(
549                   events[0].event.cat,
550                   subName,
551                   tvcm.ui.getStringColorId(subName + j),
552                   events[j - 1].event.ts / 1000);
553
554               subSlice.duration =
555                   (events[j].event.ts / 1000) - (events[j - 1].event.ts / 1000);
556
557               subSlice.startThread = events[j - 1].thread;
558               subSlice.endThread = events[j].thread;
559               subSlice.id = id;
560               subSlice.args = tvcm.concatenateObjects(events[0].event.args,
561                                                       targetEvent.event.args);
562
563               slice.subSlices.push(subSlice);
564
565               if (events[j].event.ph == 'F' && stepType == 'T') {
566                 // The args for the finish event go in the last subSlice.
567                 var lastSlice = slice.subSlices[slice.subSlices.length - 1];
568                 lastSlice.args = tvcm.concatenateObjects(lastSlice.args,
569                                                          event.args);
570               }
571             }
572
573             if (isValid) {
574               // Add |slice| to the start-thread's asyncSlices.
575               slice.startThread.asyncSliceGroup.push(slice);
576             }
577
578             delete asyncEventStatesByNameThenID[name][id];
579           }
580         }
581       }
582     },
583
584     assertStepTypeMatches_: function(stepType, event) {
585       if (stepType != event.event.ph) {
586         this.model_.importWarning({
587           type: 'async_slice_parse_error',
588           message: 'At ' + event.event.ts + ', a slice named ' +
589               event.event.name + ' with id=' + event.event.id +
590               ' had both begin and end steps, which is not allowed.'
591         });
592         return false;
593       }
594       return true;
595     },
596
597     createFlowSlices_: function() {
598       if (this.allFlowEvents_.length === 0)
599         return;
600
601       this.allFlowEvents_.sort(function(x, y) {
602         var d = x.event.ts - y.event.ts;
603         if (d != 0)
604           return d;
605         return x.sequenceNumber - y.sequenceNumber;
606       });
607
608       var flowIdToEvent = {};
609       for (var i = 0; i < this.allFlowEvents_.length; ++i) {
610         var data = this.allFlowEvents_[i];
611         var event = data.event;
612         var thread = data.thread;
613
614         if (event.name === undefined) {
615           this.model_.importWarning({
616             type: 'flow_slice_parse_error',
617             message: 'Flow events (ph: s, t or f) require a name parameter.'
618           });
619           continue;
620         }
621
622         if (event.id === undefined) {
623           this.model_.importWarning({
624             type: 'flow_slice_parse_error',
625             message: 'Flow events (ph: s, t or f) require an id parameter.'
626           });
627           continue;
628         }
629
630         var slice = new tracing.trace_model.FlowEvent(
631             event.cat,
632             event.id,
633             event.name,
634             tvcm.ui.getStringColorId(event.name),
635             event.ts / 1000,
636             this.deepCopyIfNeeded_(event.args));
637         thread.sliceGroup.pushSlice(slice);
638
639         if (event.ph === 's') {
640           if (flowIdToEvent[event.id] !== undefined) {
641             this.model_.importWarning({
642               type: 'flow_slice_start_error',
643               message: 'event id ' + event.id + ' already seen when ' +
644                   'encountering start of flow event.'});
645           }
646           flowIdToEvent[event.id] = slice;
647
648         } else if (event.ph === 't' || event.ph === 'f') {
649           var flowPosition = flowIdToEvent[event.id];
650           if (flowPosition === undefined) {
651             this.model_.importWarning({
652               type: 'flow_slice_ordering_error',
653               message: 'Found flow phase ' + event.ph + ' for id: ' + event.id +
654                   ' but no flow start found.'
655             });
656             continue;
657           }
658           this.model_.flowEvents.push([flowPosition, slice]);
659
660           if (flowPosition)
661             flowPosition.nextFlowEvent = slice;
662           if (slice)
663             slice.previousFlowEvent = flowPosition;
664
665           if (event.ph === 'f') {
666             flowIdToEvent[event.id] = undefined;
667           } else {
668             // Make this slice the next start event in this flow.
669             flowIdToEvent[event.id] = slice;
670           }
671         }
672       }
673     },
674
675     /**
676      * This function creates objects described via the N, D, and O phase
677      * events.
678      */
679     createExplicitObjects_: function() {
680       if (this.allObjectEvents_.length == 0)
681         return;
682
683       function processEvent(objectEventState) {
684         var event = objectEventState.event;
685         var thread = objectEventState.thread;
686         if (event.name === undefined) {
687           this.model_.importWarning({
688             type: 'object_parse_error',
689             message: 'While processing ' + JSON.stringify(event) + ': ' +
690                 'Object events require an name parameter.'
691           });
692         }
693
694         if (event.id === undefined) {
695           this.model_.importWarning({
696             type: 'object_parse_error',
697             message: 'While processing ' + JSON.stringify(event) + ': ' +
698                 'Object events require an id parameter.'
699           });
700         }
701         var process = thread.parent;
702         var ts = event.ts / 1000;
703         var instance;
704         if (event.ph == 'N') {
705           try {
706             instance = process.objects.idWasCreated(
707                 event.id, event.cat, event.name, ts);
708           } catch (e) {
709             this.model_.importWarning({
710               type: 'object_parse_error',
711               message: 'While processing create of ' +
712                   event.id + ' at ts=' + ts + ': ' + e
713             });
714             return;
715           }
716         } else if (event.ph == 'O') {
717           if (event.args.snapshot === undefined) {
718             this.model_.importWarning({
719               type: 'object_parse_error',
720               message: 'While processing ' + event.id + ' at ts=' + ts + ': ' +
721                   'Snapshots must have args: {snapshot: ...}'
722             });
723             return;
724           }
725           var snapshot;
726           try {
727             var args = this.deepCopyIfNeeded_(event.args.snapshot);
728             var cat;
729             if (args.cat) {
730               cat = args.cat;
731               delete args.cat;
732             } else {
733               cat = event.cat;
734             }
735
736             var baseTypename;
737             if (args.base_type) {
738               baseTypename = args.base_type;
739               delete args.base_type;
740             } else {
741               baseTypename = undefined;
742             }
743             snapshot = process.objects.addSnapshot(
744                 event.id, cat, event.name, ts,
745                 args, baseTypename);
746           } catch (e) {
747             this.model_.importWarning({
748               type: 'object_parse_error',
749               message: 'While processing snapshot of ' +
750                   event.id + ' at ts=' + ts + ': ' + e
751             });
752             return;
753           }
754           instance = snapshot.objectInstance;
755         } else if (event.ph == 'D') {
756           try {
757             instance = process.objects.idWasDeleted(
758                 event.id, event.cat, event.name, ts);
759           } catch (e) {
760             this.model_.importWarning({
761               type: 'object_parse_error',
762               message: 'While processing delete of ' +
763                   event.id + ' at ts=' + ts + ': ' + e
764             });
765             return;
766           }
767         }
768
769         if (instance)
770           instance.colorId = tvcm.ui.getStringColorId(instance.typeName);
771       }
772
773       this.allObjectEvents_.sort(function(x, y) {
774         var d = x.event.ts - y.event.ts;
775         if (d != 0)
776           return d;
777         return x.sequenceNumber - y.sequenceNumber;
778       });
779
780       var allObjectEvents = this.allObjectEvents_;
781       for (var i = 0; i < allObjectEvents.length; i++) {
782         var objectEventState = allObjectEvents[i];
783         try {
784           processEvent.call(this, objectEventState);
785         } catch (e) {
786           this.model_.importWarning({
787             type: 'object_parse_error',
788             message: e.message
789           });
790         }
791       }
792     },
793
794     createImplicitObjects_: function() {
795       tvcm.iterItems(this.model_.processes, function(pid, process) {
796         this.createImplicitObjectsForProcess_(process);
797       }, this);
798     },
799
800     // Here, we collect all the snapshots that internally contain a
801     // Javascript-level object inside their args list that has an "id" field,
802     // and turn that into a snapshot of the instance referred to by id.
803     createImplicitObjectsForProcess_: function(process) {
804
805       function processField(referencingObject,
806                             referencingObjectFieldName,
807                             referencingObjectFieldValue,
808                             containingSnapshot) {
809         if (!referencingObjectFieldValue)
810           return;
811
812         if (referencingObjectFieldValue instanceof
813             tracing.trace_model.ObjectSnapshot)
814           return null;
815         if (referencingObjectFieldValue.id === undefined)
816           return;
817
818         var implicitSnapshot = referencingObjectFieldValue;
819
820         var rawId = implicitSnapshot.id;
821         var m = /(.+)\/(.+)/.exec(rawId);
822         if (!m)
823           throw new Error('Implicit snapshots must have names.');
824         delete implicitSnapshot.id;
825         var name = m[1];
826         var id = m[2];
827         var res;
828
829         var cat;
830         if (implicitSnapshot.cat !== undefined)
831           cat = implicitSnapshot.cat;
832         else
833           cat = containingSnapshot.objectInstance.category;
834
835         var baseTypename;
836         if (implicitSnapshot.base_type)
837           baseTypename = implicitSnapshot.base_type;
838         else
839           baseTypename = undefined;
840
841         try {
842           res = process.objects.addSnapshot(
843               id, cat,
844               name, containingSnapshot.ts,
845               implicitSnapshot, baseTypename);
846         } catch (e) {
847           this.model_.importWarning({
848             type: 'object_snapshot_parse_error',
849             message: 'While processing implicit snapshot of ' +
850                 rawId + ' at ts=' + containingSnapshot.ts + ': ' + e
851           });
852           return;
853         }
854         res.objectInstance.hasImplicitSnapshots = true;
855         res.containingSnapshot = containingSnapshot;
856         referencingObject[referencingObjectFieldName] = res;
857         if (!(res instanceof tracing.trace_model.ObjectSnapshot))
858           throw new Error('Created object must be instanceof snapshot');
859         return res.args;
860       }
861
862       /**
863        * Iterates over the fields in the object, calling func for every
864        * field/value found.
865        *
866        * @return {object} If the function does not want the field's value to be
867        * iterated, return null. If iteration of the field value is desired, then
868        * return either undefined (if the field value did not change) or the new
869        * field value if it was changed.
870        */
871       function iterObject(object, func, containingSnapshot, thisArg) {
872         if (!(object instanceof Object))
873           return;
874
875         if (object instanceof Array) {
876           for (var i = 0; i < object.length; i++) {
877             var res = func.call(thisArg, object, i, object[i],
878                                 containingSnapshot);
879             if (res === null)
880               continue;
881             if (res)
882               iterObject(res, func, containingSnapshot, thisArg);
883             else
884               iterObject(object[i], func, containingSnapshot, thisArg);
885           }
886           return;
887         }
888
889         for (var key in object) {
890           var res = func.call(thisArg, object, key, object[key],
891                               containingSnapshot);
892           if (res === null)
893             continue;
894           if (res)
895             iterObject(res, func, containingSnapshot, thisArg);
896           else
897             iterObject(object[key], func, containingSnapshot, thisArg);
898         }
899       }
900
901       // TODO(nduca): We may need to iterate the instances in sorted order by
902       // creationTs.
903       process.objects.iterObjectInstances(function(instance) {
904         instance.snapshots.forEach(function(snapshot) {
905           if (snapshot.args.id !== undefined)
906             throw new Error('args cannot have an id field inside it');
907           iterObject(snapshot.args, processField, snapshot, this);
908         }, this);
909       }, this);
910     },
911
912     joinObjectRefs_: function() {
913       tvcm.iterItems(this.model_.processes, function(pid, process) {
914         this.joinObjectRefsForProcess_(process);
915       }, this);
916     },
917
918     joinObjectRefsForProcess_: function(process) {
919       // Iterate the world, looking for id_refs
920       var patchupsToApply = [];
921       tvcm.iterItems(process.threads, function(tid, thread) {
922         thread.asyncSliceGroup.slices.forEach(function(item) {
923           this.searchItemForIDRefs_(
924               patchupsToApply, process.objects, 'start', item);
925         }, this);
926         thread.sliceGroup.slices.forEach(function(item) {
927           this.searchItemForIDRefs_(
928               patchupsToApply, process.objects, 'start', item);
929         }, this);
930       }, this);
931       process.objects.iterObjectInstances(function(instance) {
932         instance.snapshots.forEach(function(item) {
933           this.searchItemForIDRefs_(
934               patchupsToApply, process.objects, 'ts', item);
935         }, this);
936       }, this);
937
938       // Change all the fields pointing at id_refs to their real values.
939       patchupsToApply.forEach(function(patchup) {
940         patchup.object[patchup.field] = patchup.value;
941       });
942     },
943
944     searchItemForIDRefs_: function(patchupsToApply, objectCollection,
945                                    itemTimestampField, item) {
946       if (!item.args)
947         throw new Error('item is missing its args');
948
949       function handleField(object, fieldName, fieldValue) {
950         if (fieldValue === undefined ||
951             (!fieldValue.id_ref && !fieldValue.idRef))
952           return;
953
954         var id = fieldValue.id_ref || fieldValue.idRef;
955         var ts = item[itemTimestampField];
956         var snapshot = objectCollection.getSnapshotAt(id, ts);
957         if (!snapshot)
958           return;
959
960         // We have to delay the actual change to the new value until after all
961         // refs have been located. Otherwise, we could end up recursing in
962         // ways we definitely didn't intend.
963         patchupsToApply.push({object: object,
964           field: fieldName,
965           value: snapshot});
966       }
967       function iterObjectFieldsRecursively(object) {
968         if (!(object instanceof Object))
969           return;
970
971         if ((object instanceof tracing.trace_model.ObjectSnapshot) ||
972             (object instanceof Float32Array) ||
973             (object instanceof tvcm.Quad))
974           return;
975
976         if (object instanceof Array) {
977           for (var i = 0; i < object.length; i++) {
978             handleField(object, i, object[i]);
979             iterObjectFieldsRecursively(object[i]);
980           }
981           return;
982         }
983
984         for (var key in object) {
985           var value = object[key];
986           handleField(object, key, value);
987           iterObjectFieldsRecursively(value);
988         }
989       }
990
991       iterObjectFieldsRecursively(item.args);
992     }
993   };
994
995   tracing.TraceModel.registerImporter(TraceEventImporter);
996
997   return {
998     TraceEventImporter: TraceEventImporter
999   };
1000 });