3 Copyright (c) 2012 The Chromium Authors. All rights reserved.
4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file.
8 <link rel="import" href="/base/quad.html">
9 <link rel="import" href="/tracing/trace_model.html">
10 <link rel="import" href="/tracing/color_scheme.html">
11 <link rel="import" href="/tracing/importer/importer.html">
12 <link rel="import" href="/tracing/trace_model/instant_event.html">
13 <link rel="import" href="/tracing/trace_model/flow_event.html">
14 <link rel="import" href="/tracing/trace_model/counter_series.html">
15 <link rel="import" href="/tracing/trace_model/slice_group.html">
21 * @fileoverview TraceEventImporter imports TraceEvent-formatted data
22 * into the provided model.
24 tv.exportTo('tracing.importer', function() {
26 var Importer = tracing.importer.Importer;
28 function deepCopy(value) {
29 if (!(value instanceof Object)) {
30 if (value === undefined || value === null)
32 if (typeof value == 'string')
33 return value.substring();
34 if (typeof value == 'boolean')
36 if (typeof value == 'number')
38 throw new Error('Unrecognized: ' + typeof value);
42 if (object instanceof Array) {
43 var res = new Array(object.length);
44 for (var i = 0; i < object.length; i++)
45 res[i] = deepCopy(object[i]);
49 if (object.__proto__ != Object.prototype)
50 throw new Error('Can only clone simple types');
52 for (var key in object) {
53 res[key] = deepCopy(object[key]);
58 function TraceEventImporter(model, eventData) {
59 this.importPriority = 1;
61 this.events_ = undefined;
62 this.sampleEvents_ = undefined;
63 this.stackFrameEvents_ = undefined;
64 this.systemTraceEvents_ = undefined;
65 this.eventsWereFromString_ = false;
66 this.softwareMeasuredCpuCount_ = undefined;
67 this.allAsyncEvents_ = [];
68 this.allFlowEvents_ = [];
69 this.allObjectEvents_ = [];
71 if (typeof(eventData) === 'string' || eventData instanceof String) {
72 // If the event data begins with a [, then we know it should end with a ].
73 // The reason we check for this is because some tracing implementations
74 // cannot guarantee that a ']' gets written to the trace file. So, we are
75 // forgiving and if this is obviously the case, we fix it up before
76 // throwing the string at JSON.parse.
77 if (eventData[0] === '[') {
78 eventData = eventData.replace(/[\r|\n]*$/, '')
79 .replace(/\s*,\s*$/, '');
80 if (eventData[eventData.length - 1] !== ']')
81 eventData = eventData + ']';
83 this.events_ = JSON.parse(eventData);
84 this.eventsWereFromString_ = true;
86 this.events_ = eventData;
89 // Some trace_event implementations put the actual trace events
90 // inside a container. E.g { ... , traceEvents: [ ] }
91 // If we see that, just pull out the trace events.
92 if (this.events_.traceEvents) {
93 var container = this.events_;
94 this.events_ = this.events_.traceEvents;
96 // Some trace_event implementations put linux_perf_importer traces as a
97 // huge string inside container.systemTraceEvents. If we see that, pull it
98 // out. It will be picked up by extractSubtraces later on.
99 this.systemTraceEvents_ = container.systemTraceEvents;
102 this.sampleEvents_ = container.samples;
103 this.stackFrameEvents_ = container.stackFrames;
105 // Any other fields in the container should be treated as metadata.
106 for (var fieldName in container) {
107 if (fieldName === 'traceEvents' || fieldName === 'systemTraceEvents' ||
108 fieldName === 'samples' || fieldName === 'stackFrames')
110 this.model_.metadata.push({name: fieldName,
111 value: container[fieldName]});
117 * @return {boolean} Whether obj is a TraceEvent array.
119 TraceEventImporter.canImport = function(eventData) {
120 // May be encoded JSON. But we dont want to parse it fully yet.
121 // Use a simple heuristic:
122 // - eventData that starts with [ are probably trace_event
123 // - eventData that starts with { are probably trace_event
124 // May be encoded JSON. Treat files that start with { as importable by us.
125 if (typeof(eventData) === 'string' || eventData instanceof String) {
126 return eventData[0] == '{' || eventData[0] == '[';
129 // Might just be an array of events
130 if (eventData instanceof Array && eventData.length && eventData[0].ph)
133 // Might be an object with a traceEvents field in it.
134 if (eventData.traceEvents) {
135 if (eventData.traceEvents instanceof Array) {
136 if (eventData.traceEvents.length && eventData.traceEvents[0].ph)
138 if (eventData.samples.length && eventData.stackFrames !== undefined)
146 TraceEventImporter.prototype = {
148 __proto__: Importer.prototype,
150 extractSubtraces: function() {
151 var tmp = this.systemTraceEvents_;
152 this.systemTraceEvents_ = undefined;
153 return tmp ? [tmp] : [];
157 * Deep copying is only needed if the trace was given to us as events.
159 deepCopyIfNeeded_: function(obj) {
160 if (obj === undefined)
162 if (this.eventsWereFromString_)
164 return deepCopy(obj);
168 * Helper to process an async event.
170 processAsyncEvent: function(event) {
171 var thread = this.model_.getOrCreateProcess(event.pid).
172 getOrCreateThread(event.tid);
173 this.allAsyncEvents_.push({
174 sequenceNumber: this.allAsyncEvents_.length,
180 * Helper to process a flow event.
182 processFlowEvent: function(event) {
183 var thread = this.model_.getOrCreateProcess(event.pid).
184 getOrCreateThread(event.tid);
185 this.allFlowEvents_.push({
186 sequenceNumber: this.allFlowEvents_.length,
193 * Helper that creates and adds samples to a Counter object based on
196 processCounterEvent: function(event) {
198 if (event.id !== undefined)
199 ctr_name = event.name + '[' + event.id + ']';
201 ctr_name = event.name;
203 var ctr = this.model_.getOrCreateProcess(event.pid)
204 .getOrCreateCounter(event.cat, ctr_name);
206 // Initialize the counter's series fields if needed.
207 if (ctr.numSeries === 0) {
208 for (var seriesName in event.args) {
209 ctr.addSeries(new tracing.trace_model.CounterSeries(seriesName,
210 tv.ui.getStringColorId(ctr.name + '.' + seriesName)));
213 if (ctr.numSeries === 0) {
214 this.model_.importWarning({
215 type: 'counter_parse_error',
216 message: 'Expected counter ' + event.name +
217 ' to have at least one argument to use as a value.'
221 delete ctr.parent.counters[ctr.name];
226 var ts = event.ts / 1000;
227 ctr.series.forEach(function(series) {
228 var val = event.args[series.name] ? event.args[series.name] : 0;
229 series.addCounterSample(ts, val);
233 processObjectEvent: function(event) {
234 var thread = this.model_.getOrCreateProcess(event.pid).
235 getOrCreateThread(event.tid);
236 this.allObjectEvents_.push({
237 sequenceNumber: this.allObjectEvents_.length,
242 processDurationEvent: function(event) {
243 var thread = this.model_.getOrCreateProcess(event.pid)
244 .getOrCreateThread(event.tid);
245 if (!thread.sliceGroup.isTimestampValidForBeginOrEnd(event.ts / 1000)) {
246 this.model_.importWarning({
247 type: 'duration_parse_error',
248 message: 'Timestamps are moving backward.'
253 if (event.ph == 'B') {
254 thread.sliceGroup.beginSlice(event.cat, event.name, event.ts / 1000,
255 this.deepCopyIfNeeded_(event.args),
258 if (!thread.sliceGroup.openSliceCount) {
259 this.model_.importWarning({
260 type: 'duration_parse_error',
261 message: 'E phase event without a matching B phase event.'
266 var slice = thread.sliceGroup.endSlice(event.ts / 1000,
268 if (event.name && slice.title != event.name) {
269 this.model_.importWarning({
270 type: 'title_match_error',
271 message: 'Titles do not match. Title is ' +
272 slice.title + ' in openSlice, and is ' +
273 event.name + ' in endSlice'
276 for (var arg in event.args) {
277 if (slice.args[arg] !== undefined) {
278 this.model_.importWarning({
279 type: 'duration_parse_error',
280 message: 'Both the B and E phases of ' + slice.name +
281 ' provided values for argument ' + arg + '.' +
282 ' The value of the E phase event will be used.'
285 slice.args[arg] = this.deepCopyIfNeeded_(event.args[arg]);
290 processCompleteEvent: function(event) {
291 var thread = this.model_.getOrCreateProcess(event.pid)
292 .getOrCreateThread(event.tid);
293 thread.sliceGroup.pushCompleteSlice(event.cat, event.name,
295 event.dur === undefined ? undefined : event.dur / 1000,
296 event.tts === undefined ? undefined : event.tts / 1000,
297 event.tdur === undefined ? undefined : event.tdur / 1000,
298 this.deepCopyIfNeeded_(event.args));
301 processMetadataEvent: function(event) {
302 if (event.name == 'process_name') {
303 var process = this.model_.getOrCreateProcess(event.pid);
304 process.name = event.args.name;
305 } else if (event.name == 'process_labels') {
306 var process = this.model_.getOrCreateProcess(event.pid);
307 var labels = event.args.labels.split(',');
308 for (var i = 0; i < labels.length; i++)
309 process.addLabelIfNeeded(labels[i]);
310 } else if (event.name == 'process_sort_index') {
311 var process = this.model_.getOrCreateProcess(event.pid);
312 process.sortIndex = event.args.sort_index;
313 } else if (event.name == 'thread_name') {
314 var thread = this.model_.getOrCreateProcess(event.pid).
315 getOrCreateThread(event.tid);
316 thread.name = event.args.name;
317 } else if (event.name == 'thread_sort_index') {
318 var thread = this.model_.getOrCreateProcess(event.pid).
319 getOrCreateThread(event.tid);
320 thread.sortIndex = event.args.sort_index;
321 } else if (event.name == 'num_cpus') {
322 var n = event.args.number;
323 // Not all render processes agree on the cpu count in trace_event. Some
324 // processes will report 1, while others will report the actual cpu
325 // count. To deal with this, take the max of what is reported.
326 if (this.softwareMeasuredCpuCount_ !== undefined)
327 n = Math.max(n, this.softwareMeasuredCpuCount_);
328 this.softwareMeasuredCpuCount_ = n;
330 this.model_.importWarning({
331 type: 'metadata_parse_error',
332 message: 'Unrecognized metadata name: ' + event.name
337 // Treat an Instant event as a duration 0 slice.
338 // SliceTrack's redraw() knows how to handle this.
339 processInstantEvent: function(event) {
343 constructor = tracing.trace_model.GlobalInstantEvent;
346 constructor = tracing.trace_model.ProcessInstantEvent;
351 // Default to thread to support old style input files.
352 constructor = tracing.trace_model.ThreadInstantEvent;
356 var colorId = tv.ui.getStringColorId(event.name);
357 var instantEvent = new constructor(event.cat, event.name,
358 colorId, event.ts / 1000, this.deepCopyIfNeeded_(event.args));
360 switch (instantEvent.type) {
361 case tracing.trace_model.InstantEventType.GLOBAL:
362 this.model_.pushInstantEvent(instantEvent);
365 case tracing.trace_model.InstantEventType.PROCESS:
366 var process = this.model_.getOrCreateProcess(event.pid);
367 process.pushInstantEvent(instantEvent);
370 case tracing.trace_model.InstantEventType.THREAD:
371 var thread = this.model_.getOrCreateProcess(event.pid)
372 .getOrCreateThread(event.tid);
373 thread.sliceGroup.pushInstantEvent(instantEvent);
377 throw new Error('Unknown instant event type: ' + event.s);
381 processTraceSampleEvent: function(event) {
382 var thread = this.model_.getOrCreateProcess(event.pid)
383 .getOrCreateThread(event.tid);
385 var id = 'te-' + tv.GUID.allocate();
386 var stackFrame = new tracing.trace_model.StackFrame(
388 event.cat, event.name,
389 tv.ui.getStringColorId(event.name));
390 this.model_.addStackFrame(stackFrame);
392 var sample = new tracing.trace_model.Sample(
393 undefined, thread, 'TRACE_EVENT_SAMPLE',
394 event.ts / 1000, stackFrame, 1,
395 this.deepCopyIfNeeded_(event.args));
396 this.model_.samples.push(sample);
400 * Walks through the events_ list and outputs the structures discovered to
403 importEvents: function() {
404 var events = this.events_;
405 for (var eI = 0; eI < events.length; eI++) {
406 var event = events[eI];
407 if (event.ph === 'B' || event.ph === 'E') {
408 this.processDurationEvent(event);
410 } else if (event.ph === 'X') {
411 this.processCompleteEvent(event);
413 } else if (event.ph === 'b' || event.ph === 'e' || event.ph === 'n' ||
414 event.ph === 'S' || event.ph === 'F' || event.ph === 'T' ||
416 this.processAsyncEvent(event);
418 // Note, I is historic. The instant event marker got changed, but we
419 // want to support loading old trace files so we have both I and i.
420 } else if (event.ph == 'I' || event.ph == 'i') {
421 this.processInstantEvent(event);
423 } else if (event.ph == 'P') {
424 this.processTraceSampleEvent(event);
426 } else if (event.ph == 'C') {
427 this.processCounterEvent(event);
429 } else if (event.ph == 'M') {
430 this.processMetadataEvent(event);
432 } else if (event.ph === 'N' || event.ph === 'D' || event.ph === 'O') {
433 this.processObjectEvent(event);
435 } else if (event.ph === 's' || event.ph === 't' || event.ph === 'f') {
436 this.processFlowEvent(event);
439 this.model_.importWarning({
441 message: 'Unrecognized event phase: ' +
442 event.ph + ' (' + event.name + ')'
447 if (this.stackFrameEvents_)
448 this.importStackFrames_();
451 importStackFrames_: function() {
453 var events = this.stackFrameEvents_;
454 for (var id in events) {
455 var event = events[id];
456 var textForColor = event.category ? event.category : event.name;
457 var frame = new tracing.trace_model.StackFrame(
459 event.category, event.name,
460 tv.ui.getStringColorId(textForColor));
461 m.addStackFrame(frame);
463 for (var id in events) {
464 var event = events[id];
465 if (event.parent === undefined)
468 var frame = m.stackFrames[id];
469 if (frame === undefined)
470 throw new Error('omg');
472 if (event.parent === undefined) {
473 parentFrame = undefined;
475 parentFrame = m.stackFrames[event.parent];
476 if (parentFrame === undefined)
477 throw new Error('omg');
479 frame.parentFrame = parentFrame;
484 * Called by the Model after all other importers have imported their
487 finalizeImport: function() {
488 if (this.softwareMeasuredCpuCount_ !== undefined) {
489 this.model_.kernel.softwareMeasuredCpuCount =
490 this.softwareMeasuredCpuCount_;
492 this.createAsyncSlices_();
493 this.createFlowSlices_();
494 this.createExplicitObjects_();
495 this.createImplicitObjects_();
498 importSampleData: function() {
499 if (!this.sampleEvents_)
503 // If this is the only importer, then fake-create the threads.
504 var events = this.sampleEvents_;
505 if (this.events_.length === 0) {
506 for (var i = 0; i < events.length; i++) {
507 var event = events[i];
508 m.getOrCreateProcess(event.tid).getOrCreateThread(event.tid);
512 var threadsByTid = {};
513 m.getAllThreads().forEach(function(t) {
514 threadsByTid[t.tid] = t;
517 for (var i = 0; i < events.length; i++) {
518 var event = events[i];
519 var thread = threadsByTid[event.tid];
520 if (thread === undefined) {
522 type: 'sample_import_error',
523 message: 'Thread ' + events.tid + 'not found'
529 if (event.cpu !== undefined)
530 cpu = m.kernel.getOrCreateCpu(event.cpu);
532 var stackFrame = m.stackFrames[event.sf];
533 if (stackFrame === undefined) {
535 type: 'sample_import_error',
536 message: 'No frame for ' + event.sf
541 var sample = new tracing.trace_model.Sample(
543 event.name, event.ts / 1000,
546 m.samples.push(sample);
551 * Called by the model to join references between objects, after final model
552 * bounds have been computed.
554 joinRefs: function() {
555 this.joinObjectRefs_();
558 createAsyncSlices_: function() {
559 if (this.allAsyncEvents_.length === 0)
562 this.allAsyncEvents_.sort(function(x, y) {
563 var d = x.event.ts - y.event.ts;
566 return x.sequenceNumber - y.sequenceNumber;
569 var legacyEvents = [];
570 // Group nestable async events by ID. Events with the same ID should
571 // belong to the same parent async event.
572 var nestableAsyncEventsByID = {};
573 for (var i = 0; i < this.allAsyncEvents_.length; i++) {
574 var asyncEventState = this.allAsyncEvents_[i];
575 var event = asyncEventState.event;
576 if (event.ph === 'S' || event.ph === 'F' || event.ph === 'T' ||
578 legacyEvents.push(asyncEventState);
581 if (event.name === undefined) {
582 this.model_.importWarning({
583 type: 'async_slice_parse_error',
584 message: 'Nestable async events (ph: b, e, or n) require a ' +
591 if (id === undefined) {
592 this.model_.importWarning({
593 type: 'async_slice_parse_error',
594 message: 'Nestable async events (ph: b, e, or n) require an ' +
599 if (nestableAsyncEventsByID[id] === undefined)
600 nestableAsyncEventsByID[id] = [];
601 nestableAsyncEventsByID[id].push(asyncEventState);
603 // Handle legacy async events.
604 this.createLegacyAsyncSlices_(legacyEvents);
606 // Parse nestable async events into AsyncSlices.
607 for (var id in nestableAsyncEventsByID) {
608 var eventStateEntries = nestableAsyncEventsByID[id];
609 // Stack of enclosing BEGIN events.
610 var parentStack = [];
611 for (var i = 0; i < eventStateEntries.length; ++i) {
612 var eventStateEntry = eventStateEntries[i];
613 // If this is the end of an event, match it to the start.
614 if (eventStateEntry.event.ph === 'e') {
615 // Walk up the parent stack to find the corresponding BEGIN for
617 var parentIndex = -1;
618 for (var k = parentStack.length - 1; k >= 0; --k) {
619 if (parentStack[k].event.name === eventStateEntry.event.name) {
624 if (parentIndex === -1) {
626 eventStateEntry.finished = false;
628 parentStack[parentIndex].end = eventStateEntry;
629 // Pop off all enclosing unmatched BEGINs util parentIndex.
630 while (parentIndex < parentStack.length) {
635 // Inherit the current parent.
636 if (parentStack.length > 0)
637 eventStateEntry.parentEntry = parentStack[parentStack.length - 1];
638 if (eventStateEntry.event.ph === 'b')
639 parentStack.push(eventStateEntry);
641 var topLevelSlices = [];
642 for (var i = 0; i < eventStateEntries.length; ++i) {
643 var eventStateEntry = eventStateEntries[i];
644 // Skip matched END, as its slice will be created when we
645 // encounter its corresponding BEGIN.
646 if (eventStateEntry.event.ph === 'e' &&
647 eventStateEntry.finished === undefined) {
650 var startState = undefined;
651 var endState = undefined;
652 var sliceArgs = undefined;
653 var sliceError = undefined;
654 if (eventStateEntry.event.ph === 'n') {
655 startState = eventStateEntry;
656 endState = eventStateEntry;
657 sliceArgs = eventStateEntry.event.args;
658 } else if (eventStateEntry.event.ph === 'b'){
659 if (eventStateEntry.end === undefined) {
660 // Unmatched BEGIN. End it when last event with this ID ends.
661 eventStateEntry.end =
662 eventStateEntries[eventStateEntries.length - 1];
664 "Slice has no matching END. End time has been adjusted.";
665 this.model_.importWarning({
666 type: 'async_slice_parse_error',
667 message: 'Nestable async BEGIN event at ' +
668 eventStateEntry.event.ts + ' with name=' +
669 eventStateEntry.event.name +
670 ' and id=' + eventStateEntry.event.id + ' was unmatched.'
672 sliceArgs = eventStateEntry.event.args;
674 // Include args for both END and BEGIN for a matched pair.
675 var concatenateArguments = function(args1, args2) {
676 if (args1.params === undefined || args2.params === undefined)
677 return tv.concatenateObjects(args1, args2);
678 // Make an argument object to hold the combined params.
680 args3.params = tv.concatenateObjects(args1.params, args2.params);
681 return tv.concatenateObjects(args1, args2, args3);
683 sliceArgs = concatenateArguments(eventStateEntry.event.args,
684 eventStateEntry.end.event.args);
686 startState = eventStateEntry;
687 endState = eventStateEntry.end;
689 // Unmatched END. Start it at the first event with this ID starts.
691 "Slice has no matching BEGIN. Start time has been adjusted.";
692 this.model_.importWarning({
693 type: 'async_slice_parse_error',
694 message: 'Nestable async END event at ' +
695 eventStateEntry.event.ts + ' with name=' +
696 eventStateEntry.event.name +
697 ' and id=' + eventStateEntry.event.id + ' was unmatched.'
699 startState = eventStateEntries[0];
700 endState = eventStateEntry;
701 sliceArgs = eventStateEntry.event.args;
704 var asyncSliceConstructor =
705 tracing.trace_model.AsyncSlice.getConstructor(
706 eventStateEntry.event.cat,
707 eventStateEntry.event.name);
708 var slice = new asyncSliceConstructor(
709 eventStateEntry.event.cat,
710 eventStateEntry.event.name,
711 tv.ui.getStringColorId(eventStateEntry.event.name),
712 startState.event.ts / 1000,
714 (endState.event.ts - startState.event.ts) / 1000);
716 slice.startThread = startState.thread;
717 slice.endThread = endState.thread;
719 if (sliceError !== undefined)
720 slice.error = sliceError;
721 eventStateEntry.slice = slice;
722 // Add slice to parent slice if there is a parent. Otherwise threat
723 // it as a top level slice.
724 if (eventStateEntry.parentEntry !== undefined &&
725 eventStateEntry.parentEntry.slice !== undefined) {
726 if (eventStateEntry.parentEntry.slice.subSlices === undefined)
727 eventStateEntry.parentEntry.slice.subSlices = [];
728 eventStateEntry.parentEntry.slice.subSlices.push(slice);
730 topLevelSlices.push(slice);
733 for (var si = 0; si < topLevelSlices.length; si++) {
734 topLevelSlices[si].startThread.asyncSliceGroup.push(topLevelSlices[si]);
739 createLegacyAsyncSlices_: function(legacyEvents) {
740 if (legacyEvents.length === 0)
743 legacyEvents.sort(function(x, y) {
744 var d = x.event.ts - y.event.ts;
747 return x.sequenceNumber - y.sequenceNumber;
750 var asyncEventStatesByNameThenID = {};
752 for (var i = 0; i < legacyEvents.length; i++) {
753 var asyncEventState = legacyEvents[i];
755 var event = asyncEventState.event;
756 var name = event.name;
757 if (name === undefined) {
758 this.model_.importWarning({
759 type: 'async_slice_parse_error',
760 message: 'Async events (ph: S, T, p, or F) require a name ' +
767 if (id === undefined) {
768 this.model_.importWarning({
769 type: 'async_slice_parse_error',
770 message: 'Async events (ph: S, T, p, or F) require an id parameter.'
775 // TODO(simonjam): Add a synchronous tick on the appropriate thread.
777 if (event.ph === 'S') {
778 if (asyncEventStatesByNameThenID[name] === undefined)
779 asyncEventStatesByNameThenID[name] = {};
780 if (asyncEventStatesByNameThenID[name][id]) {
781 this.model_.importWarning({
782 type: 'async_slice_parse_error',
783 message: 'At ' + event.ts + ', a slice of the same id ' + id +
788 asyncEventStatesByNameThenID[name][id] = [];
789 asyncEventStatesByNameThenID[name][id].push(asyncEventState);
791 if (asyncEventStatesByNameThenID[name] === undefined) {
792 this.model_.importWarning({
793 type: 'async_slice_parse_error',
794 message: 'At ' + event.ts + ', no slice named ' + name +
799 if (asyncEventStatesByNameThenID[name][id] === undefined) {
800 this.model_.importWarning({
801 type: 'async_slice_parse_error',
802 message: 'At ' + event.ts + ', no slice named ' + name +
803 ' with id=' + id + ' was open.'
807 var events = asyncEventStatesByNameThenID[name][id];
808 events.push(asyncEventState);
810 if (event.ph === 'F') {
811 // Create a slice from start to end.
812 var asyncSliceConstructor =
813 tracing.trace_model.AsyncSlice.getConstructor(
816 var slice = new asyncSliceConstructor(
819 tv.ui.getStringColorId(name),
820 events[0].event.ts / 1000,
821 this.deepCopyIfNeeded_(events[0].event.args),
822 (event.ts - events[0].event.ts) / 1000);
823 slice.startThread = events[0].thread;
824 slice.endThread = asyncEventState.thread;
826 slice.subSlices = [];
828 var stepType = events[1].event.ph;
831 // Create subSlices for each step.
832 for (var j = 1; j < events.length; ++j) {
834 if (events[j].event.ph == 'T' || events[j].event.ph == 'p') {
835 isValid = this.assertStepTypeMatches_(stepType, events[j]);
841 if (stepType == 'T') {
842 targetEvent = events[j - 1];
844 targetEvent = events[j];
847 var subName = events[0].event.name;
848 if (targetEvent.event.ph == 'T' || targetEvent.event.ph == 'p')
849 subName = subName + ':' + targetEvent.event.args.step;
851 var asyncSliceConstructor =
852 tracing.trace_model.AsyncSlice.getConstructor(
855 var subSlice = new asyncSliceConstructor(
858 tv.ui.getStringColorId(subName + j),
859 events[j - 1].event.ts / 1000,
860 tv.concatenateObjects(events[0].event.args,
861 targetEvent.event.args),
862 (events[j].event.ts - events[j - 1].event.ts) / 1000);
863 subSlice.startThread = events[j - 1].thread;
864 subSlice.endThread = events[j].thread;
867 slice.subSlices.push(subSlice);
869 if (events[j].event.ph == 'F' && stepType == 'T') {
870 // The args for the finish event go in the last subSlice.
871 var lastSlice = slice.subSlices[slice.subSlices.length - 1];
872 lastSlice.args = tv.concatenateObjects(lastSlice.args,
878 // Add |slice| to the start-thread's asyncSlices.
879 slice.startThread.asyncSliceGroup.push(slice);
882 delete asyncEventStatesByNameThenID[name][id];
888 assertStepTypeMatches_: function(stepType, event) {
889 if (stepType != event.event.ph) {
890 this.model_.importWarning({
891 type: 'async_slice_parse_error',
892 message: 'At ' + event.event.ts + ', a slice named ' +
893 event.event.name + ' with id=' + event.event.id +
894 ' had both begin and end steps, which is not allowed.'
901 createFlowSlices_: function() {
902 if (this.allFlowEvents_.length === 0)
905 this.allFlowEvents_.sort(function(x, y) {
906 var d = x.event.ts - y.event.ts;
909 return x.sequenceNumber - y.sequenceNumber;
912 var flowIdToEvent = {};
913 for (var i = 0; i < this.allFlowEvents_.length; ++i) {
914 var data = this.allFlowEvents_[i];
915 var event = data.event;
916 var thread = data.thread;
918 if (event.name === undefined) {
919 this.model_.importWarning({
920 type: 'flow_slice_parse_error',
921 message: 'Flow events (ph: s, t or f) require a name parameter.'
926 if (event.id === undefined) {
927 this.model_.importWarning({
928 type: 'flow_slice_parse_error',
929 message: 'Flow events (ph: s, t or f) require an id parameter.'
934 var slice = new tracing.trace_model.FlowEvent(
938 tv.ui.getStringColorId(event.name),
940 this.deepCopyIfNeeded_(event.args));
941 thread.sliceGroup.pushSlice(slice);
943 if (event.ph === 's') {
944 if (flowIdToEvent[event.id] !== undefined) {
945 this.model_.importWarning({
946 type: 'flow_slice_start_error',
947 message: 'event id ' + event.id + ' already seen when ' +
948 'encountering start of flow event.'});
950 flowIdToEvent[event.id] = slice;
952 } else if (event.ph === 't' || event.ph === 'f') {
953 var flowPosition = flowIdToEvent[event.id];
954 if (flowPosition === undefined) {
955 this.model_.importWarning({
956 type: 'flow_slice_ordering_error',
957 message: 'Found flow phase ' + event.ph + ' for id: ' + event.id +
958 ' but no flow start found.'
962 this.model_.flowEvents.push([flowPosition, slice]);
965 flowPosition.nextFlowEvent = slice;
967 slice.previousFlowEvent = flowPosition;
969 if (event.ph === 'f') {
970 flowIdToEvent[event.id] = undefined;
972 // Make this slice the next start event in this flow.
973 flowIdToEvent[event.id] = slice;
980 * This function creates objects described via the N, D, and O phase
983 createExplicitObjects_: function() {
984 if (this.allObjectEvents_.length == 0)
987 function processEvent(objectEventState) {
988 var event = objectEventState.event;
989 var thread = objectEventState.thread;
990 if (event.name === undefined) {
991 this.model_.importWarning({
992 type: 'object_parse_error',
993 message: 'While processing ' + JSON.stringify(event) + ': ' +
994 'Object events require an name parameter.'
998 if (event.id === undefined) {
999 this.model_.importWarning({
1000 type: 'object_parse_error',
1001 message: 'While processing ' + JSON.stringify(event) + ': ' +
1002 'Object events require an id parameter.'
1005 var process = thread.parent;
1006 var ts = event.ts / 1000;
1008 if (event.ph == 'N') {
1010 instance = process.objects.idWasCreated(
1011 event.id, event.cat, event.name, ts);
1013 this.model_.importWarning({
1014 type: 'object_parse_error',
1015 message: 'While processing create of ' +
1016 event.id + ' at ts=' + ts + ': ' + e
1020 } else if (event.ph == 'O') {
1021 if (event.args.snapshot === undefined) {
1022 this.model_.importWarning({
1023 type: 'object_parse_error',
1024 message: 'While processing ' + event.id + ' at ts=' + ts + ': ' +
1025 'Snapshots must have args: {snapshot: ...}'
1031 var args = this.deepCopyIfNeeded_(event.args.snapshot);
1041 if (args.base_type) {
1042 baseTypename = args.base_type;
1043 delete args.base_type;
1045 baseTypename = undefined;
1047 snapshot = process.objects.addSnapshot(
1048 event.id, cat, event.name, ts,
1049 args, baseTypename);
1050 snapshot.snapshottedOnThread = thread;
1052 this.model_.importWarning({
1053 type: 'object_parse_error',
1054 message: 'While processing snapshot of ' +
1055 event.id + ' at ts=' + ts + ': ' + e
1059 instance = snapshot.objectInstance;
1060 } else if (event.ph == 'D') {
1062 instance = process.objects.idWasDeleted(
1063 event.id, event.cat, event.name, ts);
1065 this.model_.importWarning({
1066 type: 'object_parse_error',
1067 message: 'While processing delete of ' +
1068 event.id + ' at ts=' + ts + ': ' + e
1075 instance.colorId = tv.ui.getStringColorId(instance.typeName);
1078 this.allObjectEvents_.sort(function(x, y) {
1079 var d = x.event.ts - y.event.ts;
1082 return x.sequenceNumber - y.sequenceNumber;
1085 var allObjectEvents = this.allObjectEvents_;
1086 for (var i = 0; i < allObjectEvents.length; i++) {
1087 var objectEventState = allObjectEvents[i];
1089 processEvent.call(this, objectEventState);
1091 this.model_.importWarning({
1092 type: 'object_parse_error',
1099 createImplicitObjects_: function() {
1100 tv.iterItems(this.model_.processes, function(pid, process) {
1101 this.createImplicitObjectsForProcess_(process);
1105 // Here, we collect all the snapshots that internally contain a
1106 // Javascript-level object inside their args list that has an "id" field,
1107 // and turn that into a snapshot of the instance referred to by id.
1108 createImplicitObjectsForProcess_: function(process) {
1110 function processField(referencingObject,
1111 referencingObjectFieldName,
1112 referencingObjectFieldValue,
1113 containingSnapshot) {
1114 if (!referencingObjectFieldValue)
1117 if (referencingObjectFieldValue instanceof
1118 tracing.trace_model.ObjectSnapshot)
1120 if (referencingObjectFieldValue.id === undefined)
1123 var implicitSnapshot = referencingObjectFieldValue;
1125 var rawId = implicitSnapshot.id;
1126 var m = /(.+)\/(.+)/.exec(rawId);
1128 throw new Error('Implicit snapshots must have names.');
1129 delete implicitSnapshot.id;
1135 if (implicitSnapshot.cat !== undefined)
1136 cat = implicitSnapshot.cat;
1138 cat = containingSnapshot.objectInstance.category;
1141 if (implicitSnapshot.base_type)
1142 baseTypename = implicitSnapshot.base_type;
1144 baseTypename = undefined;
1147 res = process.objects.addSnapshot(
1149 name, containingSnapshot.ts,
1150 implicitSnapshot, baseTypename);
1152 this.model_.importWarning({
1153 type: 'object_snapshot_parse_error',
1154 message: 'While processing implicit snapshot of ' +
1155 rawId + ' at ts=' + containingSnapshot.ts + ': ' + e
1159 res.objectInstance.hasImplicitSnapshots = true;
1160 res.containingSnapshot = containingSnapshot;
1161 res.snapshottedOnThread = containingSnapshot.snapshottedOnThread;
1162 referencingObject[referencingObjectFieldName] = res;
1163 if (!(res instanceof tracing.trace_model.ObjectSnapshot))
1164 throw new Error('Created object must be instanceof snapshot');
1169 * Iterates over the fields in the object, calling func for every
1170 * field/value found.
1172 * @return {object} If the function does not want the field's value to be
1173 * iterated, return null. If iteration of the field value is desired, then
1174 * return either undefined (if the field value did not change) or the new
1175 * field value if it was changed.
1177 function iterObject(object, func, containingSnapshot, thisArg) {
1178 if (!(object instanceof Object))
1181 if (object instanceof Array) {
1182 for (var i = 0; i < object.length; i++) {
1183 var res = func.call(thisArg, object, i, object[i],
1184 containingSnapshot);
1188 iterObject(res, func, containingSnapshot, thisArg);
1190 iterObject(object[i], func, containingSnapshot, thisArg);
1195 for (var key in object) {
1196 var res = func.call(thisArg, object, key, object[key],
1197 containingSnapshot);
1201 iterObject(res, func, containingSnapshot, thisArg);
1203 iterObject(object[key], func, containingSnapshot, thisArg);
1207 // TODO(nduca): We may need to iterate the instances in sorted order by
1209 process.objects.iterObjectInstances(function(instance) {
1210 instance.snapshots.forEach(function(snapshot) {
1211 if (snapshot.args.id !== undefined)
1212 throw new Error('args cannot have an id field inside it');
1213 iterObject(snapshot.args, processField, snapshot, this);
1218 joinObjectRefs_: function() {
1219 tv.iterItems(this.model_.processes, function(pid, process) {
1220 this.joinObjectRefsForProcess_(process);
1224 joinObjectRefsForProcess_: function(process) {
1225 // Iterate the world, looking for id_refs
1226 var patchupsToApply = [];
1227 tv.iterItems(process.threads, function(tid, thread) {
1228 thread.asyncSliceGroup.slices.forEach(function(item) {
1229 this.searchItemForIDRefs_(
1230 patchupsToApply, process.objects, 'start', item);
1232 thread.sliceGroup.slices.forEach(function(item) {
1233 this.searchItemForIDRefs_(
1234 patchupsToApply, process.objects, 'start', item);
1237 process.objects.iterObjectInstances(function(instance) {
1238 instance.snapshots.forEach(function(item) {
1239 this.searchItemForIDRefs_(
1240 patchupsToApply, process.objects, 'ts', item);
1244 // Change all the fields pointing at id_refs to their real values.
1245 patchupsToApply.forEach(function(patchup) {
1246 patchup.object[patchup.field] = patchup.value;
1250 searchItemForIDRefs_: function(patchupsToApply, objectCollection,
1251 itemTimestampField, item) {
1253 throw new Error('item is missing its args');
1255 function handleField(object, fieldName, fieldValue) {
1256 if (!fieldValue || (!fieldValue.id_ref && !fieldValue.idRef))
1259 var id = fieldValue.id_ref || fieldValue.idRef;
1260 var ts = item[itemTimestampField];
1261 var snapshot = objectCollection.getSnapshotAt(id, ts);
1265 // We have to delay the actual change to the new value until after all
1266 // refs have been located. Otherwise, we could end up recursing in
1267 // ways we definitely didn't intend.
1268 patchupsToApply.push({object: object,
1272 function iterObjectFieldsRecursively(object) {
1273 if (!(object instanceof Object))
1276 if ((object instanceof tracing.trace_model.ObjectSnapshot) ||
1277 (object instanceof Float32Array) ||
1278 (object instanceof tv.Quad))
1281 if (object instanceof Array) {
1282 for (var i = 0; i < object.length; i++) {
1283 handleField(object, i, object[i]);
1284 iterObjectFieldsRecursively(object[i]);
1289 for (var key in object) {
1290 var value = object[key];
1291 handleField(object, key, value);
1292 iterObjectFieldsRecursively(value);
1296 iterObjectFieldsRecursively(item.args);
1300 tracing.TraceModel.registerImporter(TraceEventImporter);
1303 TraceEventImporter: TraceEventImporter