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.sampleEvents_ = undefined;
58 this.stackFrameEvents_ = undefined;
59 this.systemTraceEvents_ = undefined;
60 this.eventsWereFromString_ = false;
61 this.allAsyncEvents_ = [];
62 this.allFlowEvents_ = [];
63 this.allObjectEvents_ = [];
65 if (typeof(eventData) === 'string' || eventData instanceof String) {
66 // If the event data begins with a [, then we know it should end with a ].
67 // The reason we check for this is because some tracing implementations
68 // cannot guarantee that a ']' gets written to the trace file. So, we are
69 // forgiving and if this is obviously the case, we fix it up before
70 // throwing the string at JSON.parse.
71 if (eventData[0] === '[') {
72 eventData = eventData.replace(/[\r|\n]*$/, '')
73 .replace(/\s*,\s*$/, '');
74 if (eventData[eventData.length - 1] !== ']')
75 eventData = eventData + ']';
77 this.events_ = JSON.parse(eventData);
78 this.eventsWereFromString_ = true;
80 this.events_ = eventData;
83 // Some trace_event implementations put the actual trace events
84 // inside a container. E.g { ... , traceEvents: [ ] }
85 // If we see that, just pull out the trace events.
86 if (this.events_.traceEvents) {
87 var container = this.events_;
88 this.events_ = this.events_.traceEvents;
90 // Some trace_event implementations put linux_perf_importer traces as a
91 // huge string inside container.systemTraceEvents. If we see that, pull it
92 // out. It will be picked up by extractSubtraces later on.
93 this.systemTraceEvents_ = container.systemTraceEvents;
96 this.sampleEvents_ = container.samples;
97 this.stackFrameEvents_ = container.stackFrames;
99 // Any other fields in the container should be treated as metadata.
100 for (var fieldName in container) {
101 if (fieldName === 'traceEvents' || fieldName === 'systemTraceEvents' ||
102 fieldName === 'samples' || fieldName === 'stackFrames')
104 this.model_.metadata.push({name: fieldName,
105 value: container[fieldName]});
111 * @return {boolean} Whether obj is a TraceEvent array.
113 TraceEventImporter.canImport = function(eventData) {
114 // May be encoded JSON. But we dont want to parse it fully yet.
115 // Use a simple heuristic:
116 // - eventData that starts with [ are probably trace_event
117 // - eventData that starts with { are probably trace_event
118 // May be encoded JSON. Treat files that start with { as importable by us.
119 if (typeof(eventData) === 'string' || eventData instanceof String) {
120 return eventData[0] == '{' || eventData[0] == '[';
123 // Might just be an array of events
124 if (eventData instanceof Array && eventData.length && eventData[0].ph)
127 // Might be an object with a traceEvents field in it.
128 if (eventData.traceEvents) {
129 if (eventData.traceEvents instanceof Array) {
130 if (eventData.traceEvents.length && eventData.traceEvents[0].ph)
132 if (eventData.samples.length && eventData.stackFrames !== undefined)
140 TraceEventImporter.prototype = {
142 __proto__: Importer.prototype,
144 extractSubtraces: function() {
145 var tmp = this.systemTraceEvents_;
146 this.systemTraceEvents_ = undefined;
147 return tmp ? [tmp] : [];
151 * Deep copying is only needed if the trace was given to us as events.
153 deepCopyIfNeeded_: function(obj) {
154 if (obj === undefined)
156 if (this.eventsWereFromString_)
158 return deepCopy(obj);
162 * Helper to process an async event.
164 processAsyncEvent: function(event) {
165 var thread = this.model_.getOrCreateProcess(event.pid).
166 getOrCreateThread(event.tid);
167 this.allAsyncEvents_.push({
168 sequenceNumber: this.allAsyncEvents_.length,
174 * Helper to process a flow event.
176 processFlowEvent: function(event) {
177 var thread = this.model_.getOrCreateProcess(event.pid).
178 getOrCreateThread(event.tid);
179 this.allFlowEvents_.push({
180 sequenceNumber: this.allFlowEvents_.length,
187 * Helper that creates and adds samples to a Counter object based on
190 processCounterEvent: function(event) {
192 if (event.id !== undefined)
193 ctr_name = event.name + '[' + event.id + ']';
195 ctr_name = event.name;
197 var ctr = this.model_.getOrCreateProcess(event.pid)
198 .getOrCreateCounter(event.cat, ctr_name);
200 // Initialize the counter's series fields if needed.
201 if (ctr.numSeries === 0) {
202 for (var seriesName in event.args) {
203 ctr.addSeries(new tracing.trace_model.CounterSeries(seriesName,
204 tvcm.ui.getStringColorId(ctr.name + '.' + seriesName)));
207 if (ctr.numSeries === 0) {
208 this.model_.importWarning({
209 type: 'counter_parse_error',
210 message: 'Expected counter ' + event.name +
211 ' to have at least one argument to use as a value.'
215 delete ctr.parent.counters[ctr.name];
220 var ts = event.ts / 1000;
221 ctr.series.forEach(function(series) {
222 var val = event.args[series.name] ? event.args[series.name] : 0;
223 series.addCounterSample(ts, val);
227 processObjectEvent: function(event) {
228 var thread = this.model_.getOrCreateProcess(event.pid).
229 getOrCreateThread(event.tid);
230 this.allObjectEvents_.push({
231 sequenceNumber: this.allObjectEvents_.length,
236 processDurationEvent: function(event) {
237 var thread = this.model_.getOrCreateProcess(event.pid)
238 .getOrCreateThread(event.tid);
239 if (!thread.sliceGroup.isTimestampValidForBeginOrEnd(event.ts / 1000)) {
240 this.model_.importWarning({
241 type: 'duration_parse_error',
242 message: 'Timestamps are moving backward.'
247 if (event.ph == 'B') {
248 thread.sliceGroup.beginSlice(event.cat, event.name, event.ts / 1000,
249 this.deepCopyIfNeeded_(event.args),
252 if (!thread.sliceGroup.openSliceCount) {
253 this.model_.importWarning({
254 type: 'duration_parse_error',
255 message: 'E phase event without a matching B phase event.'
260 var slice = thread.sliceGroup.endSlice(event.ts / 1000,
262 if (event.name && slice.title != event.name) {
263 this.model_.importWarning({
264 type: 'title_match_error',
265 message: 'Titles do not match. Title is ' +
266 slice.title + ' in openSlice, and is ' +
267 event.name + ' in endSlice'
270 for (var arg in event.args) {
271 if (slice.args[arg] !== undefined) {
272 this.model_.importWarning({
273 type: 'duration_parse_error',
274 message: 'Both the B and E phases of ' + slice.name +
275 ' provided values for argument ' + arg + '.' +
276 ' The value of the E phase event will be used.'
279 slice.args[arg] = this.deepCopyIfNeeded_(event.args[arg]);
284 processCompleteEvent: function(event) {
285 var thread = this.model_.getOrCreateProcess(event.pid)
286 .getOrCreateThread(event.tid);
287 thread.sliceGroup.pushCompleteSlice(event.cat, event.name,
289 event.dur === undefined ? undefined : event.dur / 1000,
290 event.tts === undefined ? undefined : event.tts / 1000,
291 event.tdur === undefined ? undefined : event.tdur / 1000,
292 this.deepCopyIfNeeded_(event.args));
295 processMetadataEvent: function(event) {
296 if (event.name == 'process_name') {
297 var process = this.model_.getOrCreateProcess(event.pid);
298 process.name = event.args.name;
299 } else if (event.name == 'process_labels') {
300 var process = this.model_.getOrCreateProcess(event.pid);
301 var labels = event.args.labels.split(',');
302 for (var i = 0; i < labels.length; i++)
303 process.addLabelIfNeeded(labels[i]);
304 } else if (event.name == 'process_sort_index') {
305 var process = this.model_.getOrCreateProcess(event.pid);
306 process.sortIndex = event.args.sort_index;
307 } else if (event.name == 'thread_name') {
308 var thread = this.model_.getOrCreateProcess(event.pid).
309 getOrCreateThread(event.tid);
310 thread.name = event.args.name;
311 } else if (event.name == 'thread_sort_index') {
312 var thread = this.model_.getOrCreateProcess(event.pid).
313 getOrCreateThread(event.tid);
314 thread.sortIndex = event.args.sort_index;
316 this.model_.importWarning({
317 type: 'metadata_parse_error',
318 message: 'Unrecognized metadata name: ' + event.name
323 // Treat an Instant event as a duration 0 slice.
324 // SliceTrack's redraw() knows how to handle this.
325 processInstantEvent: function(event) {
329 constructor = tracing.trace_model.GlobalInstantEvent;
332 constructor = tracing.trace_model.ProcessInstantEvent;
337 // Default to thread to support old style input files.
338 constructor = tracing.trace_model.ThreadInstantEvent;
342 var colorId = tvcm.ui.getStringColorId(event.name);
343 var instantEvent = new constructor(event.cat, event.name,
344 colorId, event.ts / 1000, this.deepCopyIfNeeded_(event.args));
346 switch (instantEvent.type) {
347 case tracing.trace_model.InstantEventType.GLOBAL:
348 this.model_.pushInstantEvent(instantEvent);
351 case tracing.trace_model.InstantEventType.PROCESS:
352 var process = this.model_.getOrCreateProcess(event.pid);
353 process.pushInstantEvent(instantEvent);
356 case tracing.trace_model.InstantEventType.THREAD:
357 var thread = this.model_.getOrCreateProcess(event.pid)
358 .getOrCreateThread(event.tid);
359 thread.sliceGroup.pushInstantEvent(instantEvent);
362 throw new Error('Unknown instant event type: ' + event.s);
366 processTraceSampleEvent: function(event) {
367 var thread = this.model_.getOrCreateProcess(event.pid)
368 .getOrCreateThread(event.tid);
370 var id = 'te-' + tvcm.GUID.allocate();
371 var stackFrame = new tracing.trace_model.StackFrame(
373 event.cat, event.name,
374 tvcm.ui.getStringColorId(event.name));
375 this.model_.addStackFrame(stackFrame);
377 var sample = new tracing.trace_model.Sample(
378 undefined, thread, 'TRACE_EVENT_SAMPLE',
379 event.ts / 1000, stackFrame, 1,
380 this.deepCopyIfNeeded_(event.args));
381 this.model_.samples.push(sample);
385 * Walks through the events_ list and outputs the structures discovered to
388 importEvents: function() {
389 var events = this.events_;
390 for (var eI = 0; eI < events.length; eI++) {
391 var event = events[eI];
392 if (event.ph === 'B' || event.ph === 'E') {
393 this.processDurationEvent(event);
395 } else if (event.ph === 'X') {
396 this.processCompleteEvent(event);
398 } else if (event.ph === 'S' || event.ph === 'F' || event.ph === 'T' ||
400 this.processAsyncEvent(event);
402 // Note, I is historic. The instant event marker got changed, but we
403 // want to support loading old trace files so we have both I and i.
404 } else if (event.ph == 'I' || event.ph == 'i') {
405 this.processInstantEvent(event);
407 } else if (event.ph == 'P') {
408 this.processTraceSampleEvent(event);
410 } else if (event.ph == 'C') {
411 this.processCounterEvent(event);
413 } else if (event.ph == 'M') {
414 this.processMetadataEvent(event);
416 } else if (event.ph === 'N' || event.ph === 'D' || event.ph === 'O') {
417 this.processObjectEvent(event);
419 } else if (event.ph === 's' || event.ph === 't' || event.ph === 'f') {
420 this.processFlowEvent(event);
423 this.model_.importWarning({
425 message: 'Unrecognized event phase: ' +
426 event.ph + ' (' + event.name + ')'
431 if (this.stackFrameEvents_)
432 this.importStackFrames_();
435 importStackFrames_: function() {
437 var events = this.stackFrameEvents_;
438 for (var id in events) {
439 var event = events[id];
440 var textForColor = event.category ? event.category : event.name;
441 var frame = new tracing.trace_model.StackFrame(
443 event.category, event.name,
444 tvcm.ui.getStringColorId(textForColor));
445 m.addStackFrame(frame);
447 for (var id in events) {
448 var event = events[id];
449 if (event.parent === undefined)
452 var frame = m.stackFrames[id];
453 if (frame === undefined)
454 throw new Error('omg');
456 if (event.parent === undefined) {
457 parentFrame = undefined;
459 parentFrame = m.stackFrames[event.parent];
460 if (parentFrame === undefined)
461 throw new Error('omg');
463 frame.parentFrame = parentFrame;
468 * Called by the Model after all other importers have imported their
471 finalizeImport: function() {
472 this.createAsyncSlices_();
473 this.createFlowSlices_();
474 this.createExplicitObjects_();
475 this.createImplicitObjects_();
478 importSampleData: function() {
479 if (!this.sampleEvents_)
483 // If this is the only importer, then fake-create the threads.
484 var events = this.sampleEvents_;
485 if (this.events_.length === 0) {
486 for (var i = 0; i < events.length; i++) {
487 var event = events[i];
488 m.getOrCreateProcess(event.tid).getOrCreateThread(event.tid);
492 var threadsByTid = {};
493 m.getAllThreads().forEach(function(t) {
494 threadsByTid[t.tid] = t;
497 for (var i = 0; i < events.length; i++) {
498 var event = events[i];
499 var thread = threadsByTid[event.tid];
500 if (thread === undefined) {
502 type: 'sample_import_error',
503 message: 'Thread ' + events.tid + 'not found'
509 if (event.cpu !== undefined)
510 cpu = m.kernel.getOrCreateCpu(event.cpu);
512 var stackFrame = m.stackFrames[event.sf];
513 if (stackFrame === undefined) {
515 type: 'sample_import_error',
516 message: 'No frame for ' + event.sf
521 var sample = new tracing.trace_model.Sample(
523 event.name, event.ts / 1000,
526 m.samples.push(sample);
531 * Called by the model to join references between objects, after final model
532 * bounds have been computed.
534 joinRefs: function() {
535 this.joinObjectRefs_();
538 createAsyncSlices_: function() {
539 if (this.allAsyncEvents_.length === 0)
542 this.allAsyncEvents_.sort(function(x, y) {
543 var d = x.event.ts - y.event.ts;
546 return x.sequenceNumber - y.sequenceNumber;
549 var asyncEventStatesByNameThenID = {};
551 var allAsyncEvents = this.allAsyncEvents_;
552 for (var i = 0; i < allAsyncEvents.length; i++) {
553 var asyncEventState = allAsyncEvents[i];
555 var event = asyncEventState.event;
556 var name = event.name;
557 if (name === undefined) {
558 this.model_.importWarning({
559 type: 'async_slice_parse_error',
560 message: 'Async events (ph: S, T, p, or F) require a name ' +
567 if (id === undefined) {
568 this.model_.importWarning({
569 type: 'async_slice_parse_error',
570 message: 'Async events (ph: S, T, p, or F) require an id parameter.'
575 // TODO(simonjam): Add a synchronous tick on the appropriate thread.
577 if (event.ph === 'S') {
578 if (asyncEventStatesByNameThenID[name] === undefined)
579 asyncEventStatesByNameThenID[name] = {};
580 if (asyncEventStatesByNameThenID[name][id]) {
581 this.model_.importWarning({
582 type: 'async_slice_parse_error',
583 message: 'At ' + event.ts + ', a slice of the same id ' + id +
588 asyncEventStatesByNameThenID[name][id] = [];
589 asyncEventStatesByNameThenID[name][id].push(asyncEventState);
591 if (asyncEventStatesByNameThenID[name] === undefined) {
592 this.model_.importWarning({
593 type: 'async_slice_parse_error',
594 message: 'At ' + event.ts + ', no slice named ' + name +
599 if (asyncEventStatesByNameThenID[name][id] === undefined) {
600 this.model_.importWarning({
601 type: 'async_slice_parse_error',
602 message: 'At ' + event.ts + ', no slice named ' + name +
603 ' with id=' + id + ' was open.'
607 var events = asyncEventStatesByNameThenID[name][id];
608 events.push(asyncEventState);
610 if (event.ph === 'F') {
611 // Create a slice from start to end.
612 var slice = new tracing.trace_model.AsyncSlice(
615 tvcm.ui.getStringColorId(name),
616 events[0].event.ts / 1000);
618 slice.duration = (event.ts / 1000) - (events[0].event.ts / 1000);
620 slice.startThread = events[0].thread;
621 slice.endThread = asyncEventState.thread;
623 slice.args = this.deepCopyIfNeeded_(events[0].event.args);
624 slice.subSlices = [];
626 var stepType = events[1].event.ph;
629 // Create subSlices for each step.
630 for (var j = 1; j < events.length; ++j) {
632 if (events[j].event.ph == 'T' || events[j].event.ph == 'p') {
633 isValid = this.assertStepTypeMatches_(stepType, events[j]);
639 if (stepType == 'T') {
640 targetEvent = events[j - 1];
642 targetEvent = events[j];
645 var subName = events[0].event.name;
646 if (targetEvent.event.ph == 'T' || targetEvent.event.ph == 'p')
647 subName = subName + ':' + targetEvent.event.args.step;
649 var subSlice = new tracing.trace_model.AsyncSlice(
652 tvcm.ui.getStringColorId(subName + j),
653 events[j - 1].event.ts / 1000);
656 (events[j].event.ts / 1000) - (events[j - 1].event.ts / 1000);
658 subSlice.startThread = events[j - 1].thread;
659 subSlice.endThread = events[j].thread;
661 subSlice.args = tvcm.concatenateObjects(events[0].event.args,
662 targetEvent.event.args);
664 slice.subSlices.push(subSlice);
666 if (events[j].event.ph == 'F' && stepType == 'T') {
667 // The args for the finish event go in the last subSlice.
668 var lastSlice = slice.subSlices[slice.subSlices.length - 1];
669 lastSlice.args = tvcm.concatenateObjects(lastSlice.args,
675 // Add |slice| to the start-thread's asyncSlices.
676 slice.startThread.asyncSliceGroup.push(slice);
679 delete asyncEventStatesByNameThenID[name][id];
685 assertStepTypeMatches_: function(stepType, event) {
686 if (stepType != event.event.ph) {
687 this.model_.importWarning({
688 type: 'async_slice_parse_error',
689 message: 'At ' + event.event.ts + ', a slice named ' +
690 event.event.name + ' with id=' + event.event.id +
691 ' had both begin and end steps, which is not allowed.'
698 createFlowSlices_: function() {
699 if (this.allFlowEvents_.length === 0)
702 this.allFlowEvents_.sort(function(x, y) {
703 var d = x.event.ts - y.event.ts;
706 return x.sequenceNumber - y.sequenceNumber;
709 var flowIdToEvent = {};
710 for (var i = 0; i < this.allFlowEvents_.length; ++i) {
711 var data = this.allFlowEvents_[i];
712 var event = data.event;
713 var thread = data.thread;
715 if (event.name === undefined) {
716 this.model_.importWarning({
717 type: 'flow_slice_parse_error',
718 message: 'Flow events (ph: s, t or f) require a name parameter.'
723 if (event.id === undefined) {
724 this.model_.importWarning({
725 type: 'flow_slice_parse_error',
726 message: 'Flow events (ph: s, t or f) require an id parameter.'
731 var slice = new tracing.trace_model.FlowEvent(
735 tvcm.ui.getStringColorId(event.name),
737 this.deepCopyIfNeeded_(event.args));
738 thread.sliceGroup.pushSlice(slice);
740 if (event.ph === 's') {
741 if (flowIdToEvent[event.id] !== undefined) {
742 this.model_.importWarning({
743 type: 'flow_slice_start_error',
744 message: 'event id ' + event.id + ' already seen when ' +
745 'encountering start of flow event.'});
747 flowIdToEvent[event.id] = slice;
749 } else if (event.ph === 't' || event.ph === 'f') {
750 var flowPosition = flowIdToEvent[event.id];
751 if (flowPosition === undefined) {
752 this.model_.importWarning({
753 type: 'flow_slice_ordering_error',
754 message: 'Found flow phase ' + event.ph + ' for id: ' + event.id +
755 ' but no flow start found.'
759 this.model_.flowEvents.push([flowPosition, slice]);
762 flowPosition.nextFlowEvent = slice;
764 slice.previousFlowEvent = flowPosition;
766 if (event.ph === 'f') {
767 flowIdToEvent[event.id] = undefined;
769 // Make this slice the next start event in this flow.
770 flowIdToEvent[event.id] = slice;
777 * This function creates objects described via the N, D, and O phase
780 createExplicitObjects_: function() {
781 if (this.allObjectEvents_.length == 0)
784 function processEvent(objectEventState) {
785 var event = objectEventState.event;
786 var thread = objectEventState.thread;
787 if (event.name === undefined) {
788 this.model_.importWarning({
789 type: 'object_parse_error',
790 message: 'While processing ' + JSON.stringify(event) + ': ' +
791 'Object events require an name parameter.'
795 if (event.id === undefined) {
796 this.model_.importWarning({
797 type: 'object_parse_error',
798 message: 'While processing ' + JSON.stringify(event) + ': ' +
799 'Object events require an id parameter.'
802 var process = thread.parent;
803 var ts = event.ts / 1000;
805 if (event.ph == 'N') {
807 instance = process.objects.idWasCreated(
808 event.id, event.cat, event.name, ts);
810 this.model_.importWarning({
811 type: 'object_parse_error',
812 message: 'While processing create of ' +
813 event.id + ' at ts=' + ts + ': ' + e
817 } else if (event.ph == 'O') {
818 if (event.args.snapshot === undefined) {
819 this.model_.importWarning({
820 type: 'object_parse_error',
821 message: 'While processing ' + event.id + ' at ts=' + ts + ': ' +
822 'Snapshots must have args: {snapshot: ...}'
828 var args = this.deepCopyIfNeeded_(event.args.snapshot);
838 if (args.base_type) {
839 baseTypename = args.base_type;
840 delete args.base_type;
842 baseTypename = undefined;
844 snapshot = process.objects.addSnapshot(
845 event.id, cat, event.name, ts,
848 this.model_.importWarning({
849 type: 'object_parse_error',
850 message: 'While processing snapshot of ' +
851 event.id + ' at ts=' + ts + ': ' + e
855 instance = snapshot.objectInstance;
856 } else if (event.ph == 'D') {
858 instance = process.objects.idWasDeleted(
859 event.id, event.cat, event.name, ts);
861 this.model_.importWarning({
862 type: 'object_parse_error',
863 message: 'While processing delete of ' +
864 event.id + ' at ts=' + ts + ': ' + e
871 instance.colorId = tvcm.ui.getStringColorId(instance.typeName);
874 this.allObjectEvents_.sort(function(x, y) {
875 var d = x.event.ts - y.event.ts;
878 return x.sequenceNumber - y.sequenceNumber;
881 var allObjectEvents = this.allObjectEvents_;
882 for (var i = 0; i < allObjectEvents.length; i++) {
883 var objectEventState = allObjectEvents[i];
885 processEvent.call(this, objectEventState);
887 this.model_.importWarning({
888 type: 'object_parse_error',
895 createImplicitObjects_: function() {
896 tvcm.iterItems(this.model_.processes, function(pid, process) {
897 this.createImplicitObjectsForProcess_(process);
901 // Here, we collect all the snapshots that internally contain a
902 // Javascript-level object inside their args list that has an "id" field,
903 // and turn that into a snapshot of the instance referred to by id.
904 createImplicitObjectsForProcess_: function(process) {
906 function processField(referencingObject,
907 referencingObjectFieldName,
908 referencingObjectFieldValue,
909 containingSnapshot) {
910 if (!referencingObjectFieldValue)
913 if (referencingObjectFieldValue instanceof
914 tracing.trace_model.ObjectSnapshot)
916 if (referencingObjectFieldValue.id === undefined)
919 var implicitSnapshot = referencingObjectFieldValue;
921 var rawId = implicitSnapshot.id;
922 var m = /(.+)\/(.+)/.exec(rawId);
924 throw new Error('Implicit snapshots must have names.');
925 delete implicitSnapshot.id;
931 if (implicitSnapshot.cat !== undefined)
932 cat = implicitSnapshot.cat;
934 cat = containingSnapshot.objectInstance.category;
937 if (implicitSnapshot.base_type)
938 baseTypename = implicitSnapshot.base_type;
940 baseTypename = undefined;
943 res = process.objects.addSnapshot(
945 name, containingSnapshot.ts,
946 implicitSnapshot, baseTypename);
948 this.model_.importWarning({
949 type: 'object_snapshot_parse_error',
950 message: 'While processing implicit snapshot of ' +
951 rawId + ' at ts=' + containingSnapshot.ts + ': ' + e
955 res.objectInstance.hasImplicitSnapshots = true;
956 res.containingSnapshot = containingSnapshot;
957 referencingObject[referencingObjectFieldName] = res;
958 if (!(res instanceof tracing.trace_model.ObjectSnapshot))
959 throw new Error('Created object must be instanceof snapshot');
964 * Iterates over the fields in the object, calling func for every
967 * @return {object} If the function does not want the field's value to be
968 * iterated, return null. If iteration of the field value is desired, then
969 * return either undefined (if the field value did not change) or the new
970 * field value if it was changed.
972 function iterObject(object, func, containingSnapshot, thisArg) {
973 if (!(object instanceof Object))
976 if (object instanceof Array) {
977 for (var i = 0; i < object.length; i++) {
978 var res = func.call(thisArg, object, i, object[i],
983 iterObject(res, func, containingSnapshot, thisArg);
985 iterObject(object[i], func, containingSnapshot, thisArg);
990 for (var key in object) {
991 var res = func.call(thisArg, object, key, object[key],
996 iterObject(res, func, containingSnapshot, thisArg);
998 iterObject(object[key], func, containingSnapshot, thisArg);
1002 // TODO(nduca): We may need to iterate the instances in sorted order by
1004 process.objects.iterObjectInstances(function(instance) {
1005 instance.snapshots.forEach(function(snapshot) {
1006 if (snapshot.args.id !== undefined)
1007 throw new Error('args cannot have an id field inside it');
1008 iterObject(snapshot.args, processField, snapshot, this);
1013 joinObjectRefs_: function() {
1014 tvcm.iterItems(this.model_.processes, function(pid, process) {
1015 this.joinObjectRefsForProcess_(process);
1019 joinObjectRefsForProcess_: function(process) {
1020 // Iterate the world, looking for id_refs
1021 var patchupsToApply = [];
1022 tvcm.iterItems(process.threads, function(tid, thread) {
1023 thread.asyncSliceGroup.slices.forEach(function(item) {
1024 this.searchItemForIDRefs_(
1025 patchupsToApply, process.objects, 'start', item);
1027 thread.sliceGroup.slices.forEach(function(item) {
1028 this.searchItemForIDRefs_(
1029 patchupsToApply, process.objects, 'start', item);
1032 process.objects.iterObjectInstances(function(instance) {
1033 instance.snapshots.forEach(function(item) {
1034 this.searchItemForIDRefs_(
1035 patchupsToApply, process.objects, 'ts', item);
1039 // Change all the fields pointing at id_refs to their real values.
1040 patchupsToApply.forEach(function(patchup) {
1041 patchup.object[patchup.field] = patchup.value;
1045 searchItemForIDRefs_: function(patchupsToApply, objectCollection,
1046 itemTimestampField, item) {
1048 throw new Error('item is missing its args');
1050 function handleField(object, fieldName, fieldValue) {
1051 if (fieldValue === undefined ||
1052 (!fieldValue.id_ref && !fieldValue.idRef))
1055 var id = fieldValue.id_ref || fieldValue.idRef;
1056 var ts = item[itemTimestampField];
1057 var snapshot = objectCollection.getSnapshotAt(id, ts);
1061 // We have to delay the actual change to the new value until after all
1062 // refs have been located. Otherwise, we could end up recursing in
1063 // ways we definitely didn't intend.
1064 patchupsToApply.push({object: object,
1068 function iterObjectFieldsRecursively(object) {
1069 if (!(object instanceof Object))
1072 if ((object instanceof tracing.trace_model.ObjectSnapshot) ||
1073 (object instanceof Float32Array) ||
1074 (object instanceof tvcm.Quad))
1077 if (object instanceof Array) {
1078 for (var i = 0; i < object.length; i++) {
1079 handleField(object, i, object[i]);
1080 iterObjectFieldsRecursively(object[i]);
1085 for (var key in object) {
1086 var value = object[key];
1087 handleField(object, key, value);
1088 iterObjectFieldsRecursively(value);
1092 iterObjectFieldsRecursively(item.args);
1096 tracing.TraceModel.registerImporter(TraceEventImporter);
1099 TraceEventImporter: TraceEventImporter