1<!DOCTYPE html>
2<!--
3Copyright (c) 2012 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7
8<link rel="import" href="/ui/base/color_scheme.html">
9<link rel="import" href="/base/quad.html">
10<link rel="import" href="/base/range.html">
11<link rel="import" href="/base/units/time.html">
12<link rel="import" href="/importer/importer.html">
13<link rel="import" href="/model/attribute.html">
14<link rel="import" href="/model/comment_box_annotation.html">
15<link rel="import" href="/model/instant_event.html">
16<link rel="import" href="/model/flow_event.html">
17<link rel="import" href="/model/counter_series.html">
18<link rel="import" href="/model/slice_group.html">
19<link rel="import" href="/model/global_memory_dump.html">
20<link rel="import" href="/model/memory_allocator_dump.html">
21<link rel="import" href="/model/process_memory_dump.html">
22<link rel="import" href="/model/rect_annotation.html">
23<link rel="import" href="/model/x_marker_annotation.html">
24<link rel="import" href="/model/model.html">
25
26<script>
27'use strict';
28
29/**
30 * @fileoverview TraceEventImporter imports TraceEvent-formatted data
31 * into the provided model.
32 */
33tr.exportTo('tr.e.importer', function() {
34
35  var Importer = tr.importer.Importer;
36
37  function deepCopy(value) {
38    if (!(value instanceof Object)) {
39      if (value === undefined || value === null)
40        return value;
41      if (typeof value == 'string')
42        return value.substring();
43      if (typeof value == 'boolean')
44        return value;
45      if (typeof value == 'number')
46        return value;
47      throw new Error('Unrecognized: ' + typeof value);
48    }
49
50    var object = value;
51    if (object instanceof Array) {
52      var res = new Array(object.length);
53      for (var i = 0; i < object.length; i++)
54        res[i] = deepCopy(object[i]);
55      return res;
56    }
57
58    if (object.__proto__ != Object.prototype)
59      throw new Error('Can only clone simple types');
60    var res = {};
61    for (var key in object) {
62      res[key] = deepCopy(object[key]);
63    }
64    return res;
65  }
66
67  function getEventColor(event, opt_customName) {
68    if (event.cname)
69      return tr.ui.b.getColorIdForReservedName(event.cname);
70    else if (opt_customName || event.name) {
71      return tr.ui.b.getColorIdForGeneralPurposeString(
72          opt_customName || event.name);
73    }
74  }
75
76  var timestampFromUs = tr.b.units.Time.timestampFromUs;
77  var maybeTimestampFromUs = tr.b.units.Time.maybeTimestampFromUs;
78
79  function TraceEventImporter(model, eventData) {
80    this.importPriority = 1;
81    this.model_ = model;
82    this.events_ = undefined;
83    this.sampleEvents_ = undefined;
84    this.stackFrameEvents_ = undefined;
85    this.systemTraceEvents_ = undefined;
86    this.battorData_ = undefined;
87    this.eventsWereFromString_ = false;
88    this.softwareMeasuredCpuCount_ = undefined;
89    this.allAsyncEvents_ = [];
90    this.allFlowEvents_ = [];
91    this.allObjectEvents_ = [];
92    this.traceEventSampleStackFramesByName_ = {};
93
94    // Dump ID -> {global: (event | undefined), process: [events]}
95    this.allMemoryDumpEvents_ = {};
96
97    if (typeof(eventData) === 'string' || eventData instanceof String) {
98      eventData = eventData.trim();
99      // If the event data begins with a [, then we know it should end with a ].
100      // The reason we check for this is because some tracing implementations
101      // cannot guarantee that a ']' gets written to the trace file. So, we are
102      // forgiving and if this is obviously the case, we fix it up before
103      // throwing the string at JSON.parse.
104      if (eventData[0] === '[') {
105        eventData = eventData.replace(/\s*,\s*$/, '');
106        if (eventData[eventData.length - 1] !== ']')
107          eventData = eventData + ']';
108      }
109
110      this.events_ = JSON.parse(eventData);
111      this.eventsWereFromString_ = true;
112    } else {
113      this.events_ = eventData;
114    }
115
116    this.traceAnnotations_ = this.events_.traceAnnotations;
117
118    // Some trace_event implementations put the actual trace events
119    // inside a container. E.g { ... , traceEvents: [ ] }
120    // If we see that, just pull out the trace events.
121    if (this.events_.traceEvents) {
122      var container = this.events_;
123      this.events_ = this.events_.traceEvents;
124
125      // Some trace_event implementations put ftrace_importer traces as a
126      // huge string inside container.systemTraceEvents. If we see that, pull it
127      // out. It will be picked up by extractSubtraces later on.
128      this.systemTraceEvents_ = container.systemTraceEvents;
129
130      // Some trace_event implementations put battor power traces as a
131      // huge string inside container.battorLogAsString. If we see that, pull
132      // it out. It will be picked up by extractSubtraces later on.
133      this.battorData_ = container.battorLogAsString;
134
135      // Sampling data.
136      this.sampleEvents_ = container.samples;
137      this.stackFrameEvents_ = container.stackFrames;
138
139      // Some implementations specify displayTimeUnit
140      if (container.displayTimeUnit) {
141        var unitName = container.displayTimeUnit;
142        var unit = tr.b.units.Time.supportedUnits[unitName];
143        if (unit === undefined) {
144          throw new Error('Unit ' + unitName + ' is not supported.');
145        }
146        this.model_.intrinsicTimeUnit = unit;
147      }
148
149      var knownFieldNames = {
150        battorLogAsString: true,
151        samples: true,
152        stackFrames: true,
153        systemTraceEvents: true,
154        traceAnnotations: true,
155        traceEvents: true
156      };
157      // Any other fields in the container should be treated as metadata.
158      for (var fieldName in container) {
159        if (fieldName in knownFieldNames)
160          continue;
161        this.model_.metadata.push({name: fieldName,
162          value: container[fieldName]});
163      }
164    }
165  }
166
167  /**
168   * @return {boolean} Whether obj is a TraceEvent array.
169   */
170  TraceEventImporter.canImport = function(eventData) {
171    // May be encoded JSON. But we dont want to parse it fully yet.
172    // Use a simple heuristic:
173    //   - eventData that starts with [ are probably trace_event
174    //   - eventData that starts with { are probably trace_event
175    // May be encoded JSON. Treat files that start with { as importable by us.
176    if (typeof(eventData) === 'string' || eventData instanceof String) {
177      eventData = eventData.trim();
178      return eventData[0] == '{' || eventData[0] == '[';
179    }
180
181    // Might just be an array of events
182    if (eventData instanceof Array && eventData.length && eventData[0].ph)
183      return true;
184
185    // Might be an object with a traceEvents field in it.
186    if (eventData.traceEvents) {
187      if (eventData.traceEvents instanceof Array) {
188        if (eventData.traceEvents.length && eventData.traceEvents[0].ph)
189          return true;
190        if (eventData.samples.length && eventData.stackFrames !== undefined)
191          return true;
192      }
193    }
194
195    return false;
196  };
197
198  TraceEventImporter.prototype = {
199
200    __proto__: Importer.prototype,
201
202    extractSubtraces: function() {
203      var systemEventsTmp = this.systemTraceEvents_;
204      var battorDataTmp = this.battorData_;
205      this.systemTraceEvents_ = undefined;
206      this.battorData_ = undefined;
207      var subTraces = systemEventsTmp ? [systemEventsTmp] : [];
208      if (battorDataTmp)
209        subTraces.push(battorDataTmp);
210       return subTraces;
211    },
212
213    /**
214     * Deep copying is only needed if the trace was given to us as events.
215     */
216    deepCopyIfNeeded_: function(obj) {
217      if (obj === undefined)
218        obj = {};
219      if (this.eventsWereFromString_)
220        return obj;
221      return deepCopy(obj);
222    },
223
224    /**
225     * Helper to process an async event.
226     */
227    processAsyncEvent: function(event) {
228      var thread = this.model_.getOrCreateProcess(event.pid).
229          getOrCreateThread(event.tid);
230      this.allAsyncEvents_.push({
231        sequenceNumber: this.allAsyncEvents_.length,
232        event: event,
233        thread: thread});
234    },
235
236    /**
237     * Helper to process a flow event.
238     */
239    processFlowEvent: function(event) {
240      var thread = this.model_.getOrCreateProcess(event.pid).
241          getOrCreateThread(event.tid);
242      this.allFlowEvents_.push({
243        refGuid: tr.b.GUID.getLastGuid(),
244        sequenceNumber: this.allFlowEvents_.length,
245        event: event,
246        thread: thread
247      });
248    },
249
250    /**
251     * Helper that creates and adds samples to a Counter object based on
252     * 'C' phase events.
253     */
254    processCounterEvent: function(event) {
255      var ctr_name;
256      if (event.id !== undefined)
257        ctr_name = event.name + '[' + event.id + ']';
258      else
259        ctr_name = event.name;
260
261      var ctr = this.model_.getOrCreateProcess(event.pid)
262          .getOrCreateCounter(event.cat, ctr_name);
263      var reservedColorId = event.cname ? getEventColor(event) : undefined;
264
265      // Initialize the counter's series fields if needed.
266      if (ctr.numSeries === 0) {
267        for (var seriesName in event.args) {
268          var colorId = reservedColorId ||
269              getEventColor(event, ctr.name + '.' + seriesName);
270          ctr.addSeries(new tr.model.CounterSeries(seriesName, colorId));
271        }
272
273        if (ctr.numSeries === 0) {
274          this.model_.importWarning({
275            type: 'counter_parse_error',
276            message: 'Expected counter ' + event.name +
277                ' to have at least one argument to use as a value.'
278          });
279
280          // Drop the counter.
281          delete ctr.parent.counters[ctr.name];
282          return;
283        }
284      }
285
286      var ts = timestampFromUs(event.ts);
287      ctr.series.forEach(function(series) {
288        var val = event.args[series.name] ? event.args[series.name] : 0;
289        series.addCounterSample(ts, val);
290      });
291    },
292
293    processObjectEvent: function(event) {
294      var thread = this.model_.getOrCreateProcess(event.pid).
295          getOrCreateThread(event.tid);
296      this.allObjectEvents_.push({
297        sequenceNumber: this.allObjectEvents_.length,
298        event: event,
299        thread: thread});
300    },
301
302    processDurationEvent: function(event) {
303      var thread = this.model_.getOrCreateProcess(event.pid)
304        .getOrCreateThread(event.tid);
305      var ts = timestampFromUs(event.ts);
306      if (!thread.sliceGroup.isTimestampValidForBeginOrEnd(ts)) {
307        this.model_.importWarning({
308          type: 'duration_parse_error',
309          message: 'Timestamps are moving backward.'
310        });
311        return;
312      }
313
314      if (event.ph == 'B') {
315        var slice = thread.sliceGroup.beginSlice(
316            event.cat, event.name, timestampFromUs(event.ts),
317            this.deepCopyIfNeeded_(event.args),
318            timestampFromUs(event.tts), event.argsStripped,
319            getEventColor(event));
320        slice.startStackFrame = this.getStackFrameForEvent_(event);
321      } else if (event.ph == 'I' || event.ph == 'i') {
322        if (event.s !== undefined && event.s !== 't')
323          throw new Error('This should never happen');
324
325        thread.sliceGroup.beginSlice(event.cat, event.name,
326                                     timestampFromUs(event.ts),
327                                     this.deepCopyIfNeeded_(event.args),
328                                     timestampFromUs(event.tts),
329                                     event.argsStripped,
330                                     getEventColor(event));
331        var slice = thread.sliceGroup.endSlice(timestampFromUs(event.ts),
332                                   timestampFromUs(event.tts));
333        slice.startStackFrame = this.getStackFrameForEvent_(event);
334        slice.endStackFrame = undefined;
335      } else {
336        if (!thread.sliceGroup.openSliceCount) {
337          this.model_.importWarning({
338            type: 'duration_parse_error',
339            message: 'E phase event without a matching B phase event.'
340          });
341          return;
342        }
343
344        var slice = thread.sliceGroup.endSlice(timestampFromUs(event.ts),
345                                               timestampFromUs(event.tts),
346                                               getEventColor(event));
347        if (event.name && slice.title != event.name) {
348          this.model_.importWarning({
349            type: 'title_match_error',
350            message: 'Titles do not match. Title is ' +
351                slice.title + ' in openSlice, and is ' +
352                event.name + ' in endSlice'
353          });
354        }
355        slice.endStackFrame = this.getStackFrameForEvent_(event);
356
357        this.mergeArgsInto_(slice.args, event.args, slice.title);
358      }
359    },
360
361    mergeArgsInto_: function(dstArgs, srcArgs, eventName) {
362      for (var arg in srcArgs) {
363        if (dstArgs[arg] !== undefined) {
364          this.model_.importWarning({
365            type: 'arg_merge_error',
366            message: 'Different phases of ' + eventName +
367                ' provided values for argument ' + arg + '.' +
368                ' The last provided value will be used.'
369          });
370        }
371        dstArgs[arg] = this.deepCopyIfNeeded_(srcArgs[arg]);
372      }
373    },
374
375    processCompleteEvent: function(event) {
376      var thread = this.model_.getOrCreateProcess(event.pid)
377          .getOrCreateThread(event.tid);
378      var slice = thread.sliceGroup.pushCompleteSlice(event.cat, event.name,
379          timestampFromUs(event.ts),
380          maybeTimestampFromUs(event.dur),
381          maybeTimestampFromUs(event.tts),
382          maybeTimestampFromUs(event.tdur),
383          this.deepCopyIfNeeded_(event.args),
384          event.argsStripped,
385          getEventColor(event));
386      slice.startStackFrame = this.getStackFrameForEvent_(event);
387      slice.endStackFrame = this.getStackFrameForEvent_(event, true);
388    },
389
390    processMetadataEvent: function(event) {
391      // The metadata events aren't useful without args.
392      if (event.argsStripped)
393        return;
394
395      if (event.name == 'process_name') {
396        var process = this.model_.getOrCreateProcess(event.pid);
397        process.name = event.args.name;
398      } else if (event.name == 'process_labels') {
399        var process = this.model_.getOrCreateProcess(event.pid);
400        var labels = event.args.labels.split(',');
401        for (var i = 0; i < labels.length; i++)
402          process.addLabelIfNeeded(labels[i]);
403      } else if (event.name == 'process_sort_index') {
404        var process = this.model_.getOrCreateProcess(event.pid);
405        process.sortIndex = event.args.sort_index;
406      } else if (event.name == 'thread_name') {
407        var thread = this.model_.getOrCreateProcess(event.pid).
408            getOrCreateThread(event.tid);
409        thread.name = event.args.name;
410      } else if (event.name == 'thread_sort_index') {
411        var thread = this.model_.getOrCreateProcess(event.pid).
412            getOrCreateThread(event.tid);
413        thread.sortIndex = event.args.sort_index;
414      } else if (event.name == 'num_cpus') {
415        var n = event.args.number;
416        // Not all render processes agree on the cpu count in trace_event. Some
417        // processes will report 1, while others will report the actual cpu
418        // count. To deal with this, take the max of what is reported.
419        if (this.softwareMeasuredCpuCount_ !== undefined)
420          n = Math.max(n, this.softwareMeasuredCpuCount_);
421        this.softwareMeasuredCpuCount_ = n;
422      } else {
423        this.model_.importWarning({
424          type: 'metadata_parse_error',
425          message: 'Unrecognized metadata name: ' + event.name
426        });
427      }
428    },
429
430    processInstantEvent: function(event) {
431      // Thread-level instant events are treated as zero-duration slices.
432      if (event.s == 't' || event.s === undefined) {
433        this.processDurationEvent(event);
434        return;
435      }
436
437      var constructor;
438      switch (event.s) {
439        case 'g':
440          constructor = tr.model.GlobalInstantEvent;
441          break;
442        case 'p':
443          constructor = tr.model.ProcessInstantEvent;
444          break;
445        default:
446          this.model_.importWarning({
447            type: 'instant_parse_error',
448            message: 'I phase event with unknown "s" field value.'
449          });
450          return;
451      }
452
453      var instantEvent = new constructor(event.cat, event.name,
454          getEventColor(event), timestampFromUs(event.ts),
455          this.deepCopyIfNeeded_(event.args));
456
457      switch (instantEvent.type) {
458        case tr.model.InstantEventType.GLOBAL:
459          this.model_.pushInstantEvent(instantEvent);
460          break;
461
462        case tr.model.InstantEventType.PROCESS:
463          var process = this.model_.getOrCreateProcess(event.pid);
464          process.pushInstantEvent(instantEvent);
465          break;
466
467        default:
468          throw new Error('Unknown instant event type: ' + event.s);
469      }
470    },
471
472    processTraceSampleEvent: function(event) {
473      var thread = this.model_.getOrCreateProcess(event.pid)
474        .getOrCreateThread(event.tid);
475
476      var stackFrame = this.getStackFrameForEvent_(event);
477      if (stackFrame === undefined) {
478        stackFrame = this.traceEventSampleStackFramesByName_[
479            event.name];
480      }
481      if (stackFrame === undefined) {
482        var id = 'te-' + tr.b.GUID.allocate();
483        stackFrame = new tr.model.StackFrame(
484            undefined, id,
485            event.cat, event.name,
486            tr.ui.b.getColorIdForGeneralPurposeString(event.name));
487        this.model_.addStackFrame(stackFrame);
488        this.traceEventSampleStackFramesByName_[event.name] = stackFrame;
489      }
490
491      var sample = new tr.model.Sample(
492          undefined, thread, 'TRACE_EVENT_SAMPLE',
493          timestampFromUs(event.ts), stackFrame, 1,
494          this.deepCopyIfNeeded_(event.args));
495      this.model_.samples.push(sample);
496    },
497
498    getOrCreateMemoryDumpEvents_: function(dumpId) {
499      if (this.allMemoryDumpEvents_[dumpId] === undefined) {
500        this.allMemoryDumpEvents_[dumpId] = {
501          global: undefined,
502          process: []
503        };
504      }
505      return this.allMemoryDumpEvents_[dumpId];
506    },
507
508    processMemoryDumpEvent: function(event) {
509      if (event.id === undefined) {
510        this.model_.importWarning({
511          type: 'memory_dump_parse_error',
512          message: event.ph + ' phase event without a dump ID.'
513        });
514        return;
515      }
516      var events = this.getOrCreateMemoryDumpEvents_(event.id);
517
518      if (event.ph === 'v') {
519        // Add a process memory dump.
520        events.process.push(event);
521      } else if (event.ph === 'V') {
522        // Add a global memory dump (unless already present).
523        if (events.global !== undefined) {
524          this.model_.importWarning({
525            type: 'memory_dump_parse_error',
526            message: 'Multiple V phase events with the same dump ID.'
527          });
528          return;
529        }
530        events.global = event;
531      } else {
532        throw new Error('Invalid memory dump event phase "' + event.ph + '".');
533      }
534    },
535
536    /**
537     * Walks through the events_ list and outputs the structures discovered to
538     * model_.
539     */
540    importEvents: function() {
541      var csr = new tr.ClockSyncRecord('ftrace_importer', 0, {});
542      this.model_.clockSyncRecords.push(csr);
543      if (this.stackFrameEvents_)
544        this.importStackFrames_();
545
546      if (this.traceAnnotations_)
547        this.importAnnotations_();
548
549      var events = this.events_;
550      for (var eI = 0; eI < events.length; eI++) {
551        var event = events[eI];
552        if (event.args === '__stripped__') {
553          event.argsStripped = true;
554          event.args = undefined;
555        }
556
557        if (event.ph === 'B' || event.ph === 'E') {
558          this.processDurationEvent(event);
559
560        } else if (event.ph === 'X') {
561          this.processCompleteEvent(event);
562
563        } else if (event.ph === 'b' || event.ph === 'e' || event.ph === 'n' ||
564                   event.ph === 'S' || event.ph === 'F' || event.ph === 'T' ||
565                   event.ph === 'p') {
566          this.processAsyncEvent(event);
567
568        // Note, I is historic. The instant event marker got changed, but we
569        // want to support loading old trace files so we have both I and i.
570        } else if (event.ph == 'I' || event.ph == 'i') {
571          this.processInstantEvent(event);
572
573        } else if (event.ph == 'P') {
574          this.processTraceSampleEvent(event);
575
576        } else if (event.ph == 'C') {
577          this.processCounterEvent(event);
578
579        } else if (event.ph == 'M') {
580          this.processMetadataEvent(event);
581
582        } else if (event.ph === 'N' || event.ph === 'D' || event.ph === 'O') {
583          this.processObjectEvent(event);
584
585        } else if (event.ph === 's' || event.ph === 't' || event.ph === 'f') {
586          this.processFlowEvent(event);
587
588        } else if (event.ph === 'v' || event.ph === 'V') {
589          this.processMemoryDumpEvent(event);
590
591        } else {
592          this.model_.importWarning({
593            type: 'parse_error',
594            message: 'Unrecognized event phase: ' +
595                event.ph + ' (' + event.name + ')'
596          });
597        }
598      }
599    },
600
601    importStackFrames_: function() {
602      var m = this.model_;
603      var events = this.stackFrameEvents_;
604
605      for (var id in events) {
606        var event = events[id];
607        var textForColor = event.category ? event.category : event.name;
608        var frame = new tr.model.StackFrame(
609            undefined, 'g' + id,
610            event.category, event.name,
611            tr.ui.b.getColorIdForGeneralPurposeString(textForColor));
612        m.addStackFrame(frame);
613      }
614      for (var id in events) {
615        var event = events[id];
616        if (event.parent === undefined)
617          continue;
618
619        var frame = m.stackFrames['g' + id];
620        if (frame === undefined)
621          throw new Error('omg');
622        var parentFrame;
623        if (event.parent === undefined) {
624          parentFrame = undefined;
625        } else {
626          parentFrame = m.stackFrames['g' + event.parent];
627          if (parentFrame === undefined)
628            throw new Error('omg');
629        }
630        frame.parentFrame = parentFrame;
631      }
632    },
633
634    importAnnotations_: function() {
635      for (var id in this.traceAnnotations_) {
636        var annotation = tr.model.Annotation.fromDictIfPossible(
637           this.traceAnnotations_[id]);
638        if (!annotation) {
639          this.model_.importWarning({
640            type: 'annotation_warning',
641            message: 'Unrecognized traceAnnotation typeName \"' +
642                this.traceAnnotations_[id].typeName + '\"'
643          });
644          continue;
645        }
646        this.model_.addAnnotation(annotation);
647      }
648    },
649
650    /**
651     * Called by the Model after all other importers have imported their
652     * events.
653     */
654    finalizeImport: function() {
655      if (this.softwareMeasuredCpuCount_ !== undefined) {
656        this.model_.kernel.softwareMeasuredCpuCount =
657            this.softwareMeasuredCpuCount_;
658      }
659      this.createAsyncSlices_();
660      this.createFlowSlices_();
661      this.createExplicitObjects_();
662      this.createImplicitObjects_();
663      this.createMemoryDumps_();
664    },
665
666    /* Events can have one or more stack frames associated with them, but
667     * that frame might be encoded either as a stack trace of program counters,
668     * or as a direct stack frame reference. This handles either case and
669     * if found, returns the stackframe.
670     */
671    getStackFrameForEvent_: function(event, opt_lookForEndEvent) {
672      var sf;
673      var stack;
674      if (opt_lookForEndEvent) {
675        sf = event.esf;
676        stack = event.estack;
677      } else {
678        sf = event.sf;
679        stack = event.stack;
680      }
681      if (stack !== undefined && sf !== undefined) {
682        this.model_.importWarning({
683          type: 'stack_frame_and_stack_error',
684          message: 'Event at ' + event.ts +
685              ' cannot have both a stack and a stackframe.'
686        });
687        return undefined;
688      }
689
690      if (stack !== undefined)
691        return this.model_.resolveStackToStackFrame_(event.pid, stack);
692      if (sf === undefined)
693        return undefined;
694
695      var stackFrame = this.model_.stackFrames['g' + sf];
696      if (stackFrame === undefined) {
697        this.model_.importWarning({
698          type: 'sample_import_error',
699          message: 'No frame for ' + sf
700        });
701        return;
702      }
703      return stackFrame;
704    },
705
706    resolveStackToStackFrame_: function(pid, stack) {
707      // TODO(alph,fmeawad): Add codemap resolution code here.
708      return undefined;
709    },
710
711    importSampleData: function() {
712      if (!this.sampleEvents_)
713        return;
714      var m = this.model_;
715
716      // If this is the only importer, then fake-create the threads.
717      var events = this.sampleEvents_;
718      if (this.events_.length === 0) {
719        for (var i = 0; i < events.length; i++) {
720          var event = events[i];
721          m.getOrCreateProcess(event.tid).getOrCreateThread(event.tid);
722        }
723      }
724
725      var threadsByTid = {};
726      m.getAllThreads().forEach(function(t) {
727        threadsByTid[t.tid] = t;
728      });
729
730      for (var i = 0; i < events.length; i++) {
731        var event = events[i];
732        var thread = threadsByTid[event.tid];
733        if (thread === undefined) {
734          m.importWarning({
735            type: 'sample_import_error',
736            message: 'Thread ' + events.tid + 'not found'
737          });
738          continue;
739        }
740
741        var cpu;
742        if (event.cpu !== undefined)
743          cpu = m.kernel.getOrCreateCpu(event.cpu);
744
745        var stackFrame = this.getStackFrameForEvent_(event);
746
747        var sample = new tr.model.Sample(
748            cpu, thread,
749            event.name,
750            timestampFromUs(event.ts),
751            stackFrame,
752            event.weight);
753        m.samples.push(sample);
754      }
755    },
756
757    /**
758     * Called by the model to join references between objects, after final model
759     * bounds have been computed.
760     */
761    joinRefs: function() {
762      this.joinObjectRefs_();
763    },
764
765    createAsyncSlices_: function() {
766      if (this.allAsyncEvents_.length === 0)
767        return;
768
769      this.allAsyncEvents_.sort(function(x, y) {
770        var d = x.event.ts - y.event.ts;
771        if (d !== 0)
772          return d;
773        return x.sequenceNumber - y.sequenceNumber;
774      });
775
776      var legacyEvents = [];
777      // Group nestable async events by ID. Events with the same ID should
778      // belong to the same parent async event.
779      var nestableAsyncEventsByKey = {};
780      for (var i = 0; i < this.allAsyncEvents_.length; i++) {
781        var asyncEventState = this.allAsyncEvents_[i];
782        var event = asyncEventState.event;
783        if (event.ph === 'S' || event.ph === 'F' || event.ph === 'T' ||
784            event.ph === 'p') {
785          legacyEvents.push(asyncEventState);
786          continue;
787        }
788        if (event.cat === undefined) {
789          this.model_.importWarning({
790            type: 'async_slice_parse_error',
791            message: 'Nestable async events (ph: b, e, or n) require a ' +
792                'cat parameter.'
793          });
794          continue;
795        }
796
797        if (event.name === undefined) {
798          this.model_.importWarning({
799            type: 'async_slice_parse_error',
800            message: 'Nestable async events (ph: b, e, or n) require a ' +
801                'name parameter.'
802          });
803          continue;
804        }
805
806        if (event.id === undefined) {
807          this.model_.importWarning({
808            type: 'async_slice_parse_error',
809            message: 'Nestable async events (ph: b, e, or n) require an ' +
810                'id parameter.'
811          });
812          continue;
813        }
814        var key = event.cat + ':' + event.id;
815        if (nestableAsyncEventsByKey[key] === undefined)
816           nestableAsyncEventsByKey[key] = [];
817        nestableAsyncEventsByKey[key].push(asyncEventState);
818      }
819      // Handle legacy async events.
820      this.createLegacyAsyncSlices_(legacyEvents);
821
822      // Parse nestable async events into AsyncSlices.
823      for (var key in nestableAsyncEventsByKey) {
824        var eventStateEntries = nestableAsyncEventsByKey[key];
825        // Stack of enclosing BEGIN events.
826        var parentStack = [];
827        for (var i = 0; i < eventStateEntries.length; ++i) {
828          var eventStateEntry = eventStateEntries[i];
829          // If this is the end of an event, match it to the start.
830          if (eventStateEntry.event.ph === 'e') {
831            // Walk up the parent stack to find the corresponding BEGIN for
832            // this END.
833            var parentIndex = -1;
834            for (var k = parentStack.length - 1; k >= 0; --k) {
835              if (parentStack[k].event.name === eventStateEntry.event.name) {
836                parentIndex = k;
837                break;
838              }
839            }
840            if (parentIndex === -1) {
841              // Unmatched end.
842              eventStateEntry.finished = false;
843            } else {
844              parentStack[parentIndex].end = eventStateEntry;
845              // Pop off all enclosing unmatched BEGINs util parentIndex.
846              while (parentIndex < parentStack.length) {
847                parentStack.pop();
848              }
849            }
850          }
851          // Inherit the current parent.
852          if (parentStack.length > 0)
853            eventStateEntry.parentEntry = parentStack[parentStack.length - 1];
854          if (eventStateEntry.event.ph === 'b')
855            parentStack.push(eventStateEntry);
856        }
857        var topLevelSlices = [];
858        for (var i = 0; i < eventStateEntries.length; ++i) {
859          var eventStateEntry = eventStateEntries[i];
860          // Skip matched END, as its slice will be created when we
861          // encounter its corresponding BEGIN.
862          if (eventStateEntry.event.ph === 'e' &&
863              eventStateEntry.finished === undefined) {
864            continue;
865          }
866          var startState = undefined;
867          var endState = undefined;
868          var sliceArgs = eventStateEntry.event.args || {};
869          var sliceError = undefined;
870          if (eventStateEntry.event.ph === 'n') {
871            startState = eventStateEntry;
872            endState = eventStateEntry;
873          } else if (eventStateEntry.event.ph === 'b') {
874            if (eventStateEntry.end === undefined) {
875              // Unmatched BEGIN. End it when last event with this ID ends.
876              eventStateEntry.end =
877                  eventStateEntries[eventStateEntries.length - 1];
878              sliceError =
879                  'Slice has no matching END. End time has been adjusted.';
880              this.model_.importWarning({
881                type: 'async_slice_parse_error',
882                message: 'Nestable async BEGIN event at ' +
883                    eventStateEntry.event.ts + ' with name=' +
884                    eventStateEntry.event.name +
885                    ' and id=' + eventStateEntry.event.id + ' was unmatched.'
886              });
887            } else {
888              // Include args for both END and BEGIN for a matched pair.
889              var concatenateArguments = function(args1, args2) {
890                if (args1.params === undefined || args2.params === undefined)
891                  return tr.b.concatenateObjects(args1, args2);
892                // Make an argument object to hold the combined params.
893                var args3 = {};
894                args3.params = tr.b.concatenateObjects(args1.params,
895                                                       args2.params);
896                return tr.b.concatenateObjects(args1, args2, args3);
897              }
898              var endArgs = eventStateEntry.end.event.args || {};
899              sliceArgs = concatenateArguments(sliceArgs, endArgs);
900            }
901            startState = eventStateEntry;
902            endState = eventStateEntry.end;
903          } else {
904            // Unmatched END. Start it at the first event with this ID starts.
905            sliceError =
906                'Slice has no matching BEGIN. Start time has been adjusted.';
907            this.model_.importWarning({
908              type: 'async_slice_parse_error',
909              message: 'Nestable async END event at ' +
910                  eventStateEntry.event.ts + ' with name=' +
911                  eventStateEntry.event.name +
912                  ' and id=' + eventStateEntry.event.id + ' was unmatched.'
913            });
914            startState = eventStateEntries[0];
915            endState = eventStateEntry;
916          }
917
918          var isTopLevel = (eventStateEntry.parentEntry === undefined);
919          var asyncSliceConstructor =
920             tr.model.AsyncSlice.getConstructor(
921                eventStateEntry.event.cat,
922                eventStateEntry.event.name);
923
924          var thread_start = undefined;
925          var thread_duration = undefined;
926          if (startState.event.tts && startState.event.use_async_tts) {
927            thread_start = timestampFromUs(startState.event.tts);
928            if (endState.event.tts) {
929              var thread_end = timestampFromUs(endState.event.tts);
930              thread_duration = thread_end - thread_start;
931            }
932          }
933
934          var slice = new asyncSliceConstructor(
935              eventStateEntry.event.cat,
936              eventStateEntry.event.name,
937              getEventColor(endState.event),
938              timestampFromUs(startState.event.ts),
939              sliceArgs,
940              timestampFromUs(endState.event.ts - startState.event.ts),
941              isTopLevel,
942              thread_start,
943              thread_duration,
944              startState.event.argsStripped);
945
946          slice.startThread = startState.thread;
947          slice.endThread = endState.thread;
948          slice.id = key;
949          if (sliceError !== undefined)
950            slice.error = sliceError;
951          eventStateEntry.slice = slice;
952          // Add the slice to the topLevelSlices array if there is no parent.
953          // Otherwise, add the slice to the subSlices of its parent.
954          if (isTopLevel) {
955            topLevelSlices.push(slice);
956          } else if (eventStateEntry.parentEntry.slice !== undefined) {
957            eventStateEntry.parentEntry.slice.subSlices.push(slice);
958          }
959        }
960        for (var si = 0; si < topLevelSlices.length; si++) {
961          topLevelSlices[si].startThread.asyncSliceGroup.push(
962              topLevelSlices[si]);
963        }
964      }
965    },
966
967    createLegacyAsyncSlices_: function(legacyEvents) {
968      if (legacyEvents.length === 0)
969        return;
970
971      legacyEvents.sort(function(x, y) {
972        var d = x.event.ts - y.event.ts;
973        if (d != 0)
974          return d;
975        return x.sequenceNumber - y.sequenceNumber;
976      });
977
978      var asyncEventStatesByNameThenID = {};
979
980      for (var i = 0; i < legacyEvents.length; i++) {
981        var asyncEventState = legacyEvents[i];
982
983        var event = asyncEventState.event;
984        var name = event.name;
985        if (name === undefined) {
986          this.model_.importWarning({
987            type: 'async_slice_parse_error',
988            message: 'Async events (ph: S, T, p, or F) require a name ' +
989                ' parameter.'
990          });
991          continue;
992        }
993
994        var id = event.id;
995        if (id === undefined) {
996          this.model_.importWarning({
997            type: 'async_slice_parse_error',
998            message: 'Async events (ph: S, T, p, or F) require an id parameter.'
999          });
1000          continue;
1001        }
1002
1003        // TODO(simonjam): Add a synchronous tick on the appropriate thread.
1004
1005        if (event.ph === 'S') {
1006          if (asyncEventStatesByNameThenID[name] === undefined)
1007            asyncEventStatesByNameThenID[name] = {};
1008          if (asyncEventStatesByNameThenID[name][id]) {
1009            this.model_.importWarning({
1010              type: 'async_slice_parse_error',
1011              message: 'At ' + event.ts + ', a slice of the same id ' + id +
1012                  ' was alrady open.'
1013            });
1014            continue;
1015          }
1016          asyncEventStatesByNameThenID[name][id] = [];
1017          asyncEventStatesByNameThenID[name][id].push(asyncEventState);
1018        } else {
1019          if (asyncEventStatesByNameThenID[name] === undefined) {
1020            this.model_.importWarning({
1021              type: 'async_slice_parse_error',
1022              message: 'At ' + event.ts + ', no slice named ' + name +
1023                  ' was open.'
1024            });
1025            continue;
1026          }
1027          if (asyncEventStatesByNameThenID[name][id] === undefined) {
1028            this.model_.importWarning({
1029              type: 'async_slice_parse_error',
1030              message: 'At ' + event.ts + ', no slice named ' + name +
1031                  ' with id=' + id + ' was open.'
1032            });
1033            continue;
1034          }
1035          var events = asyncEventStatesByNameThenID[name][id];
1036          events.push(asyncEventState);
1037
1038          if (event.ph === 'F') {
1039            // Create a slice from start to end.
1040            var asyncSliceConstructor =
1041               tr.model.AsyncSlice.getConstructor(
1042                  events[0].event.cat,
1043                  name);
1044            var slice = new asyncSliceConstructor(
1045                events[0].event.cat,
1046                name,
1047                getEventColor(events[0].event),
1048                timestampFromUs(events[0].event.ts),
1049                tr.b.concatenateObjects(events[0].event.args,
1050                                      events[events.length - 1].event.args),
1051                timestampFromUs(event.ts - events[0].event.ts),
1052                true, undefined, undefined, events[0].event.argsStripped);
1053            slice.startThread = events[0].thread;
1054            slice.endThread = asyncEventState.thread;
1055            slice.id = id;
1056
1057            var stepType = events[1].event.ph;
1058            var isValid = true;
1059
1060            // Create subSlices for each step. Skip the start and finish events,
1061            // which are always first and last respectively.
1062            for (var j = 1; j < events.length - 1; ++j) {
1063              if (events[j].event.ph === 'T' || events[j].event.ph === 'p') {
1064                isValid = this.assertStepTypeMatches_(stepType, events[j]);
1065                if (!isValid)
1066                  break;
1067              }
1068
1069              if (events[j].event.ph === 'S') {
1070                this.model_.importWarning({
1071                  type: 'async_slice_parse_error',
1072                  message: 'At ' + event.event.ts + ', a slice named ' +
1073                      event.event.name + ' with id=' + event.event.id +
1074                      ' had a step before the start event.'
1075                });
1076                continue;
1077              }
1078
1079              if (events[j].event.ph === 'F') {
1080                this.model_.importWarning({
1081                  type: 'async_slice_parse_error',
1082                  message: 'At ' + event.event.ts + ', a slice named ' +
1083                      event.event.name + ' with id=' + event.event.id +
1084                      ' had a step after the finish event.'
1085                });
1086                continue;
1087              }
1088
1089              var startIndex = j + (stepType === 'T' ? 0 : -1);
1090              var endIndex = startIndex + 1;
1091
1092              var subName = events[j].event.name;
1093              if (!events[j].event.argsStripped &&
1094                  (events[j].event.ph === 'T' || events[j].event.ph === 'p'))
1095                subName = subName + ':' + events[j].event.args.step;
1096
1097              var asyncSliceConstructor =
1098                 tr.model.AsyncSlice.getConstructor(
1099                    events[0].event.cat,
1100                    subName);
1101              var subSlice = new asyncSliceConstructor(
1102                  events[0].event.cat,
1103                  subName,
1104                  getEventColor(event, subName + j),
1105                  timestampFromUs(events[startIndex].event.ts),
1106                  this.deepCopyIfNeeded_(events[j].event.args),
1107                  timestampFromUs(
1108                    events[endIndex].event.ts - events[startIndex].event.ts),
1109                      undefined, undefined,
1110                      events[startIndex].event.argsStripped);
1111              subSlice.startThread = events[startIndex].thread;
1112              subSlice.endThread = events[endIndex].thread;
1113              subSlice.id = id;
1114
1115              slice.subSlices.push(subSlice);
1116            }
1117
1118            if (isValid) {
1119              // Add |slice| to the start-thread's asyncSlices.
1120              slice.startThread.asyncSliceGroup.push(slice);
1121            }
1122
1123            delete asyncEventStatesByNameThenID[name][id];
1124          }
1125        }
1126      }
1127    },
1128
1129    assertStepTypeMatches_: function(stepType, event) {
1130      if (stepType != event.event.ph) {
1131        this.model_.importWarning({
1132          type: 'async_slice_parse_error',
1133          message: 'At ' + event.event.ts + ', a slice named ' +
1134              event.event.name + ' with id=' + event.event.id +
1135              ' had both begin and end steps, which is not allowed.'
1136        });
1137        return false;
1138      }
1139      return true;
1140    },
1141
1142    createFlowSlices_: function() {
1143      if (this.allFlowEvents_.length === 0)
1144        return;
1145
1146      var that = this;
1147
1148      function validateFlowEvent() {
1149        if (event.name === undefined) {
1150          that.model_.importWarning({
1151            type: 'flow_slice_parse_error',
1152            message: 'Flow events (ph: s, t or f) require a name parameter.'
1153          });
1154          return false;
1155        }
1156
1157        if (event.id === undefined) {
1158          that.model_.importWarning({
1159            type: 'flow_slice_parse_error',
1160            message: 'Flow events (ph: s, t or f) require an id parameter.'
1161          });
1162          return false;
1163        }
1164        return true;
1165      }
1166
1167      function createFlowEvent(thread, event, refGuid) {
1168        var ts = timestampFromUs(event.ts);
1169        var startSlice = thread.sliceGroup.findSliceAtTs(ts);
1170        if (startSlice === undefined)
1171          return undefined;
1172
1173        var flowEvent = new tr.model.FlowEvent(
1174            event.cat,
1175            event.id,
1176            event.name,
1177            getEventColor(event),
1178            timestampFromUs(event.ts),
1179            that.deepCopyIfNeeded_(event.args));
1180        flowEvent.startSlice = startSlice;
1181        startSlice.outFlowEvents.push(flowEvent);
1182        return flowEvent;
1183      }
1184
1185      function finishFlowEventWith(flowEvent, thread, event,
1186                                   refGuid, bindToParent) {
1187        // TODO(nduca): Figure out endSlice from ts and binding point.
1188        var endSlice;
1189        var ts = timestampFromUs(event.ts);
1190        if (bindToParent) {
1191          endSlice = thread.sliceGroup.findSliceAtTs(ts);
1192        } else {
1193          endSlice = thread.sliceGroup.findNextSliceAfter(ts, refGuid);
1194        }
1195        if (endSlice === undefined)
1196          return false;
1197        endSlice.inFlowEvents.push(flowEvent);
1198
1199        // Modify the flowEvent with the new data.
1200        flowEvent.endSlice = endSlice;
1201        flowEvent.duration = ts - flowEvent.start;
1202        that.mergeArgsInto_(flowEvent.args, event.args, flowEvent.title);
1203        return true;
1204      }
1205
1206      // Actual import.
1207      this.allFlowEvents_.sort(function(x, y) {
1208        var d = x.event.ts - y.event.ts;
1209        if (d != 0)
1210          return d;
1211        return x.sequenceNumber - y.sequenceNumber;
1212      });
1213
1214      var flowIdToEvent = {};
1215      for (var i = 0; i < this.allFlowEvents_.length; ++i) {
1216        var data = this.allFlowEvents_[i];
1217        var refGuid = data.refGuid;
1218        var event = data.event;
1219        var thread = data.thread;
1220        if (!validateFlowEvent(event))
1221          continue;
1222
1223        var flowEvent;
1224        if (event.ph === 's') {
1225          if (flowIdToEvent[event.id]) {
1226            this.model_.importWarning({
1227              type: 'flow_slice_start_error',
1228              message: 'event id ' + event.id + ' already seen when ' +
1229                  'encountering start of flow event.'});
1230            continue;
1231          }
1232          flowEvent = createFlowEvent(thread, event, refGuid);
1233          if (!flowEvent) {
1234            this.model_.importWarning({
1235              type: 'flow_slice_start_error',
1236              message: 'event id ' + event.id + ' does not start ' +
1237                  'at an actual slice, so cannot be created.'});
1238            continue;
1239          }
1240          flowIdToEvent[event.id] = flowEvent;
1241
1242        } else if (event.ph === 't' || event.ph === 'f') {
1243          flowEvent = flowIdToEvent[event.id];
1244          if (flowEvent === undefined) {
1245            this.model_.importWarning({
1246              type: 'flow_slice_ordering_error',
1247              message: 'Found flow phase ' + event.ph + ' for id: ' + event.id +
1248                  ' but no flow start found.'
1249            });
1250            continue;
1251          }
1252
1253          var bindToParent = event.ph === 't';
1254
1255          if (event.ph === 'f') {
1256            if (event.bp === undefined) {
1257              // Hack: input latency flow events used to be issued
1258              // without bp='e' letter in old traces. But, we still want their
1259              // flow events to parse "properly." This adjusts their binding
1260              // point so that they parse.
1261              //
1262              // TODO(nduca): Removal of this hack is tracked in
1263              // https://github.com/google/trace-viewer/issues/991.
1264              if (event.cat.indexOf('input') > -1)
1265                bindToParent = true;
1266              // Hack: ipc flow events used to be issued
1267              // without bp='e' letter in old traces. But, we still want their
1268              // flow events to parse "properly." This adjusts their binding
1269              // point so that they parse.
1270              //
1271              // TODO: Removal of this hack is tracked in
1272              // https://github.com/google/trace-viewer/issues/991.
1273              else if (event.cat.indexOf('ipc.flow') > -1)
1274                bindToParent = true;
1275            } else {
1276              if (event.bp !== 'e') {
1277                this.model_.importWarning({
1278                 type: 'flow_slice_bind_point_error',
1279                 message: 'Flow event with invalid binding point (event.bp).'
1280                });
1281                continue;
1282              }
1283              bindToParent = true;
1284            }
1285          }
1286
1287          var ok = finishFlowEventWith(flowEvent, thread, event,
1288                                       refGuid, bindToParent);
1289          if (ok) {
1290            that.model_.flowEvents.push(flowEvent);
1291          } else {
1292            this.model_.importWarning({
1293              type: 'flow_slice_start_error',
1294              message: 'event id ' + event.id + ' does not end ' +
1295                  'at an actual slice, so cannot be created.'});
1296          }
1297
1298          flowIdToEvent[event.id] = undefined;
1299
1300          // If this is a step, then create another flow event.
1301          if (ok && event.ph === 't') {
1302            flowEvent = createFlowEvent(thread, event);
1303            flowIdToEvent[event.id] = flowEvent;
1304          }
1305        }
1306      }
1307    },
1308
1309    /**
1310     * This function creates objects described via the N, D, and O phase
1311     * events.
1312     */
1313    createExplicitObjects_: function() {
1314      if (this.allObjectEvents_.length == 0)
1315        return;
1316
1317      function processEvent(objectEventState) {
1318        var event = objectEventState.event;
1319        var thread = objectEventState.thread;
1320        if (event.name === undefined) {
1321          this.model_.importWarning({
1322            type: 'object_parse_error',
1323            message: 'While processing ' + JSON.stringify(event) + ': ' +
1324                'Object events require an name parameter.'
1325          });
1326        }
1327
1328        if (event.id === undefined) {
1329          this.model_.importWarning({
1330            type: 'object_parse_error',
1331            message: 'While processing ' + JSON.stringify(event) + ': ' +
1332                'Object events require an id parameter.'
1333          });
1334        }
1335        var process = thread.parent;
1336        var ts = timestampFromUs(event.ts);
1337        var instance;
1338        if (event.ph == 'N') {
1339          try {
1340            instance = process.objects.idWasCreated(
1341                event.id, event.cat, event.name, ts);
1342          } catch (e) {
1343            this.model_.importWarning({
1344              type: 'object_parse_error',
1345              message: 'While processing create of ' +
1346                  event.id + ' at ts=' + ts + ': ' + e
1347            });
1348            return;
1349          }
1350        } else if (event.ph == 'O') {
1351          if (event.args.snapshot === undefined) {
1352            this.model_.importWarning({
1353              type: 'object_parse_error',
1354              message: 'While processing ' + event.id + ' at ts=' + ts + ': ' +
1355                  'Snapshots must have args: {snapshot: ...}'
1356            });
1357            return;
1358          }
1359          var snapshot;
1360          try {
1361            var args = this.deepCopyIfNeeded_(event.args.snapshot);
1362            var cat;
1363            if (args.cat) {
1364              cat = args.cat;
1365              delete args.cat;
1366            } else {
1367              cat = event.cat;
1368            }
1369
1370            var baseTypename;
1371            if (args.base_type) {
1372              baseTypename = args.base_type;
1373              delete args.base_type;
1374            } else {
1375              baseTypename = undefined;
1376            }
1377            snapshot = process.objects.addSnapshot(
1378                event.id, cat, event.name, ts,
1379                args, baseTypename);
1380            snapshot.snapshottedOnThread = thread;
1381          } catch (e) {
1382            this.model_.importWarning({
1383              type: 'object_parse_error',
1384              message: 'While processing snapshot of ' +
1385                  event.id + ' at ts=' + ts + ': ' + e
1386            });
1387            return;
1388          }
1389          instance = snapshot.objectInstance;
1390        } else if (event.ph == 'D') {
1391          try {
1392            process.objects.idWasDeleted(event.id, event.cat, event.name, ts);
1393            var instanceMap = process.objects.getOrCreateInstanceMap_(event.id);
1394            instance = instanceMap.lastInstance;
1395          } catch (e) {
1396            this.model_.importWarning({
1397              type: 'object_parse_error',
1398              message: 'While processing delete of ' +
1399                  event.id + ' at ts=' + ts + ': ' + e
1400            });
1401            return;
1402          }
1403        }
1404
1405        if (instance)
1406          instance.colorId = getEventColor(event, instance.typeName);
1407      }
1408
1409      this.allObjectEvents_.sort(function(x, y) {
1410        var d = x.event.ts - y.event.ts;
1411        if (d != 0)
1412          return d;
1413        return x.sequenceNumber - y.sequenceNumber;
1414      });
1415
1416      var allObjectEvents = this.allObjectEvents_;
1417      for (var i = 0; i < allObjectEvents.length; i++) {
1418        var objectEventState = allObjectEvents[i];
1419        try {
1420          processEvent.call(this, objectEventState);
1421        } catch (e) {
1422          this.model_.importWarning({
1423            type: 'object_parse_error',
1424            message: e.message
1425          });
1426        }
1427      }
1428    },
1429
1430    createImplicitObjects_: function() {
1431      tr.b.iterItems(this.model_.processes, function(pid, process) {
1432        this.createImplicitObjectsForProcess_(process);
1433      }, this);
1434    },
1435
1436    // Here, we collect all the snapshots that internally contain a
1437    // Javascript-level object inside their args list that has an "id" field,
1438    // and turn that into a snapshot of the instance referred to by id.
1439    createImplicitObjectsForProcess_: function(process) {
1440
1441      function processField(referencingObject,
1442                            referencingObjectFieldName,
1443                            referencingObjectFieldValue,
1444                            containingSnapshot) {
1445        if (!referencingObjectFieldValue)
1446          return;
1447
1448        if (referencingObjectFieldValue instanceof
1449            tr.model.ObjectSnapshot)
1450          return null;
1451        if (referencingObjectFieldValue.id === undefined)
1452          return;
1453
1454        var implicitSnapshot = referencingObjectFieldValue;
1455
1456        var rawId = implicitSnapshot.id;
1457        var m = /(.+)\/(.+)/.exec(rawId);
1458        if (!m)
1459          throw new Error('Implicit snapshots must have names.');
1460        delete implicitSnapshot.id;
1461        var name = m[1];
1462        var id = m[2];
1463        var res;
1464
1465        var cat;
1466        if (implicitSnapshot.cat !== undefined)
1467          cat = implicitSnapshot.cat;
1468        else
1469          cat = containingSnapshot.objectInstance.category;
1470
1471        var baseTypename;
1472        if (implicitSnapshot.base_type)
1473          baseTypename = implicitSnapshot.base_type;
1474        else
1475          baseTypename = undefined;
1476
1477        try {
1478          res = process.objects.addSnapshot(
1479              id, cat,
1480              name, containingSnapshot.ts,
1481              implicitSnapshot, baseTypename);
1482        } catch (e) {
1483          this.model_.importWarning({
1484            type: 'object_snapshot_parse_error',
1485            message: 'While processing implicit snapshot of ' +
1486                rawId + ' at ts=' + containingSnapshot.ts + ': ' + e
1487          });
1488          return;
1489        }
1490        res.objectInstance.hasImplicitSnapshots = true;
1491        res.containingSnapshot = containingSnapshot;
1492        res.snapshottedOnThread = containingSnapshot.snapshottedOnThread;
1493        referencingObject[referencingObjectFieldName] = res;
1494        if (!(res instanceof tr.model.ObjectSnapshot))
1495          throw new Error('Created object must be instanceof snapshot');
1496        return res.args;
1497      }
1498
1499      /**
1500       * Iterates over the fields in the object, calling func for every
1501       * field/value found.
1502       *
1503       * @return {object} If the function does not want the field's value to be
1504       * iterated, return null. If iteration of the field value is desired, then
1505       * return either undefined (if the field value did not change) or the new
1506       * field value if it was changed.
1507       */
1508      function iterObject(object, func, containingSnapshot, thisArg) {
1509        if (!(object instanceof Object))
1510          return;
1511
1512        if (object instanceof Array) {
1513          for (var i = 0; i < object.length; i++) {
1514            var res = func.call(thisArg, object, i, object[i],
1515                                containingSnapshot);
1516            if (res === null)
1517              continue;
1518            if (res)
1519              iterObject(res, func, containingSnapshot, thisArg);
1520            else
1521              iterObject(object[i], func, containingSnapshot, thisArg);
1522          }
1523          return;
1524        }
1525
1526        for (var key in object) {
1527          var res = func.call(thisArg, object, key, object[key],
1528                              containingSnapshot);
1529          if (res === null)
1530            continue;
1531          if (res)
1532            iterObject(res, func, containingSnapshot, thisArg);
1533          else
1534            iterObject(object[key], func, containingSnapshot, thisArg);
1535        }
1536      }
1537
1538      // TODO(nduca): We may need to iterate the instances in sorted order by
1539      // creationTs.
1540      process.objects.iterObjectInstances(function(instance) {
1541        instance.snapshots.forEach(function(snapshot) {
1542          if (snapshot.args.id !== undefined)
1543            throw new Error('args cannot have an id field inside it');
1544          iterObject(snapshot.args, processField, snapshot, this);
1545        }, this);
1546      }, this);
1547    },
1548
1549    createMemoryDumps_: function() {
1550      tr.b.iterItems(this.allMemoryDumpEvents_, function(id, events) {
1551        // Calculate the range of the global memory dump.
1552        var range = new tr.b.Range();
1553        if (events.global !== undefined)
1554          range.addValue(timestampFromUs(events.global.ts));
1555        for (var i = 0; i < events.process.length; i++)
1556          range.addValue(timestampFromUs(events.process[i].ts));
1557
1558        // Create the global memory dump.
1559        var globalMemoryDump = new tr.model.GlobalMemoryDump(
1560            this.model_, range.min);
1561        globalMemoryDump.duration = range.range;
1562        this.model_.globalMemoryDumps.push(globalMemoryDump);
1563
1564        // Create individual process memory dumps.
1565        if (events.process.length === 0) {
1566          this.model_.importWarning({
1567              type: 'memory_dump_parse_error',
1568              message: 'No process memory dumps associated with global memory' +
1569                  ' dump ' + id + '.'
1570          });
1571        }
1572
1573        var allMemoryAllocatorDumpsByGuid = {};
1574        var globalMemoryAllocatorDumpsByFullName = {};
1575
1576        events.process.forEach(function(processEvent) {
1577          var pid = processEvent.pid;
1578          if (pid in globalMemoryDump.processMemoryDumps) {
1579            this.model_.importWarning({
1580              type: 'memory_dump_parse_error',
1581              message: 'Multiple process memory dumps with pid=' + pid +
1582                  ' for dump id ' + id + '.'
1583            });
1584            return;
1585          }
1586
1587          var dumps = processEvent.args.dumps;
1588          if (dumps === undefined) {
1589            this.model_.importWarning({
1590                type: 'memory_dump_parse_error',
1591                message: 'dumps not found in process memory dump for ' +
1592                    'pid=' + pid + ' and dump id=' + id + '.'
1593            });
1594            return;
1595          }
1596
1597          var process = this.model_.getOrCreateProcess(pid);
1598          var processMemoryDump = new tr.model.ProcessMemoryDump(
1599              globalMemoryDump, process,
1600              timestampFromUs(processEvent.ts));
1601
1602          // Parse the totals, which are mandatory.
1603          if (dumps.process_totals === undefined ||
1604              dumps.process_totals.resident_set_bytes === undefined) {
1605            this.model_.importWarning({
1606                type: 'memory_dump_parse_error',
1607                message: 'Mandatory field resident_set_bytes not found in' +
1608                    ' process memory dump for pid=' + pid +
1609                    ' and dump id=' + id + '.'
1610            });
1611            processMemoryDump.totalResidentBytes = undefined;
1612          } else {
1613            processMemoryDump.totalResidentBytes = parseInt(
1614                dumps.process_totals.resident_set_bytes, 16);
1615          }
1616
1617          // Populate the vmRegions, if present.
1618          if (dumps.process_mmaps && dumps.process_mmaps.vm_regions) {
1619            function parseByteStat(rawValue) {
1620              if (rawValue === undefined)
1621                return undefined;
1622              return parseInt(rawValue, 16);
1623            }
1624
1625            processMemoryDump.vmRegions = dumps.process_mmaps.vm_regions.map(
1626              function(rawRegion) {
1627                // See //base/trace_event/process_memory_maps.cc in Chromium.
1628                var byteStats = new tr.model.VMRegionByteStats(
1629                  parseByteStat(rawRegion.bs.pc),
1630                  parseByteStat(rawRegion.bs.pd),
1631                  parseByteStat(rawRegion.bs.sc),
1632                  parseByteStat(rawRegion.bs.sd),
1633                  parseByteStat(rawRegion.bs.pss),
1634                  parseByteStat(rawRegion.bs.sw)
1635                );
1636                return new tr.model.VMRegion(
1637                    parseInt(rawRegion.sa, 16),  // startAddress
1638                    parseInt(rawRegion.sz, 16),  // sizeInBytes
1639                    rawRegion.pf,  // protectionFlags
1640                    rawRegion.mf,  // mappedFile
1641                    byteStats
1642                );
1643              }
1644            );
1645          }
1646
1647          // Gather the process and global memory allocator dumps, if present.
1648          var processMemoryAllocatorDumpsByFullName = {};
1649          if (dumps.allocators !== undefined) {
1650            // Construct the MemoryAllocatorDump objects without parent links
1651            // and add them to the processMemoryAllocatorDumpsByName and
1652            // globalMemoryAllocatorDumpsByName indices appropriately.
1653            tr.b.iterItems(dumps.allocators,
1654                function(fullName, rawAllocatorDump) {
1655              // Every memory allocator dump should have a GUID. If not, then
1656              // it cannot be associated with any edges.
1657              var guid = rawAllocatorDump.guid;
1658              if (guid === undefined) {
1659                this.model_.importWarning({
1660                  type: 'memory_dump_parse_error',
1661                  message: 'Memory allocator dump ' + fullName +
1662                      ' from pid=' + pid + ' does not have a GUID.'
1663                });
1664              }
1665
1666              // Determine if this is a global memory allocator dump (check if
1667              // it's prefixed with 'global/').
1668              var GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX = 'global/';
1669              var containerMemoryDump;
1670              var dstIndex;
1671              if (fullName.startsWith(GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX)) {
1672                // Global memory allocator dump.
1673                fullName = fullName.substring(
1674                    GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX.length);
1675                containerMemoryDump = globalMemoryDump;
1676                dstIndex = globalMemoryAllocatorDumpsByFullName;
1677              } else {
1678                // Process memory allocator dump.
1679                containerMemoryDump = processMemoryDump;
1680                dstIndex = processMemoryAllocatorDumpsByFullName;
1681              }
1682
1683              // Construct or retrieve a memory allocator dump with the provided
1684              // GUID.
1685              var allocatorDump = allMemoryAllocatorDumpsByGuid[guid];
1686              if (allocatorDump === undefined) {
1687                if (fullName in dstIndex) {
1688                  this.model_.importWarning({
1689                    type: 'memory_dump_parse_error',
1690                    message: 'Multiple GUIDs provided for' +
1691                        ' memory allocator dump ' + fullName + ': ' +
1692                        dstIndex[fullName].guid + ', ' + guid + ' (ignored).'
1693                  });
1694                  return;
1695                }
1696                allocatorDump = new tr.model.MemoryAllocatorDump(
1697                    containerMemoryDump, fullName, guid);
1698                dstIndex[fullName] = allocatorDump;
1699                if (guid !== undefined)
1700                  allMemoryAllocatorDumpsByGuid[guid] = allocatorDump;
1701              } else {
1702                // A memory allocator dump with this GUID has already been
1703                // dumped (so we will only add new attributes). Check that it
1704                // belonged to the same process or was also global.
1705                if (allocatorDump.containerMemoryDump !== containerMemoryDump) {
1706                  this.model_.importWarning({
1707                  type: 'memory_dump_parse_error',
1708                  message: 'Memory allocator dump ' + fullName +
1709                      ' (GUID=' + guid + ') dumped in different contexts.'
1710                  });
1711                  return;
1712                }
1713                // Check that the names of the memory allocator dumps match.
1714                if (allocatorDump.fullName !== fullName) {
1715                  this.model_.importWarning({
1716                  type: 'memory_dump_parse_error',
1717                  message: 'Memory allocator dump with GUID=' + guid +
1718                      ' has multiple names: ' + allocatorDump.fullName +
1719                      ', ' + fullName + ' (ignored).'
1720                  });
1721                  return;
1722                }
1723              }
1724
1725              // Add all new attributes to the memory allocator dump.
1726              var attributes = rawAllocatorDump.attrs;
1727              tr.b.iterItems(attributes, function(attrName, attrArgs) {
1728                if (attrName in allocatorDump.attributes) {
1729                  // Skip existing attributes of the memory allocator dump.
1730                  this.model_.importWarning({
1731                  type: 'memory_dump_parse_error',
1732                  message: 'Multiple values provided for attribute ' +
1733                      attrName + ' of memory allocator dump ' + fullName +
1734                      ' (GUID=' + guid + ').'
1735                  });
1736                  return;
1737                }
1738                var attrValue =
1739                    tr.model.Attribute.fromDictIfPossible(attrArgs);
1740                allocatorDump.addAttribute(attrName, attrValue);
1741              }, this);
1742            }, this);
1743          }
1744
1745          // Find the root allocator dumps and establish the parent links of
1746          // the process memory dump.
1747          processMemoryDump.memoryAllocatorDumps =
1748              this.inferMemoryAllocatorDumpTree_(
1749                  processMemoryAllocatorDumpsByFullName);
1750
1751          process.memoryDumps.push(processMemoryDump);
1752          globalMemoryDump.processMemoryDumps[pid] = processMemoryDump;
1753        }, this);
1754
1755        // Find the root allocator dumps and establish the parent links of
1756        // the global memory dump.
1757        globalMemoryDump.memoryAllocatorDumps =
1758            this.inferMemoryAllocatorDumpTree_(
1759                globalMemoryAllocatorDumpsByFullName);
1760
1761        // Set up edges between memory allocator dumps.
1762        events.process.forEach(function(processEvent) {
1763          var dumps = processEvent.args.dumps;
1764          if (dumps === undefined)
1765            return;
1766
1767          var edges = dumps.allocators_graph;
1768          if (edges === undefined)
1769            return;
1770
1771          edges.forEach(function(rawEdge) {
1772            var sourceGuid = rawEdge.source;
1773            var sourceDump = allMemoryAllocatorDumpsByGuid[sourceGuid];
1774            if (sourceDump === undefined) {
1775              this.model_.importWarning({
1776                type: 'memory_dump_parse_error',
1777                message: 'Edge is missing source memory allocator dump (GUID=' +
1778                    sourceGuid + ')'
1779              });
1780              return;
1781            }
1782
1783            var targetGuid = rawEdge.target;
1784            var targetDump = allMemoryAllocatorDumpsByGuid[targetGuid];
1785            if (targetDump === undefined) {
1786              this.model_.importWarning({
1787                type: 'memory_dump_parse_error',
1788                message: 'Edge is missing target memory allocator dump (GUID=' +
1789                    targetGuid + ')'
1790              });
1791              return;
1792            }
1793
1794            var importance = rawEdge.importance;
1795            var edge = new tr.model.MemoryAllocatorDumpLink(
1796                sourceDump, targetDump, importance);
1797
1798            switch (rawEdge.type) {
1799              case 'ownership':
1800                if (sourceDump.owns !== undefined) {
1801                  this.model_.importWarning({
1802                    type: 'memory_dump_parse_error',
1803                    message: 'Memory allocator dump ' + sourceDump.fullName +
1804                        ' (GUID=' + sourceGuid + ') already owns a memory' +
1805                        ' allocator dump (' +
1806                        sourceDump.owns.target.fullName + ').'
1807                  });
1808                  return;
1809                }
1810                sourceDump.owns = edge;
1811                targetDump.ownedBy.push(edge);
1812                break;
1813
1814              case 'retention':
1815                sourceDump.retains.push(edge);
1816                targetDump.retainedBy.push(edge);
1817                break;
1818
1819              default:
1820                this.model_.importWarning({
1821                  type: 'memory_dump_parse_error',
1822                  message: 'Invalid edge type: ' + rawEdge.type +
1823                      ' (source=' + sourceGuid + ', target=' + targetGuid +
1824                      ', importance=' + importance + ').'
1825                });
1826            }
1827          }, this);
1828        }, this);
1829      }, this);
1830    },
1831
1832    inferMemoryAllocatorDumpTree_: function(memoryAllocatorDumpsByFullName) {
1833      var rootAllocatorDumps = [];
1834
1835      var fullNames = Object.keys(memoryAllocatorDumpsByFullName);
1836      fullNames.sort();
1837      fullNames.forEach(function(fullName) {
1838        var allocatorDump = memoryAllocatorDumpsByFullName[fullName];
1839
1840        // This is a loop because we might need to build implicit
1841        // ancestors in case they were not present in the trace.
1842        while (true) {
1843          var lastSlashIndex = fullName.lastIndexOf('/');
1844          if (lastSlashIndex === -1) {
1845            // If the dump is a root, add it to the top-level
1846            // rootAllocatorDumps list.
1847            rootAllocatorDumps.push(allocatorDump);
1848            break;
1849          }
1850
1851          // If the dump is not a root, find its parent.
1852          var parentFullName = fullName.substring(0, lastSlashIndex);
1853          var parentAllocatorDump =
1854              memoryAllocatorDumpsByFullName[parentFullName];
1855
1856          // If the parent dump does not exist yet, we build an implicit
1857          // one and continue up the ancestor chain.
1858          var parentAlreadyExisted = true;
1859          if (parentAllocatorDump === undefined) {
1860            parentAlreadyExisted = false;
1861            parentAllocatorDump = new tr.model.MemoryAllocatorDump(
1862                allocatorDump.containerMemoryDump, parentFullName);
1863            memoryAllocatorDumpsByFullName[parentFullName] =
1864                parentAllocatorDump;
1865          }
1866
1867          // Setup the parent <-> children relationships
1868          allocatorDump.parent = parentAllocatorDump;
1869          parentAllocatorDump.children.push(allocatorDump);
1870
1871          // If the parent already existed, then its ancestors were/will be
1872          // constructed in another iteration of the forEach loop.
1873          if (parentAlreadyExisted)
1874            break;
1875
1876          fullName = parentFullName;
1877          allocatorDump = parentAllocatorDump;
1878        }
1879      }, this);
1880
1881      return rootAllocatorDumps;
1882    },
1883
1884    joinObjectRefs_: function() {
1885      tr.b.iterItems(this.model_.processes, function(pid, process) {
1886        this.joinObjectRefsForProcess_(process);
1887      }, this);
1888    },
1889
1890    joinObjectRefsForProcess_: function(process) {
1891      // Iterate the world, looking for id_refs
1892      var patchupsToApply = [];
1893      tr.b.iterItems(process.threads, function(tid, thread) {
1894        thread.asyncSliceGroup.slices.forEach(function(item) {
1895          this.searchItemForIDRefs_(
1896              patchupsToApply, process.objects, 'start', item);
1897        }, this);
1898        thread.sliceGroup.slices.forEach(function(item) {
1899          this.searchItemForIDRefs_(
1900              patchupsToApply, process.objects, 'start', item);
1901        }, this);
1902      }, this);
1903      process.objects.iterObjectInstances(function(instance) {
1904        instance.snapshots.forEach(function(item) {
1905          this.searchItemForIDRefs_(
1906              patchupsToApply, process.objects, 'ts', item);
1907        }, this);
1908      }, this);
1909
1910      // Change all the fields pointing at id_refs to their real values.
1911      patchupsToApply.forEach(function(patchup) {
1912        patchup.object[patchup.field] = patchup.value;
1913      });
1914    },
1915
1916    searchItemForIDRefs_: function(patchupsToApply, objectCollection,
1917                                   itemTimestampField, item) {
1918      if (!item.args)
1919        throw new Error('item is missing its args');
1920
1921      function handleField(object, fieldName, fieldValue) {
1922        if (!fieldValue || (!fieldValue.id_ref && !fieldValue.idRef))
1923          return;
1924
1925        var id = fieldValue.id_ref || fieldValue.idRef;
1926        var ts = item[itemTimestampField];
1927        var snapshot = objectCollection.getSnapshotAt(id, ts);
1928        if (!snapshot)
1929          return;
1930
1931        // We have to delay the actual change to the new value until after all
1932        // refs have been located. Otherwise, we could end up recursing in
1933        // ways we definitely didn't intend.
1934        patchupsToApply.push({object: object,
1935          field: fieldName,
1936          value: snapshot});
1937      }
1938      function iterObjectFieldsRecursively(object) {
1939        if (!(object instanceof Object))
1940          return;
1941
1942        if ((object instanceof tr.model.ObjectSnapshot) ||
1943            (object instanceof Float32Array) ||
1944            (object instanceof tr.b.Quad))
1945          return;
1946
1947        if (object instanceof Array) {
1948          for (var i = 0; i < object.length; i++) {
1949            handleField(object, i, object[i]);
1950            iterObjectFieldsRecursively(object[i]);
1951          }
1952          return;
1953        }
1954
1955        for (var key in object) {
1956          var value = object[key];
1957          handleField(object, key, value);
1958          iterObjectFieldsRecursively(value);
1959        }
1960      }
1961
1962      iterObjectFieldsRecursively(item.args);
1963    }
1964  };
1965
1966  tr.importer.Importer.register(TraceEventImporter);
1967
1968  return {
1969    TraceEventImporter: TraceEventImporter
1970  };
1971});
1972</script>
1973