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.
8 * @fileoverview TraceEventImporter imports TraceEvent-formatted data
9 * into the provided model.
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');
19 tvcm.exportTo('tracing.importer', function() {
21 var Importer = tracing.importer.Importer;
23 function deepCopy(value) {
24 if (!(value instanceof Object)) {
25 if (value === undefined || value === null)
27 if (typeof value == 'string')
28 return value.substring();
29 if (typeof value == 'boolean')
31 if (typeof value == 'number')
33 throw new Error('Unrecognized: ' + typeof 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]);
44 if (object.__proto__ != Object.prototype)
45 throw new Error('Can only clone simple types');
47 for (var key in object) {
48 res[key] = deepCopy(object[key]);
53 function TraceEventImporter(model, eventData) {
54 this.importPriority = 1;
56 this.events_ = undefined;
57 this.systemTraceEvents_ = undefined;
58 this.eventsWereFromString_ = false;
59 this.allAsyncEvents_ = [];
60 this.allFlowEvents_ = [];
61 this.allObjectEvents_ = [];
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 + ']';
75 this.events_ = JSON.parse(eventData);
76 this.eventsWereFromString_ = true;
78 this.events_ = eventData;
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;
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;
93 // Any other fields in the container should be treated as metadata.
94 for (var fieldName in container) {
95 if (fieldName === 'traceEvents' || fieldName === 'systemTraceEvents')
97 this.model_.metadata.push({name: fieldName,
98 value: container[fieldName]});
104 * @return {boolean} Whether obj is a TraceEvent array.
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] == '[';
116 // Might just be an array of events
117 if (eventData instanceof Array && eventData.length && eventData[0].ph)
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;
128 TraceEventImporter.prototype = {
130 __proto__: Importer.prototype,
132 extractSubtraces: function() {
133 var tmp = this.systemTraceEvents_;
134 this.systemTraceEvents_ = undefined;
135 return tmp ? [tmp] : [];
139 * Deep copying is only needed if the trace was given to us as events.
141 deepCopyIfNeeded_: function(obj) {
142 if (obj === undefined)
144 if (this.eventsWereFromString_)
146 return deepCopy(obj);
150 * Helper to process an async event.
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,
162 * Helper to process a flow event.
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,
175 * Helper that creates and adds samples to a Counter object based on
178 processCounterEvent: function(event) {
180 if (event.id !== undefined)
181 ctr_name = event.name + '[' + event.id + ']';
183 ctr_name = event.name;
185 var ctr = this.model_.getOrCreateProcess(event.pid)
186 .getOrCreateCounter(event.cat, ctr_name);
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)));
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.'
203 delete ctr.parent.counters[ctr.name];
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);
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,
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.'
235 if (event.ph == 'B') {
236 thread.sliceGroup.beginSlice(event.cat, event.name, event.ts / 1000,
237 this.deepCopyIfNeeded_(event.args),
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.'
248 var slice = thread.sliceGroup.endSlice(event.ts / 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'
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.'
267 slice.args[arg] = this.deepCopyIfNeeded_(event.args[arg]);
272 processCompleteEvent: function(event) {
273 var thread = this.model_.getOrCreateProcess(event.pid)
274 .getOrCreateThread(event.tid);
275 thread.sliceGroup.pushCompleteSlice(event.cat, event.name,
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));
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;
304 this.model_.importWarning({
305 type: 'metadata_parse_error',
306 message: 'Unrecognized metadata name: ' + event.name
311 // Treat an Instant event as a duration 0 slice.
312 // SliceTrack's redraw() knows how to handle this.
313 processInstantEvent: function(event) {
317 constructor = tracing.trace_model.GlobalInstantEvent;
320 constructor = tracing.trace_model.ProcessInstantEvent;
325 // Default to thread to support old style input files.
326 constructor = tracing.trace_model.ThreadInstantEvent;
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));
334 switch (instantEvent.type) {
335 case tracing.trace_model.InstantEventType.GLOBAL:
336 this.model_.pushInstantEvent(instantEvent);
339 case tracing.trace_model.InstantEventType.PROCESS:
340 var process = this.model_.getOrCreateProcess(event.pid);
341 process.pushInstantEvent(instantEvent);
344 case tracing.trace_model.InstantEventType.THREAD:
345 var thread = this.model_.getOrCreateProcess(event.pid)
346 .getOrCreateThread(event.tid);
347 thread.sliceGroup.pushInstantEvent(instantEvent);
350 throw new Error('Unknown instant event type: ' + event.s);
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));
362 * Walks through the events_ list and outputs the structures discovered to
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);
372 } else if (event.ph === 'X') {
373 this.processCompleteEvent(event);
375 } else if (event.ph === 'S' || event.ph === 'F' || event.ph === 'T' ||
377 this.processAsyncEvent(event);
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);
384 } else if (event.ph == 'P') {
385 this.processSampleEvent(event);
387 } else if (event.ph == 'C') {
388 this.processCounterEvent(event);
390 } else if (event.ph == 'M') {
391 this.processMetadataEvent(event);
393 } else if (event.ph === 'N' || event.ph === 'D' || event.ph === 'O') {
394 this.processObjectEvent(event);
396 } else if (event.ph === 's' || event.ph === 't' || event.ph === 'f') {
397 this.processFlowEvent(event);
400 this.model_.importWarning({
402 message: 'Unrecognized event phase: ' +
403 event.ph + ' (' + event.name + ')'
410 * Called by the Model after all other importers have imported their
413 finalizeImport: function() {
414 this.createSubSlices_();
415 this.createAsyncSlices_();
416 this.createFlowSlices_();
417 this.createExplicitObjects_();
418 this.createImplicitObjects_();
422 * Called by the model to join references between objects, after final model
423 * bounds have been computed.
425 joinRefs: function() {
426 this.joinObjectRefs_();
429 createSubSlices_: function() {
430 tvcm.iterItems(this.model_.processes, function(pid, process) {
431 tvcm.iterItems(process.threads, function(tid, thread) {
432 thread.createSubSlices();
437 createAsyncSlices_: function() {
438 if (this.allAsyncEvents_.length === 0)
441 this.allAsyncEvents_.sort(function(x, y) {
442 var d = x.event.ts - y.event.ts;
445 return x.sequenceNumber - y.sequenceNumber;
448 var asyncEventStatesByNameThenID = {};
450 var allAsyncEvents = this.allAsyncEvents_;
451 for (var i = 0; i < allAsyncEvents.length; i++) {
452 var asyncEventState = allAsyncEvents[i];
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 ' +
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.'
474 // TODO(simonjam): Add a synchronous tick on the appropriate thread.
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 +
487 asyncEventStatesByNameThenID[name][id] = [];
488 asyncEventStatesByNameThenID[name][id].push(asyncEventState);
490 if (asyncEventStatesByNameThenID[name] === undefined) {
491 this.model_.importWarning({
492 type: 'async_slice_parse_error',
493 message: 'At ' + event.ts + ', no slice named ' + name +
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.'
506 var events = asyncEventStatesByNameThenID[name][id];
507 events.push(asyncEventState);
509 if (event.ph === 'F') {
510 // Create a slice from start to end.
511 var slice = new tracing.trace_model.AsyncSlice(
514 tvcm.ui.getStringColorId(name),
515 events[0].event.ts / 1000);
517 slice.duration = (event.ts / 1000) - (events[0].event.ts / 1000);
519 slice.startThread = events[0].thread;
520 slice.endThread = asyncEventState.thread;
522 slice.args = this.deepCopyIfNeeded_(events[0].event.args);
523 slice.subSlices = [];
525 var stepType = events[1].event.ph;
528 // Create subSlices for each step.
529 for (var j = 1; j < events.length; ++j) {
531 if (events[j].event.ph == 'T' || events[j].event.ph == 'p') {
532 isValid = this.assertStepTypeMatches_(stepType, events[j]);
538 if (stepType == 'T') {
539 targetEvent = events[j - 1];
541 targetEvent = events[j];
544 var subName = events[0].event.name;
545 if (targetEvent.event.ph == 'T' || targetEvent.event.ph == 'p')
546 subName = subName + ':' + targetEvent.event.args.step;
548 var subSlice = new tracing.trace_model.AsyncSlice(
551 tvcm.ui.getStringColorId(subName + j),
552 events[j - 1].event.ts / 1000);
555 (events[j].event.ts / 1000) - (events[j - 1].event.ts / 1000);
557 subSlice.startThread = events[j - 1].thread;
558 subSlice.endThread = events[j].thread;
560 subSlice.args = tvcm.concatenateObjects(events[0].event.args,
561 targetEvent.event.args);
563 slice.subSlices.push(subSlice);
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,
574 // Add |slice| to the start-thread's asyncSlices.
575 slice.startThread.asyncSliceGroup.push(slice);
578 delete asyncEventStatesByNameThenID[name][id];
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.'
597 createFlowSlices_: function() {
598 if (this.allFlowEvents_.length === 0)
601 this.allFlowEvents_.sort(function(x, y) {
602 var d = x.event.ts - y.event.ts;
605 return x.sequenceNumber - y.sequenceNumber;
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;
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.'
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.'
630 var slice = new tracing.trace_model.FlowEvent(
634 tvcm.ui.getStringColorId(event.name),
636 this.deepCopyIfNeeded_(event.args));
637 thread.sliceGroup.pushSlice(slice);
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.'});
646 flowIdToEvent[event.id] = slice;
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.'
658 this.model_.flowEvents.push([flowPosition, slice]);
661 flowPosition.nextFlowEvent = slice;
663 slice.previousFlowEvent = flowPosition;
665 if (event.ph === 'f') {
666 flowIdToEvent[event.id] = undefined;
668 // Make this slice the next start event in this flow.
669 flowIdToEvent[event.id] = slice;
676 * This function creates objects described via the N, D, and O phase
679 createExplicitObjects_: function() {
680 if (this.allObjectEvents_.length == 0)
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.'
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.'
701 var process = thread.parent;
702 var ts = event.ts / 1000;
704 if (event.ph == 'N') {
706 instance = process.objects.idWasCreated(
707 event.id, event.cat, event.name, ts);
709 this.model_.importWarning({
710 type: 'object_parse_error',
711 message: 'While processing create of ' +
712 event.id + ' at ts=' + ts + ': ' + e
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: ...}'
727 var args = this.deepCopyIfNeeded_(event.args.snapshot);
737 if (args.base_type) {
738 baseTypename = args.base_type;
739 delete args.base_type;
741 baseTypename = undefined;
743 snapshot = process.objects.addSnapshot(
744 event.id, cat, event.name, ts,
747 this.model_.importWarning({
748 type: 'object_parse_error',
749 message: 'While processing snapshot of ' +
750 event.id + ' at ts=' + ts + ': ' + e
754 instance = snapshot.objectInstance;
755 } else if (event.ph == 'D') {
757 instance = process.objects.idWasDeleted(
758 event.id, event.cat, event.name, ts);
760 this.model_.importWarning({
761 type: 'object_parse_error',
762 message: 'While processing delete of ' +
763 event.id + ' at ts=' + ts + ': ' + e
770 instance.colorId = tvcm.ui.getStringColorId(instance.typeName);
773 this.allObjectEvents_.sort(function(x, y) {
774 var d = x.event.ts - y.event.ts;
777 return x.sequenceNumber - y.sequenceNumber;
780 var allObjectEvents = this.allObjectEvents_;
781 for (var i = 0; i < allObjectEvents.length; i++) {
782 var objectEventState = allObjectEvents[i];
784 processEvent.call(this, objectEventState);
786 this.model_.importWarning({
787 type: 'object_parse_error',
794 createImplicitObjects_: function() {
795 tvcm.iterItems(this.model_.processes, function(pid, process) {
796 this.createImplicitObjectsForProcess_(process);
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) {
805 function processField(referencingObject,
806 referencingObjectFieldName,
807 referencingObjectFieldValue,
808 containingSnapshot) {
809 if (!referencingObjectFieldValue)
812 if (referencingObjectFieldValue instanceof
813 tracing.trace_model.ObjectSnapshot)
815 if (referencingObjectFieldValue.id === undefined)
818 var implicitSnapshot = referencingObjectFieldValue;
820 var rawId = implicitSnapshot.id;
821 var m = /(.+)\/(.+)/.exec(rawId);
823 throw new Error('Implicit snapshots must have names.');
824 delete implicitSnapshot.id;
830 if (implicitSnapshot.cat !== undefined)
831 cat = implicitSnapshot.cat;
833 cat = containingSnapshot.objectInstance.category;
836 if (implicitSnapshot.base_type)
837 baseTypename = implicitSnapshot.base_type;
839 baseTypename = undefined;
842 res = process.objects.addSnapshot(
844 name, containingSnapshot.ts,
845 implicitSnapshot, baseTypename);
847 this.model_.importWarning({
848 type: 'object_snapshot_parse_error',
849 message: 'While processing implicit snapshot of ' +
850 rawId + ' at ts=' + containingSnapshot.ts + ': ' + e
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');
863 * Iterates over the fields in the object, calling func for every
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.
871 function iterObject(object, func, containingSnapshot, thisArg) {
872 if (!(object instanceof Object))
875 if (object instanceof Array) {
876 for (var i = 0; i < object.length; i++) {
877 var res = func.call(thisArg, object, i, object[i],
882 iterObject(res, func, containingSnapshot, thisArg);
884 iterObject(object[i], func, containingSnapshot, thisArg);
889 for (var key in object) {
890 var res = func.call(thisArg, object, key, object[key],
895 iterObject(res, func, containingSnapshot, thisArg);
897 iterObject(object[key], func, containingSnapshot, thisArg);
901 // TODO(nduca): We may need to iterate the instances in sorted order by
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);
912 joinObjectRefs_: function() {
913 tvcm.iterItems(this.model_.processes, function(pid, process) {
914 this.joinObjectRefsForProcess_(process);
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);
926 thread.sliceGroup.slices.forEach(function(item) {
927 this.searchItemForIDRefs_(
928 patchupsToApply, process.objects, 'start', item);
931 process.objects.iterObjectInstances(function(instance) {
932 instance.snapshots.forEach(function(item) {
933 this.searchItemForIDRefs_(
934 patchupsToApply, process.objects, 'ts', item);
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;
944 searchItemForIDRefs_: function(patchupsToApply, objectCollection,
945 itemTimestampField, item) {
947 throw new Error('item is missing its args');
949 function handleField(object, fieldName, fieldValue) {
950 if (fieldValue === undefined ||
951 (!fieldValue.id_ref && !fieldValue.idRef))
954 var id = fieldValue.id_ref || fieldValue.idRef;
955 var ts = item[itemTimestampField];
956 var snapshot = objectCollection.getSnapshotAt(id, ts);
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,
967 function iterObjectFieldsRecursively(object) {
968 if (!(object instanceof Object))
971 if ((object instanceof tracing.trace_model.ObjectSnapshot) ||
972 (object instanceof Float32Array) ||
973 (object instanceof tvcm.Quad))
976 if (object instanceof Array) {
977 for (var i = 0; i < object.length; i++) {
978 handleField(object, i, object[i]);
979 iterObjectFieldsRecursively(object[i]);
984 for (var key in object) {
985 var value = object[key];
986 handleField(object, key, value);
987 iterObjectFieldsRecursively(value);
991 iterObjectFieldsRecursively(item.args);
995 tracing.TraceModel.registerImporter(TraceEventImporter);
998 TraceEventImporter: TraceEventImporter