blob: 662eaf7119d82533cc800ab0ee5eacf2bf8f3a27 [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2012 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel="import" href="/tracing/base/color_scheme.html">
<link rel="import" href="/tracing/base/quad.html">
<link rel="import" href="/tracing/base/range.html">
<link rel="import" href="/tracing/base/units/units.html">
<link rel="import" href="/tracing/base/utils.html">
<link rel="import" href="/tracing/extras/importer/trace_code_entry.html">
<link rel="import" href="/tracing/extras/importer/trace_code_map.html">
<link rel="import" href="/tracing/extras/importer/v8/codemap.html">
<link rel="import" href="/tracing/importer/importer.html">
<link rel="import" href="/tracing/model/attribute.html">
<link rel="import" href="/tracing/model/comment_box_annotation.html">
<link rel="import" href="/tracing/model/counter_series.html">
<link rel="import" href="/tracing/model/flow_event.html">
<link rel="import" href="/tracing/model/global_memory_dump.html">
<link rel="import" href="/tracing/model/heap_dump.html">
<link rel="import" href="/tracing/model/instant_event.html">
<link rel="import" href="/tracing/model/memory_allocator_dump.html">
<link rel="import" href="/tracing/model/model.html">
<link rel="import" href="/tracing/model/process_memory_dump.html">
<link rel="import" href="/tracing/model/rect_annotation.html">
<link rel="import" href="/tracing/model/slice_group.html">
<link rel="import" href="/tracing/model/x_marker_annotation.html">
<script>
'use strict';
/**
* @fileoverview TraceEventImporter imports TraceEvent-formatted data
* into the provided model.
*/
tr.exportTo('tr.e.importer', function() {
var deepCopy = tr.b.deepCopy;
var ColorScheme = tr.b.ColorScheme;
function getEventColor(event, opt_customName) {
if (event.cname)
return ColorScheme.getColorIdForReservedName(event.cname);
else if (opt_customName || event.name) {
return ColorScheme.getColorIdForGeneralPurposeString(
opt_customName || event.name);
}
}
var timestampFromUs = tr.b.u.Units.timestampFromUs;
var maybeTimestampFromUs = tr.b.u.Units.maybeTimestampFromUs;
var PRODUCER = 'producer';
var CONSUMER = 'consumer';
var STEP = 'step';
var MEMORY_DUMP_LEVELS_OF_DETAIL = [undefined, 'light', 'detailed'];
var GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX = 'global/';
function TraceEventImporter(model, eventData) {
this.importPriority = 1;
this.model_ = model;
this.events_ = undefined;
this.sampleEvents_ = undefined;
this.stackFrameEvents_ = undefined;
this.systemTraceEvents_ = undefined;
this.battorData_ = undefined;
this.eventsWereFromString_ = false;
this.softwareMeasuredCpuCount_ = undefined;
this.allAsyncEvents_ = [];
this.allFlowEvents_ = [];
this.allObjectEvents_ = [];
this.traceEventSampleStackFramesByName_ = {};
this.v8ProcessCodeMaps_ = {};
this.v8ProcessRootStackFrame_ = {};
// Dump ID -> PID -> [process memory dump events].
this.allMemoryDumpEvents_ = {};
if (typeof(eventData) === 'string' || eventData instanceof String) {
eventData = eventData.trim();
// If the event data begins with a [, then we know it should end with a ].
// The reason we check for this is because some tracing implementations
// cannot guarantee that a ']' gets written to the trace file. So, we are
// forgiving and if this is obviously the case, we fix it up before
// throwing the string at JSON.parse.
if (eventData[0] === '[') {
eventData = eventData.replace(/\s*,\s*$/, '');
if (eventData[eventData.length - 1] !== ']')
eventData = eventData + ']';
}
this.events_ = JSON.parse(eventData);
this.eventsWereFromString_ = true;
} else {
this.events_ = eventData;
}
this.traceAnnotations_ = this.events_.traceAnnotations;
// Some trace_event implementations put the actual trace events
// inside a container. E.g { ... , traceEvents: [ ] }
// If we see that, just pull out the trace events.
if (this.events_.traceEvents) {
var container = this.events_;
this.events_ = this.events_.traceEvents;
// Some trace_event implementations put ftrace_importer traces as a
// huge string inside container.systemTraceEvents. If we see that, pull it
// out. It will be picked up by extractSubtraces later on.
this.systemTraceEvents_ = container.systemTraceEvents;
// Some trace_event implementations put battor power traces as a
// huge string inside container.battorLogAsString. If we see that, pull
// it out. It will be picked up by extractSubtraces later on.
this.battorData_ = container.battorLogAsString;
// Sampling data.
this.sampleEvents_ = container.samples;
this.stackFrameEvents_ = container.stackFrames;
// Some implementations specify displayTimeUnit
if (container.displayTimeUnit) {
var unitName = container.displayTimeUnit;
var unit = tr.b.u.TimeDisplayModes[unitName];
if (unit === undefined) {
throw new Error('Unit ' + unitName + ' is not supported.');
}
this.model_.intrinsicTimeUnit = unit;
}
var knownFieldNames = {
battorLogAsString: true,
samples: true,
stackFrames: true,
systemTraceEvents: true,
traceAnnotations: true,
traceEvents: true
};
// Any other fields in the container should be treated as metadata.
for (var fieldName in container) {
if (fieldName in knownFieldNames)
continue;
this.model_.metadata.push({name: fieldName,
value: container[fieldName]});
if (fieldName === 'metadata' && container[fieldName]['highres-ticks']) {
this.model_.isTimeHighResolution =
container[fieldName]['highres-ticks'];
}
}
}
}
/**
* @return {boolean} Whether obj is a TraceEvent array.
*/
TraceEventImporter.canImport = function(eventData) {
// May be encoded JSON. But we dont want to parse it fully yet.
// Use a simple heuristic:
// - eventData that starts with [ are probably trace_event
// - eventData that starts with { are probably trace_event
// May be encoded JSON. Treat files that start with { as importable by us.
if (typeof(eventData) === 'string' || eventData instanceof String) {
eventData = eventData.trim();
return eventData[0] === '{' || eventData[0] === '[';
}
// Might just be an array of events
if (eventData instanceof Array && eventData.length && eventData[0].ph)
return true;
// Might be an object with a traceEvents field in it.
if (eventData.traceEvents) {
if (eventData.traceEvents instanceof Array) {
if (eventData.traceEvents.length && eventData.traceEvents[0].ph)
return true;
if (eventData.samples.length && eventData.stackFrames !== undefined)
return true;
}
}
return false;
};
TraceEventImporter.prototype = {
__proto__: tr.importer.Importer.prototype,
extractSubtraces: function() {
var systemEventsTmp = this.systemTraceEvents_;
var battorDataTmp = this.battorData_;
this.systemTraceEvents_ = undefined;
this.battorData_ = undefined;
var subTraces = systemEventsTmp ? [systemEventsTmp] : [];
if (battorDataTmp)
subTraces.push(battorDataTmp);
return subTraces;
},
/**
* Deep copying is only needed if the trace was given to us as events.
*/
deepCopyIfNeeded_: function(obj) {
if (obj === undefined)
obj = {};
if (this.eventsWereFromString_)
return obj;
return deepCopy(obj);
},
/**
* Always perform deep copying.
*/
deepCopyAlways_: function(obj) {
if (obj === undefined)
obj = {};
return deepCopy(obj);
},
/**
* Helper to process an async event.
*/
processAsyncEvent: function(event) {
var thread = this.model_.getOrCreateProcess(event.pid).
getOrCreateThread(event.tid);
this.allAsyncEvents_.push({
sequenceNumber: this.allAsyncEvents_.length,
event: event,
thread: thread
});
},
/**
* Helper to process a flow event.
*/
processFlowEvent: function(event, opt_slice) {
var thread = this.model_.getOrCreateProcess(event.pid).
getOrCreateThread(event.tid);
this.allFlowEvents_.push({
refGuid: tr.b.GUID.getLastGuid(),
sequenceNumber: this.allFlowEvents_.length,
event: event,
slice: opt_slice, // slice for events that have flow info
thread: thread
});
},
/**
* Helper that creates and adds samples to a Counter object based on
* 'C' phase events.
*/
processCounterEvent: function(event) {
var ctr_name;
if (event.id !== undefined)
ctr_name = event.name + '[' + event.id + ']';
else
ctr_name = event.name;
var ctr = this.model_.getOrCreateProcess(event.pid)
.getOrCreateCounter(event.cat, ctr_name);
var reservedColorId = event.cname ? getEventColor(event) : undefined;
// Initialize the counter's series fields if needed.
if (ctr.numSeries === 0) {
for (var seriesName in event.args) {
var colorId = reservedColorId ||
getEventColor(event, ctr.name + '.' + seriesName);
ctr.addSeries(new tr.model.CounterSeries(seriesName, colorId));
}
if (ctr.numSeries === 0) {
this.model_.importWarning({
type: 'counter_parse_error',
message: 'Expected counter ' + event.name +
' to have at least one argument to use as a value.'
});
// Drop the counter.
delete ctr.parent.counters[ctr.name];
return;
}
}
var ts = timestampFromUs(event.ts);
ctr.series.forEach(function(series) {
var val = event.args[series.name] ? event.args[series.name] : 0;
series.addCounterSample(ts, val);
});
},
processObjectEvent: function(event) {
var thread = this.model_.getOrCreateProcess(event.pid).
getOrCreateThread(event.tid);
this.allObjectEvents_.push({
sequenceNumber: this.allObjectEvents_.length,
event: event,
thread: thread});
},
processDurationEvent: function(event) {
var thread = this.model_.getOrCreateProcess(event.pid)
.getOrCreateThread(event.tid);
var ts = timestampFromUs(event.ts);
if (!thread.sliceGroup.isTimestampValidForBeginOrEnd(ts)) {
this.model_.importWarning({
type: 'duration_parse_error',
message: 'Timestamps are moving backward.'
});
return;
}
if (event.ph === 'B') {
var slice = thread.sliceGroup.beginSlice(
event.cat, event.name, timestampFromUs(event.ts),
this.deepCopyIfNeeded_(event.args),
timestampFromUs(event.tts), event.argsStripped,
getEventColor(event));
slice.startStackFrame = this.getStackFrameForEvent_(event);
} else if (event.ph === 'I' || event.ph === 'i' || event.ph === 'R') {
if (event.s !== undefined && event.s !== 't')
throw new Error('This should never happen');
thread.sliceGroup.beginSlice(event.cat, event.name,
timestampFromUs(event.ts),
this.deepCopyIfNeeded_(event.args),
timestampFromUs(event.tts),
event.argsStripped,
getEventColor(event));
var slice = thread.sliceGroup.endSlice(timestampFromUs(event.ts),
timestampFromUs(event.tts));
slice.startStackFrame = this.getStackFrameForEvent_(event);
slice.endStackFrame = undefined;
} else {
if (!thread.sliceGroup.openSliceCount) {
this.model_.importWarning({
type: 'duration_parse_error',
message: 'E phase event without a matching B phase event.'
});
return;
}
var slice = thread.sliceGroup.endSlice(timestampFromUs(event.ts),
timestampFromUs(event.tts),
getEventColor(event));
if (event.name && slice.title != event.name) {
this.model_.importWarning({
type: 'title_match_error',
message: 'Titles do not match. Title is ' +
slice.title + ' in openSlice, and is ' +
event.name + ' in endSlice'
});
}
slice.endStackFrame = this.getStackFrameForEvent_(event);
this.mergeArgsInto_(slice.args, event.args, slice.title);
}
},
mergeArgsInto_: function(dstArgs, srcArgs, eventName) {
for (var arg in srcArgs) {
if (dstArgs[arg] !== undefined) {
this.model_.importWarning({
type: 'arg_merge_error',
message: 'Different phases of ' + eventName +
' provided values for argument ' + arg + '.' +
' The last provided value will be used.'
});
}
dstArgs[arg] = this.deepCopyIfNeeded_(srcArgs[arg]);
}
},
processCompleteEvent: function(event) {
// Preventing the overhead slices from making it into the model. This
// only applies to legacy traces, as the overhead traces have been
// removed from the chromium code.
if (event.cat !== undefined &&
event.cat.indexOf('trace_event_overhead') > -1)
return undefined;
var thread = this.model_.getOrCreateProcess(event.pid)
.getOrCreateThread(event.tid);
if (event.flow_out) {
if (event.flow_in)
event.flowPhase = STEP;
else
event.flowPhase = PRODUCER;
} else if (event.flow_in) {
event.flowPhase = CONSUMER;
}
var slice = thread.sliceGroup.pushCompleteSlice(event.cat, event.name,
timestampFromUs(event.ts),
maybeTimestampFromUs(event.dur),
maybeTimestampFromUs(event.tts),
maybeTimestampFromUs(event.tdur),
this.deepCopyIfNeeded_(event.args),
event.argsStripped,
getEventColor(event),
event.bind_id);
slice.startStackFrame = this.getStackFrameForEvent_(event);
slice.endStackFrame = this.getStackFrameForEvent_(event, true);
return slice;
},
processMetadataEvent: function(event) {
// The metadata events aren't useful without args.
if (event.argsStripped)
return;
if (event.name === 'process_name') {
var process = this.model_.getOrCreateProcess(event.pid);
process.name = event.args.name;
} else if (event.name === 'process_labels') {
var process = this.model_.getOrCreateProcess(event.pid);
var labels = event.args.labels.split(',');
for (var i = 0; i < labels.length; i++)
process.addLabelIfNeeded(labels[i]);
} else if (event.name === 'process_sort_index') {
var process = this.model_.getOrCreateProcess(event.pid);
process.sortIndex = event.args.sort_index;
} else if (event.name === 'thread_name') {
var thread = this.model_.getOrCreateProcess(event.pid).
getOrCreateThread(event.tid);
thread.name = event.args.name;
} else if (event.name === 'thread_sort_index') {
var thread = this.model_.getOrCreateProcess(event.pid).
getOrCreateThread(event.tid);
thread.sortIndex = event.args.sort_index;
} else if (event.name === 'num_cpus') {
var n = event.args.number;
// Not all render processes agree on the cpu count in trace_event. Some
// processes will report 1, while others will report the actual cpu
// count. To deal with this, take the max of what is reported.
if (this.softwareMeasuredCpuCount_ !== undefined)
n = Math.max(n, this.softwareMeasuredCpuCount_);
this.softwareMeasuredCpuCount_ = n;
} else if (event.name === 'stackFrames') {
var stackFrames = event.args.stackFrames;
if (stackFrames === undefined) {
this.model_.importWarning({
type: 'metadata_parse_error',
message: 'No stack frames found in a \'' + event.name +
'\' metadata event'
});
} else {
this.importStackFrames_(
stackFrames, 'p' + event.pid + ':', true /* addRootFrame */);
}
} else {
this.model_.importWarning({
type: 'metadata_parse_error',
message: 'Unrecognized metadata name: ' + event.name
});
}
},
processJitCodeEvent: function(event) {
if (this.v8ProcessCodeMaps_[event.pid] === undefined)
this.v8ProcessCodeMaps_[event.pid] = new tr.e.importer.TraceCodeMap();
var map = this.v8ProcessCodeMaps_[event.pid];
var data = event.args.data;
if (event.name === 'JitCodeMoved')
map.moveEntry(data.code_start, data.new_code_start, data.code_len);
else
map.addEntry(data.code_start, data.code_len, data.name, data.script_id);
},
processInstantEvent: function(event) {
// V8 JIT events are logged as phase 'I' so we need to separate them out
// and handle specially.
//
// TODO(dsinclair): There are _a lot_ of JitCode events so I'm skipping
// the display for now. Can revisit later if we want to show them.
if (event.name === 'JitCodeAdded' || event.name === 'JitCodeMoved') {
this.processJitCodeEvent(event);
return;
}
// Thread-level instant events are treated as zero-duration slices.
if (event.s === 't' || event.s === undefined) {
this.processDurationEvent(event);
return;
}
var constructor;
switch (event.s) {
case 'g':
constructor = tr.model.GlobalInstantEvent;
break;
case 'p':
constructor = tr.model.ProcessInstantEvent;
break;
default:
this.model_.importWarning({
type: 'instant_parse_error',
message: 'I phase event with unknown "s" field value.'
});
return;
}
var instantEvent = new constructor(event.cat, event.name,
getEventColor(event), timestampFromUs(event.ts),
this.deepCopyIfNeeded_(event.args));
switch (instantEvent.type) {
case tr.model.InstantEventType.GLOBAL:
this.model_.pushInstantEvent(instantEvent);
break;
case tr.model.InstantEventType.PROCESS:
var process = this.model_.getOrCreateProcess(event.pid);
process.pushInstantEvent(instantEvent);
break;
default:
throw new Error('Unknown instant event type: ' + event.s);
}
},
processV8Sample: function(event) {
var data = event.args.data;
// As-per DevTools, the backend sometimes creates bogus samples. Skip it.
if (data.vm_state === 'js' && !data.stack.length)
return;
var rootStackFrame = this.v8ProcessRootStackFrame_[event.pid];
if (!rootStackFrame) {
rootStackFrame = new tr.model.StackFrame(
undefined /* parent */, 'v8-root-stack-frame' /* id */,
'v8-root-stack-frame' /* title */, 0 /* colorId */);
this.v8ProcessRootStackFrame_[event.pid] = rootStackFrame;
}
function findChildWithEntryID(stackFrame, entryID) {
return tr.b.findFirstInArray(stackFrame.children, function(child) {
return child.entryID === entryID;
});
}
var model = this.model_;
function addStackFrame(lastStackFrame, entry) {
var childFrame = findChildWithEntryID(lastStackFrame, entry.id);
if (childFrame)
return childFrame;
var frame = new tr.model.StackFrame(
lastStackFrame, tr.b.GUID.allocate(), entry.name,
ColorScheme.getColorIdForGeneralPurposeString(entry.name),
entry.sourceInfo);
frame.entryID = entry.id;
model.addStackFrame(frame);
return frame;
}
var lastStackFrame = rootStackFrame;
// There are several types of v8 sample events, gc, native, compiler, etc.
// Some of these types have stacks and some don't, we handle those two
// cases differently. For types that don't have any stack frames attached
// we synthesize one based on the type of thing that's happening so when
// we view all the samples we'll see something like 'external' or 'gc'
// as a fraction of the time spent.
if (data.stack.length > 0 && this.v8ProcessCodeMaps_[event.pid]) {
var map = this.v8ProcessCodeMaps_[event.pid];
// Stacks have the leaf node first, flip them around so the root
// comes first.
data.stack.reverse();
for (var i = 0; i < data.stack.length; i++) {
var entry = map.lookupEntry(data.stack[i]);
if (entry === undefined) {
entry = {
id: 'unknown',
name: 'unknown',
sourceInfo: undefined
};
}
lastStackFrame = addStackFrame(lastStackFrame, entry);
}
} else {
var entry = {
id: data.vm_state,
name: data.vm_state,
sourceInfo: undefined
};
lastStackFrame = addStackFrame(lastStackFrame, entry);
}
var thread = this.model_.getOrCreateProcess(event.pid)
.getOrCreateThread(event.tid);
var sample = new tr.model.Sample(
undefined /* cpu */, thread, 'V8 Sample',
timestampFromUs(event.ts), lastStackFrame, 1 /* weight */,
this.deepCopyIfNeeded_(event.args));
this.model_.samples.push(sample);
},
processTraceSampleEvent: function(event) {
if (event.name === 'V8Sample') {
this.processV8Sample(event);
return;
}
var stackFrame = this.getStackFrameForEvent_(event);
if (stackFrame === undefined) {
stackFrame = this.traceEventSampleStackFramesByName_[
event.name];
}
if (stackFrame === undefined) {
var id = 'te-' + tr.b.GUID.allocate();
stackFrame = new tr.model.StackFrame(
undefined, id, event.name,
ColorScheme.getColorIdForGeneralPurposeString(event.name));
this.model_.addStackFrame(stackFrame);
this.traceEventSampleStackFramesByName_[event.name] = stackFrame;
}
var thread = this.model_.getOrCreateProcess(event.pid)
.getOrCreateThread(event.tid);
var sample = new tr.model.Sample(
undefined, thread, 'Trace Event Sample',
timestampFromUs(event.ts), stackFrame, 1,
this.deepCopyIfNeeded_(event.args));
this.model_.samples.push(sample);
},
processMemoryDumpEvent: function(event) {
if (event.ph !== 'v')
throw new Error('Invalid memory dump event phase "' + event.ph + '".');
var dumpId = event.id;
if (dumpId === undefined) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Memory dump event (phase \'' + event.ph +
'\') without a dump ID.'
});
return;
}
var pid = event.pid;
if (pid === undefined) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Memory dump event (phase\'' + event.ph + '\', dump ID \'' +
dumpId + '\') without a PID.'
});
return;
}
// Dump ID -> PID -> [process memory dump events].
var allEvents = this.allMemoryDumpEvents_;
// PID -> [process memory dump events].
var dumpIdEvents = allEvents[dumpId];
if (dumpIdEvents === undefined)
allEvents[dumpId] = dumpIdEvents = {};
// [process memory dump events].
var processEvents = dumpIdEvents[pid];
if (processEvents === undefined)
dumpIdEvents[pid] = processEvents = [];
processEvents.push(event);
},
/**
* Walks through the events_ list and outputs the structures discovered to
* model_.
*/
importEvents: function() {
var csr = new tr.ClockSyncRecord('ftrace_importer', 0, {});
this.model_.clockSyncRecords.push(csr);
if (this.stackFrameEvents_) {
this.importStackFrames_(
this.stackFrameEvents_, 'g', false /* addRootFrame */);
}
if (this.traceAnnotations_)
this.importAnnotations_();
var events = this.events_;
for (var eI = 0; eI < events.length; eI++) {
var event = events[eI];
if (event.args === '__stripped__') {
event.argsStripped = true;
event.args = undefined;
}
if (event.ph === 'B' || event.ph === 'E') {
this.processDurationEvent(event);
} else if (event.ph === 'X') {
var slice = this.processCompleteEvent(event);
// TODO(yuhaoz): If Chrome supports creating other events with flow,
// we will need to call processFlowEvent for them also.
// https://github.com/catapult-project/catapult/issues/1259
if (slice !== undefined && event.bind_id !== undefined)
this.processFlowEvent(event, slice);
} else if (event.ph === 'b' || event.ph === 'e' || event.ph === 'n' ||
event.ph === 'S' || event.ph === 'F' || event.ph === 'T' ||
event.ph === 'p') {
this.processAsyncEvent(event);
// Note, I is historic. The instant event marker got changed, but we
// want to support loading old trace files so we have both I and i.
} else if (event.ph === 'I' || event.ph === 'i' || event.ph === 'R') {
this.processInstantEvent(event);
} else if (event.ph === 'P') {
this.processTraceSampleEvent(event);
} else if (event.ph === 'C') {
this.processCounterEvent(event);
} else if (event.ph === 'M') {
this.processMetadataEvent(event);
} else if (event.ph === 'N' || event.ph === 'D' || event.ph === 'O') {
this.processObjectEvent(event);
} else if (event.ph === 's' || event.ph === 't' || event.ph === 'f') {
this.processFlowEvent(event);
} else if (event.ph === 'v') {
this.processMemoryDumpEvent(event);
} else {
this.model_.importWarning({
type: 'parse_error',
message: 'Unrecognized event phase: ' +
event.ph + ' (' + event.name + ')'
});
}
}
// Remove all the root stack frame children as they should
// already be added.
tr.b.iterItems(this.v8ProcessRootStackFrame_, function(name, frame) {
frame.removeAllChildren();
});
},
importStackFrames_: function(rawStackFrames, idPrefix, addRootFrame) {
var model = this.model_;
var rootStackFrame;
if (addRootFrame) {
// In certain cases (heap dumps), we need to be able to distinguish
// between an empty and an undefined stack trace. To this end, we add
// an auxiliary root stack frame which is common to all stack frames
// in a process. An empty stack trace is then represented by setting
// the root stack frame as the leaf stack frame (of the relevant model
// object with an associated empty stack trace, e.g. HeapEntry in the
// case of heap dumps).
rootStackFrame = new tr.model.StackFrame(
undefined /* parentFrame */, idPrefix, undefined /* title */,
undefined /* colorId */);
model.addStackFrame(rootStackFrame);
} else {
rootStackFrame = undefined;
}
for (var id in rawStackFrames) {
var rawStackFrame = rawStackFrames[id];
var fullId = idPrefix + id;
var textForColor = rawStackFrame.category ?
rawStackFrame.category : rawStackFrame.name;
var stackFrame = new tr.model.StackFrame(
undefined /* parentFrame */, fullId, rawStackFrame.name,
ColorScheme.getColorIdForGeneralPurposeString(textForColor));
model.addStackFrame(stackFrame);
}
for (var id in rawStackFrames) {
var fullId = idPrefix + id;
var stackFrame = model.stackFrames[fullId];
if (stackFrame === undefined)
throw new Error('Internal error');
var rawStackFrame = rawStackFrames[id];
var parentId = rawStackFrame.parent;
var parentStackFrame;
if (parentId === undefined) {
parentStackFrame = rootStackFrame;
} else {
var parentFullId = idPrefix + parentId;
parentStackFrame = model.stackFrames[parentFullId];
if (parentStackFrame === undefined) {
this.model_.importWarning({
type: 'metadata_parse_error',
message: 'Missing parent frame with ID ' + parentFullId +
' for stack frame \'' + stackFrame.name + '\' (ID ' + fullId +
').'
});
parentStackFrame = rootStackFrame;
}
}
stackFrame.parentFrame = parentStackFrame;
}
},
importAnnotations_: function() {
for (var id in this.traceAnnotations_) {
var annotation = tr.model.Annotation.fromDictIfPossible(
this.traceAnnotations_[id]);
if (!annotation) {
this.model_.importWarning({
type: 'annotation_warning',
message: 'Unrecognized traceAnnotation typeName \"' +
this.traceAnnotations_[id].typeName + '\"'
});
continue;
}
this.model_.addAnnotation(annotation);
}
},
/**
* Called by the Model after all other importers have imported their
* events.
*/
finalizeImport: function() {
if (this.softwareMeasuredCpuCount_ !== undefined) {
this.model_.kernel.softwareMeasuredCpuCount =
this.softwareMeasuredCpuCount_;
}
this.createAsyncSlices_();
this.createFlowSlices_();
this.createExplicitObjects_();
this.createImplicitObjects_();
this.createMemoryDumps_();
},
/* Events can have one or more stack frames associated with them, but
* that frame might be encoded either as a stack trace of program counters,
* or as a direct stack frame reference. This handles either case and
* if found, returns the stackframe.
*/
getStackFrameForEvent_: function(event, opt_lookForEndEvent) {
var sf;
var stack;
if (opt_lookForEndEvent) {
sf = event.esf;
stack = event.estack;
} else {
sf = event.sf;
stack = event.stack;
}
if (stack !== undefined && sf !== undefined) {
this.model_.importWarning({
type: 'stack_frame_and_stack_error',
message: 'Event at ' + event.ts +
' cannot have both a stack and a stackframe.'
});
return undefined;
}
if (stack !== undefined)
return this.model_.resolveStackToStackFrame_(event.pid, stack);
if (sf === undefined)
return undefined;
var stackFrame = this.model_.stackFrames['g' + sf];
if (stackFrame === undefined) {
this.model_.importWarning({
type: 'sample_import_error',
message: 'No frame for ' + sf
});
return;
}
return stackFrame;
},
resolveStackToStackFrame_: function(pid, stack) {
// TODO(alph,fmeawad): Add codemap resolution code here.
return undefined;
},
importSampleData: function() {
if (!this.sampleEvents_)
return;
var m = this.model_;
// If this is the only importer, then fake-create the threads.
var events = this.sampleEvents_;
if (this.events_.length === 0) {
for (var i = 0; i < events.length; i++) {
var event = events[i];
m.getOrCreateProcess(event.tid).getOrCreateThread(event.tid);
}
}
var threadsByTid = {};
m.getAllThreads().forEach(function(t) {
threadsByTid[t.tid] = t;
});
for (var i = 0; i < events.length; i++) {
var event = events[i];
var thread = threadsByTid[event.tid];
if (thread === undefined) {
m.importWarning({
type: 'sample_import_error',
message: 'Thread ' + events.tid + 'not found'
});
continue;
}
var cpu;
if (event.cpu !== undefined)
cpu = m.kernel.getOrCreateCpu(event.cpu);
var stackFrame = this.getStackFrameForEvent_(event);
var sample = new tr.model.Sample(
cpu, thread,
event.name,
timestampFromUs(event.ts),
stackFrame,
event.weight);
m.samples.push(sample);
}
},
/**
* Called by the model to join references between objects, after final model
* bounds have been computed.
*/
joinRefs: function() {
this.joinObjectRefs_();
},
createAsyncSlices_: function() {
if (this.allAsyncEvents_.length === 0)
return;
this.allAsyncEvents_.sort(function(x, y) {
var d = x.event.ts - y.event.ts;
if (d !== 0)
return d;
return x.sequenceNumber - y.sequenceNumber;
});
var legacyEvents = [];
// Group nestable async events by ID. Events with the same ID should
// belong to the same parent async event.
var nestableAsyncEventsByKey = {};
for (var i = 0; i < this.allAsyncEvents_.length; i++) {
var asyncEventState = this.allAsyncEvents_[i];
var event = asyncEventState.event;
if (event.ph === 'S' || event.ph === 'F' || event.ph === 'T' ||
event.ph === 'p') {
legacyEvents.push(asyncEventState);
continue;
}
if (event.cat === undefined) {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'Nestable async events (ph: b, e, or n) require a ' +
'cat parameter.'
});
continue;
}
if (event.name === undefined) {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'Nestable async events (ph: b, e, or n) require a ' +
'name parameter.'
});
continue;
}
if (event.id === undefined) {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'Nestable async events (ph: b, e, or n) require an ' +
'id parameter.'
});
continue;
}
var key = event.cat + ':' + event.id;
if (nestableAsyncEventsByKey[key] === undefined)
nestableAsyncEventsByKey[key] = [];
nestableAsyncEventsByKey[key].push(asyncEventState);
}
// Handle legacy async events.
this.createLegacyAsyncSlices_(legacyEvents);
// Parse nestable async events into AsyncSlices.
for (var key in nestableAsyncEventsByKey) {
var eventStateEntries = nestableAsyncEventsByKey[key];
// Stack of enclosing BEGIN events.
var parentStack = [];
for (var i = 0; i < eventStateEntries.length; ++i) {
var eventStateEntry = eventStateEntries[i];
// If this is the end of an event, match it to the start.
if (eventStateEntry.event.ph === 'e') {
// Walk up the parent stack to find the corresponding BEGIN for
// this END.
var parentIndex = -1;
for (var k = parentStack.length - 1; k >= 0; --k) {
if (parentStack[k].event.name === eventStateEntry.event.name) {
parentIndex = k;
break;
}
}
if (parentIndex === -1) {
// Unmatched end.
eventStateEntry.finished = false;
} else {
parentStack[parentIndex].end = eventStateEntry;
// Pop off all enclosing unmatched BEGINs util parentIndex.
while (parentIndex < parentStack.length) {
parentStack.pop();
}
}
}
// Inherit the current parent.
if (parentStack.length > 0)
eventStateEntry.parentEntry = parentStack[parentStack.length - 1];
if (eventStateEntry.event.ph === 'b')
parentStack.push(eventStateEntry);
}
var topLevelSlices = [];
for (var i = 0; i < eventStateEntries.length; ++i) {
var eventStateEntry = eventStateEntries[i];
// Skip matched END, as its slice will be created when we
// encounter its corresponding BEGIN.
if (eventStateEntry.event.ph === 'e' &&
eventStateEntry.finished === undefined) {
continue;
}
var startState = undefined;
var endState = undefined;
var sliceArgs = eventStateEntry.event.args || {};
var sliceError = undefined;
if (eventStateEntry.event.ph === 'n') {
startState = eventStateEntry;
endState = eventStateEntry;
} else if (eventStateEntry.event.ph === 'b') {
if (eventStateEntry.end === undefined) {
// Unmatched BEGIN. End it when last event with this ID ends.
eventStateEntry.end =
eventStateEntries[eventStateEntries.length - 1];
sliceError =
'Slice has no matching END. End time has been adjusted.';
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'Nestable async BEGIN event at ' +
eventStateEntry.event.ts + ' with name=' +
eventStateEntry.event.name +
' and id=' + eventStateEntry.event.id + ' was unmatched.'
});
} else {
// Include args for both END and BEGIN for a matched pair.
function concatenateArguments(args1, args2) {
if (args1.params === undefined || args2.params === undefined)
return tr.b.concatenateObjects(args1, args2);
// Make an argument object to hold the combined params.
var args3 = {};
args3.params = tr.b.concatenateObjects(args1.params,
args2.params);
return tr.b.concatenateObjects(args1, args2, args3);
}
var endArgs = eventStateEntry.end.event.args || {};
sliceArgs = concatenateArguments(sliceArgs, endArgs);
}
startState = eventStateEntry;
endState = eventStateEntry.end;
} else {
// Unmatched END. Start it at the first event with this ID starts.
sliceError =
'Slice has no matching BEGIN. Start time has been adjusted.';
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'Nestable async END event at ' +
eventStateEntry.event.ts + ' with name=' +
eventStateEntry.event.name +
' and id=' + eventStateEntry.event.id + ' was unmatched.'
});
startState = eventStateEntries[0];
endState = eventStateEntry;
}
var isTopLevel = (eventStateEntry.parentEntry === undefined);
var asyncSliceConstructor =
tr.model.AsyncSlice.getConstructor(
eventStateEntry.event.cat,
eventStateEntry.event.name);
var thread_start = undefined;
var thread_duration = undefined;
if (startState.event.tts && startState.event.use_async_tts) {
thread_start = timestampFromUs(startState.event.tts);
if (endState.event.tts) {
var thread_end = timestampFromUs(endState.event.tts);
thread_duration = thread_end - thread_start;
}
}
var slice = new asyncSliceConstructor(
eventStateEntry.event.cat,
eventStateEntry.event.name,
getEventColor(endState.event),
timestampFromUs(startState.event.ts),
sliceArgs,
timestampFromUs(endState.event.ts - startState.event.ts),
isTopLevel,
thread_start,
thread_duration,
startState.event.argsStripped);
slice.startThread = startState.thread;
slice.endThread = endState.thread;
slice.startStackFrame = this.getStackFrameForEvent_(startState.event);
slice.endStackFrame = this.getStackFrameForEvent_(endState.event);
slice.id = key;
if (sliceError !== undefined)
slice.error = sliceError;
eventStateEntry.slice = slice;
// Add the slice to the topLevelSlices array if there is no parent.
// Otherwise, add the slice to the subSlices of its parent.
if (isTopLevel) {
topLevelSlices.push(slice);
} else if (eventStateEntry.parentEntry.slice !== undefined) {
eventStateEntry.parentEntry.slice.subSlices.push(slice);
}
}
for (var si = 0; si < topLevelSlices.length; si++) {
topLevelSlices[si].startThread.asyncSliceGroup.push(
topLevelSlices[si]);
}
}
},
createLegacyAsyncSlices_: function(legacyEvents) {
if (legacyEvents.length === 0)
return;
legacyEvents.sort(function(x, y) {
var d = x.event.ts - y.event.ts;
if (d != 0)
return d;
return x.sequenceNumber - y.sequenceNumber;
});
var asyncEventStatesByNameThenID = {};
for (var i = 0; i < legacyEvents.length; i++) {
var asyncEventState = legacyEvents[i];
var event = asyncEventState.event;
var name = event.name;
if (name === undefined) {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'Async events (ph: S, T, p, or F) require a name ' +
' parameter.'
});
continue;
}
var id = event.id;
if (id === undefined) {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'Async events (ph: S, T, p, or F) require an id parameter.'
});
continue;
}
// TODO(simonjam): Add a synchronous tick on the appropriate thread.
if (event.ph === 'S') {
if (asyncEventStatesByNameThenID[name] === undefined)
asyncEventStatesByNameThenID[name] = {};
if (asyncEventStatesByNameThenID[name][id]) {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'At ' + event.ts + ', a slice of the same id ' + id +
' was alrady open.'
});
continue;
}
asyncEventStatesByNameThenID[name][id] = [];
asyncEventStatesByNameThenID[name][id].push(asyncEventState);
} else {
if (asyncEventStatesByNameThenID[name] === undefined) {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'At ' + event.ts + ', no slice named ' + name +
' was open.'
});
continue;
}
if (asyncEventStatesByNameThenID[name][id] === undefined) {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'At ' + event.ts + ', no slice named ' + name +
' with id=' + id + ' was open.'
});
continue;
}
var events = asyncEventStatesByNameThenID[name][id];
events.push(asyncEventState);
if (event.ph === 'F') {
// Create a slice from start to end.
var asyncSliceConstructor =
tr.model.AsyncSlice.getConstructor(
events[0].event.cat,
name);
var slice = new asyncSliceConstructor(
events[0].event.cat,
name,
getEventColor(events[0].event),
timestampFromUs(events[0].event.ts),
tr.b.concatenateObjects(events[0].event.args,
events[events.length - 1].event.args),
timestampFromUs(event.ts - events[0].event.ts),
true, undefined, undefined, events[0].event.argsStripped);
slice.startThread = events[0].thread;
slice.endThread = asyncEventState.thread;
slice.id = id;
var stepType = events[1].event.ph;
var isValid = true;
// Create subSlices for each step. Skip the start and finish events,
// which are always first and last respectively.
for (var j = 1; j < events.length - 1; ++j) {
if (events[j].event.ph === 'T' || events[j].event.ph === 'p') {
isValid = this.assertStepTypeMatches_(stepType, events[j]);
if (!isValid)
break;
}
if (events[j].event.ph === 'S') {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'At ' + event.event.ts + ', a slice named ' +
event.event.name + ' with id=' + event.event.id +
' had a step before the start event.'
});
continue;
}
if (events[j].event.ph === 'F') {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'At ' + event.event.ts + ', a slice named ' +
event.event.name + ' with id=' + event.event.id +
' had a step after the finish event.'
});
continue;
}
var startIndex = j + (stepType === 'T' ? 0 : -1);
var endIndex = startIndex + 1;
var subName = events[j].event.name;
if (!events[j].event.argsStripped &&
(events[j].event.ph === 'T' || events[j].event.ph === 'p'))
subName = subName + ':' + events[j].event.args.step;
var asyncSliceConstructor =
tr.model.AsyncSlice.getConstructor(
events[0].event.cat,
subName);
var subSlice = new asyncSliceConstructor(
events[0].event.cat,
subName,
getEventColor(event, subName + j),
timestampFromUs(events[startIndex].event.ts),
this.deepCopyIfNeeded_(events[j].event.args),
timestampFromUs(
events[endIndex].event.ts - events[startIndex].event.ts),
undefined, undefined,
events[startIndex].event.argsStripped);
subSlice.startThread = events[startIndex].thread;
subSlice.endThread = events[endIndex].thread;
subSlice.id = id;
slice.subSlices.push(subSlice);
}
if (isValid) {
// Add |slice| to the start-thread's asyncSlices.
slice.startThread.asyncSliceGroup.push(slice);
}
delete asyncEventStatesByNameThenID[name][id];
}
}
}
},
assertStepTypeMatches_: function(stepType, event) {
if (stepType != event.event.ph) {
this.model_.importWarning({
type: 'async_slice_parse_error',
message: 'At ' + event.event.ts + ', a slice named ' +
event.event.name + ' with id=' + event.event.id +
' had both begin and end steps, which is not allowed.'
});
return false;
}
return true;
},
createFlowSlices_: function() {
if (this.allFlowEvents_.length === 0)
return;
var that = this;
function validateFlowEvent() {
if (event.name === undefined) {
that.model_.importWarning({
type: 'flow_slice_parse_error',
message: 'Flow events (ph: s, t or f) require a name parameter.'
});
return false;
}
// Support Flow API v1.
if (event.ph === 's' || event.ph === 'f' || event.ph === 't') {
if (event.id === undefined) {
that.model_.importWarning({
type: 'flow_slice_parse_error',
message: 'Flow events (ph: s, t or f) require an id parameter.'
});
return false;
}
return true;
}
// Support Flow API v2.
if (event.bind_id) {
if (event.flow_in === undefined && event.flow_out === undefined) {
that.model_.importWarning({
type: 'flow_slice_parse_error',
message: 'Flow producer or consumer require flow_in or flow_out.'
});
return false;
}
return true;
}
return false;
}
function createFlowEvent(thread, event, opt_slice) {
var startSlice, flowId, flowStartTs;
if (event.bind_id) {
// Support Flow API v2.
startSlice = opt_slice;
flowId = event.bind_id;
flowStartTs = timestampFromUs(event.ts + event.dur);
} else {
// Support Flow API v1.
var ts = timestampFromUs(event.ts);
startSlice = thread.sliceGroup.findSliceAtTs(ts);
if (startSlice === undefined)
return undefined;
flowId = event.id;
flowStartTs = ts;
}
var flowEvent = new tr.model.FlowEvent(
event.cat,
flowId,
event.name,
getEventColor(event),
flowStartTs,
that.deepCopyAlways_(event.args));
flowEvent.startSlice = startSlice;
flowEvent.startStackFrame = that.getStackFrameForEvent_(event);
flowEvent.endStackFrame = undefined;
startSlice.outFlowEvents.push(flowEvent);
return flowEvent;
}
function finishFlowEventWith(flowEvent, thread, event,
refGuid, bindToParent, opt_slice) {
var endSlice;
if (event.bind_id) {
// Support Flow API v2.
endSlice = opt_slice;
} else {
// Support Flow API v1.
var ts = timestampFromUs(event.ts);
if (bindToParent) {
endSlice = thread.sliceGroup.findSliceAtTs(ts);
} else {
endSlice = thread.sliceGroup.findNextSliceAfter(ts, refGuid);
}
if (endSlice === undefined)
return false;
}
endSlice.inFlowEvents.push(flowEvent);
flowEvent.endSlice = endSlice;
flowEvent.duration = timestampFromUs(event.ts) - flowEvent.start;
flowEvent.endStackFrame = that.getStackFrameForEvent_(event);
that.mergeArgsInto_(flowEvent.args, event.args, flowEvent.title);
return true;
}
function processFlowConsumer(flowIdToEvent, sliceGuidToEvent, event,
slice) {
var flowEvent = flowIdToEvent[event.bind_id];
if (flowEvent === undefined) {
that.model_.importWarning({
type: 'flow_slice_ordering_error',
message: 'Flow consumer ' + event.bind_id + ' does not have ' +
'a flow producer'});
return false;
} else if (flowEvent.endSlice) {
// One flow producer can have more than one flow consumers.
// In this case, create a new flow event using the flow producer.
var flowProducer = flowEvent.startSlice;
flowEvent = createFlowEvent(undefined,
sliceGuidToEvent[flowProducer.guid], flowProducer);
}
var ok = finishFlowEventWith(flowEvent, undefined, event,
refGuid, undefined, slice);
if (ok) {
that.model_.flowEvents.push(flowEvent);
} else {
that.model_.importWarning({
type: 'flow_slice_end_error',
message: 'Flow consumer ' + event.bind_id + ' does not end ' +
'at an actual slice, so cannot be created.'});
return false;
}
return true;
}
function processFlowProducer(flowIdToEvent, flowStatus, event, slice) {
if (flowIdToEvent[event.bind_id] &&
flowStatus[event.bind_id]) {
// Can't open the same flow again while it's still open.
// This is essentially the multi-producer case which we don't support
that.model_.importWarning({
type: 'flow_slice_start_error',
message: 'Flow producer ' + event.bind_id + ' already seen'});
return false;
}
var flowEvent = createFlowEvent(undefined, event, slice);
if (!flowEvent) {
that.model_.importWarning({
type: 'flow_slice_start_error',
message: 'Flow producer ' + event.bind_id + ' does not start' +
'a flow'});
return false;
}
flowIdToEvent[event.bind_id] = flowEvent;
return;
}
// Actual import.
this.allFlowEvents_.sort(function(x, y) {
var d = x.event.ts - y.event.ts;
if (d != 0)
return d;
return x.sequenceNumber - y.sequenceNumber;
});
var flowIdToEvent = {};
var sliceGuidToEvent = {};
var flowStatus = {}; // true: open; false: closed.
for (var i = 0; i < this.allFlowEvents_.length; ++i) {
var data = this.allFlowEvents_[i];
var refGuid = data.refGuid;
var event = data.event;
var thread = data.thread;
if (!validateFlowEvent(event))
continue;
// Support for Flow API v2.
if (event.bind_id) {
var slice = data.slice;
sliceGuidToEvent[slice.guid] = event;
if (event.flowPhase === PRODUCER) {
if (!processFlowProducer(flowIdToEvent, flowStatus, event, slice))
continue;
flowStatus[event.bind_id] = true; // open the flow.
}
else {
if (!processFlowConsumer(flowIdToEvent, sliceGuidToEvent,
event, slice))
continue;
flowStatus[event.bind_id] = false; // close the flow.
if (event.flowPhase === STEP) {
if (!processFlowProducer(flowIdToEvent, flowStatus,
event, slice))
continue;
flowStatus[event.bind_id] = true; // open the flow again.
}
}
continue;
}
// Support for Flow API v1.
var flowEvent;
if (event.ph === 's') {
if (flowIdToEvent[event.id]) {
this.model_.importWarning({
type: 'flow_slice_start_error',
message: 'event id ' + event.id + ' already seen when ' +
'encountering start of flow event.'});
continue;
}
flowEvent = createFlowEvent(thread, event);
if (!flowEvent) {
this.model_.importWarning({
type: 'flow_slice_start_error',
message: 'event id ' + event.id + ' does not start ' +
'at an actual slice, so cannot be created.'});
continue;
}
flowIdToEvent[event.id] = flowEvent;
} else if (event.ph === 't' || event.ph === 'f') {
flowEvent = flowIdToEvent[event.id];
if (flowEvent === undefined) {
this.model_.importWarning({
type: 'flow_slice_ordering_error',
message: 'Found flow phase ' + event.ph + ' for id: ' + event.id +
' but no flow start found.'
});
continue;
}
var bindToParent = event.ph === 't';
if (event.ph === 'f') {
if (event.bp === undefined) {
// TODO(yuhaoz): In flow V2, there is no notion of binding point.
// Removal of binding point is tracked in
// https://github.com/google/trace-viewer/issues/991.
if (event.cat.indexOf('input') > -1)
bindToParent = true;
else if (event.cat.indexOf('ipc.flow') > -1)
bindToParent = true;
} else {
if (event.bp !== 'e') {
this.model_.importWarning({
type: 'flow_slice_bind_point_error',
message: 'Flow event with invalid binding point (event.bp).'
});
continue;
}
bindToParent = true;
}
}
var ok = finishFlowEventWith(flowEvent, thread, event,
refGuid, bindToParent);
if (ok) {
that.model_.flowEvents.push(flowEvent);
} else {
this.model_.importWarning({
type: 'flow_slice_end_error',
message: 'event id ' + event.id + ' does not end ' +
'at an actual slice, so cannot be created.'});
}
flowIdToEvent[event.id] = undefined;
// If this is a step, then create another flow event.
if (ok && event.ph === 't') {
flowEvent = createFlowEvent(thread, event);
flowIdToEvent[event.id] = flowEvent;
}
}
}
},
/**
* This function creates objects described via the N, D, and O phase
* events.
*/
createExplicitObjects_: function() {
if (this.allObjectEvents_.length === 0)
return;
function processEvent(objectEventState) {
var event = objectEventState.event;
var thread = objectEventState.thread;
if (event.name === undefined) {
this.model_.importWarning({
type: 'object_parse_error',
message: 'While processing ' + JSON.stringify(event) + ': ' +
'Object events require an name parameter.'
});
}
if (event.id === undefined) {
this.model_.importWarning({
type: 'object_parse_error',
message: 'While processing ' + JSON.stringify(event) + ': ' +
'Object events require an id parameter.'
});
}
var process = thread.parent;
var ts = timestampFromUs(event.ts);
var instance;
if (event.ph === 'N') {
try {
instance = process.objects.idWasCreated(
event.id, event.cat, event.name, ts);
} catch (e) {
this.model_.importWarning({
type: 'object_parse_error',
message: 'While processing create of ' +
event.id + ' at ts=' + ts + ': ' + e
});
return;
}
} else if (event.ph === 'O') {
if (event.args.snapshot === undefined) {
this.model_.importWarning({
type: 'object_parse_error',
message: 'While processing ' + event.id + ' at ts=' + ts + ': ' +
'Snapshots must have args: {snapshot: ...}'
});
return;
}
var snapshot;
try {
var args = this.deepCopyIfNeeded_(event.args.snapshot);
var cat;
if (args.cat) {
cat = args.cat;
delete args.cat;
} else {
cat = event.cat;
}
var baseTypename;
if (args.base_type) {
baseTypename = args.base_type;
delete args.base_type;
} else {
baseTypename = undefined;
}
snapshot = process.objects.addSnapshot(
event.id, cat, event.name, ts,
args, baseTypename);
snapshot.snapshottedOnThread = thread;
} catch (e) {
this.model_.importWarning({
type: 'object_parse_error',
message: 'While processing snapshot of ' +
event.id + ' at ts=' + ts + ': ' + e
});
return;
}
instance = snapshot.objectInstance;
} else if (event.ph === 'D') {
try {
process.objects.idWasDeleted(event.id, event.cat, event.name, ts);
var instanceMap = process.objects.getOrCreateInstanceMap_(event.id);
instance = instanceMap.lastInstance;
} catch (e) {
this.model_.importWarning({
type: 'object_parse_error',
message: 'While processing delete of ' +
event.id + ' at ts=' + ts + ': ' + e
});
return;
}
}
if (instance)
instance.colorId = getEventColor(event, instance.typeName);
}
this.allObjectEvents_.sort(function(x, y) {
var d = x.event.ts - y.event.ts;
if (d != 0)
return d;
return x.sequenceNumber - y.sequenceNumber;
});
var allObjectEvents = this.allObjectEvents_;
for (var i = 0; i < allObjectEvents.length; i++) {
var objectEventState = allObjectEvents[i];
try {
processEvent.call(this, objectEventState);
} catch (e) {
this.model_.importWarning({
type: 'object_parse_error',
message: e.message
});
}
}
},
createImplicitObjects_: function() {
tr.b.iterItems(this.model_.processes, function(pid, process) {
this.createImplicitObjectsForProcess_(process);
}, this);
},
// Here, we collect all the snapshots that internally contain a
// Javascript-level object inside their args list that has an "id" field,
// and turn that into a snapshot of the instance referred to by id.
createImplicitObjectsForProcess_: function(process) {
function processField(referencingObject,
referencingObjectFieldName,
referencingObjectFieldValue,
containingSnapshot) {
if (!referencingObjectFieldValue)
return;
if (referencingObjectFieldValue instanceof
tr.model.ObjectSnapshot)
return null;
if (referencingObjectFieldValue.id === undefined)
return;
var implicitSnapshot = referencingObjectFieldValue;
var rawId = implicitSnapshot.id;
var m = /(.+)\/(.+)/.exec(rawId);
if (!m)
throw new Error('Implicit snapshots must have names.');
delete implicitSnapshot.id;
var name = m[1];
var id = m[2];
var res;
var cat;
if (implicitSnapshot.cat !== undefined)
cat = implicitSnapshot.cat;
else
cat = containingSnapshot.objectInstance.category;
var baseTypename;
if (implicitSnapshot.base_type)
baseTypename = implicitSnapshot.base_type;
else
baseTypename = undefined;
try {
res = process.objects.addSnapshot(
id, cat,
name, containingSnapshot.ts,
implicitSnapshot, baseTypename);
} catch (e) {
this.model_.importWarning({
type: 'object_snapshot_parse_error',
message: 'While processing implicit snapshot of ' +
rawId + ' at ts=' + containingSnapshot.ts + ': ' + e
});
return;
}
res.objectInstance.hasImplicitSnapshots = true;
res.containingSnapshot = containingSnapshot;
res.snapshottedOnThread = containingSnapshot.snapshottedOnThread;
referencingObject[referencingObjectFieldName] = res;
if (!(res instanceof tr.model.ObjectSnapshot))
throw new Error('Created object must be instanceof snapshot');
return res.args;
}
/**
* Iterates over the fields in the object, calling func for every
* field/value found.
*
* @return {object} If the function does not want the field's value to be
* iterated, return null. If iteration of the field value is desired, then
* return either undefined (if the field value did not change) or the new
* field value if it was changed.
*/
function iterObject(object, func, containingSnapshot, thisArg) {
if (!(object instanceof Object))
return;
if (object instanceof Array) {
for (var i = 0; i < object.length; i++) {
var res = func.call(thisArg, object, i, object[i],
containingSnapshot);
if (res === null)
continue;
if (res)
iterObject(res, func, containingSnapshot, thisArg);
else
iterObject(object[i], func, containingSnapshot, thisArg);
}
return;
}
for (var key in object) {
var res = func.call(thisArg, object, key, object[key],
containingSnapshot);
if (res === null)
continue;
if (res)
iterObject(res, func, containingSnapshot, thisArg);
else
iterObject(object[key], func, containingSnapshot, thisArg);
}
}
// TODO(nduca): We may need to iterate the instances in sorted order by
// creationTs.
process.objects.iterObjectInstances(function(instance) {
instance.snapshots.forEach(function(snapshot) {
if (snapshot.args.id !== undefined)
throw new Error('args cannot have an id field inside it');
iterObject(snapshot.args, processField, snapshot, this);
}, this);
}, this);
},
createMemoryDumps_: function() {
for (var dumpId in this.allMemoryDumpEvents_)
this.createGlobalMemoryDump_(this.allMemoryDumpEvents_[dumpId], dumpId);
},
createGlobalMemoryDump_: function(dumpIdEvents, dumpId) {
// 1. Create a GlobalMemoryDump for the provided process memory dump
// the events, all of which have the same dump ID.
// Calculate the range of the global memory dump.
var globalRange = new tr.b.Range();
for (var pid in dumpIdEvents) {
var processEvents = dumpIdEvents[pid];
for (var i = 0; i < processEvents.length; i++)
globalRange.addValue(timestampFromUs(processEvents[i].ts));
}
if (globalRange.isEmpty)
throw new Error('Internal error: Global memory dump without events');
// Create the global memory dump.
var globalMemoryDump = new tr.model.GlobalMemoryDump(
this.model_, globalRange.min);
globalMemoryDump.duration = globalRange.range;
this.model_.globalMemoryDumps.push(globalMemoryDump);
var globalMemoryAllocatorDumpsByFullName = {};
var levelOfDetailIndices = {};
var allMemoryAllocatorDumpsByGuid = {};
// 2. Create a ProcessMemoryDump for each PID in the provided process
// memory dump events. Everything except for edges between memory
// allocator dumps is parsed from the process memory dump trace events at
// this step.
for (var pid in dumpIdEvents) {
this.createProcessMemoryDump_(globalMemoryDump,
globalMemoryAllocatorDumpsByFullName, levelOfDetailIndices,
allMemoryAllocatorDumpsByGuid, dumpIdEvents[pid], pid, dumpId);
}
// 3. Set the level of detail and memory allocator dumps of the
// GlobalMemoryDump, which come from the process memory dump trace
// events parsed in the prebvious step.
globalMemoryDump.levelOfDetail =
MEMORY_DUMP_LEVELS_OF_DETAIL[levelOfDetailIndices.global];
// Find the root allocator dumps and establish the parent links of
// the global memory dump.
globalMemoryDump.memoryAllocatorDumps =
this.inferMemoryAllocatorDumpTree_(
globalMemoryAllocatorDumpsByFullName);
// 4. Finally, parse the edges between all memory allocator dumps within
// the GlobalMemoryDump. This can only be done once all memory allocator
// dumps have been parsed (i.e. it is necessary to iterate over the
// process memory dump trace events once more).
this.parseMemoryDumpAllocatorEdges_(allMemoryAllocatorDumpsByGuid,
dumpIdEvents, dumpId);
},
createProcessMemoryDump_: function(globalMemoryDump,
globalMemoryAllocatorDumpsByFullName, levelOfDetailIndices,
allMemoryAllocatorDumpsByGuid, processEvents, pid, dumpId) {
// Calculate the range of the process memory dump.
var processRange = new tr.b.Range();
for (var i = 0; i < processEvents.length; i++)
processRange.addValue(timestampFromUs(processEvents[i].ts));
if (processRange.isEmpty)
throw new Error('Internal error: Process memory dump without events');
// Create the process memory dump.
var process = this.model_.getOrCreateProcess(pid);
var processMemoryDump = new tr.model.ProcessMemoryDump(
globalMemoryDump, process, processRange.min);
processMemoryDump.duration = processRange.range;
process.memoryDumps.push(processMemoryDump);
globalMemoryDump.processMemoryDumps[pid] = processMemoryDump;
var processMemoryAllocatorDumpsByFullName = {};
// Parse all process memory dump trace events for the newly created
// ProcessMemoryDump.
for (var i = 0; i < processEvents.length; i++) {
var processEvent = processEvents[i];
var dumps = processEvent.args.dumps;
if (dumps === undefined) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: '\'dumps\' field not found in a process memory dump' +
' event for PID=' + pid + ' and dump ID=' + dumpId + '.'
});
continue;
}
// Totals, VM regions, and heap dumps for the newly created
// ProcessMemoryDump should be present in at most one event, so they
// can be added to the ProcessMemoryDump immediately.
this.parseMemoryDumpTotals_(processMemoryDump, dumps, pid, dumpId);
this.parseMemoryDumpVmRegions_(processMemoryDump, dumps, pid, dumpId);
this.parseMemoryDumpHeapDumps_(processMemoryDump, dumps, pid, dumpId);
// All process memory dump trace events for the newly created
// ProcessMemoryDump must be processed before level of detail and
// allocator dumps can be added to it.
this.parseMemoryDumpLevelOfDetail_(levelOfDetailIndices, dumps, pid,
dumpId);
this.parseMemoryDumpAllocatorDumps_(processMemoryDump, globalMemoryDump,
processMemoryAllocatorDumpsByFullName,
globalMemoryAllocatorDumpsByFullName,
allMemoryAllocatorDumpsByGuid, dumps, pid, dumpId);
}
processMemoryDump.levelOfDetail =
MEMORY_DUMP_LEVELS_OF_DETAIL[levelOfDetailIndices.process];
delete levelOfDetailIndices.process; // Reused for all process dumps.
// Find the root allocator dumps and establish the parent links of
// the process memory dump.
processMemoryDump.memoryAllocatorDumps =
this.inferMemoryAllocatorDumpTree_(
processMemoryAllocatorDumpsByFullName);
},
parseMemoryDumpTotals_: function(processMemoryDump, dumps, pid, dumpId) {
var rawTotals = dumps.process_totals;
if (rawTotals === undefined)
return;
if (processMemoryDump.totals !== undefined) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Process totals provided multiple times for' +
' process memory dump for PID=' + pid +
' and dump ID=' + dumpId + '.'
});
return;
}
var totals = {};
var platformSpecificTotals = undefined;
for (var rawTotalName in rawTotals) {
var rawTotalValue = rawTotals[rawTotalName];
if (rawTotalValue === undefined)
continue;
// Total resident bytes.
if (rawTotalName === 'resident_set_bytes') {
totals.residentBytes = parseInt(rawTotalValue, 16);
continue;
}
// Peak resident bytes.
if (rawTotalName === 'peak_resident_set_bytes') {
totals.peakResidentBytes = parseInt(rawTotalValue, 16);
continue;
}
if (rawTotalName === 'is_peak_rss_resetable') {
totals.arePeakResidentBytesResettable = !!rawTotalValue;
continue;
}
// OS-specific totals (e.g. private resident on Mac).
if (platformSpecificTotals === undefined) {
platformSpecificTotals = {};
totals.platformSpecific = platformSpecificTotals;
}
platformSpecificTotals[rawTotalName] = parseInt(rawTotalValue, 16);
}
// Either both peak_resident_set_bytes and is_peak_rss_resetable should
// be present in the trace, or neither.
if (totals.peakResidentBytes === undefined &&
totals.arePeakResidentBytesResettable !== undefined) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Optional field peak_resident_set_bytes found' +
' but is_peak_rss_resetable not found in' +
' process memory dump for PID=' + pid +
' and dump ID=' + dumpId + '.'
});
}
if (totals.arePeakResidentBytesResettable !== undefined &&
totals.peakResidentBytes === undefined) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Optional field is_peak_rss_resetable found' +
' but peak_resident_set_bytes not found in' +
' process memory dump for PID=' + pid +
' and dump ID=' + dumpId + '.'
});
}
processMemoryDump.totals = totals;
},
parseMemoryDumpVmRegions_: function(processMemoryDump, dumps, pid, dumpId) {
var rawProcessMmaps = dumps.process_mmaps;
if (rawProcessMmaps === undefined)
return;
var rawVmRegions = rawProcessMmaps.vm_regions;
if (rawVmRegions === undefined)
return;
if (processMemoryDump.vmRegions !== undefined) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'VM regions provided multiple times for' +
' process memory dump for PID=' + pid +
' and dump ID=' + dumpId + '.'
});
return;
}
function parseByteStat(rawValue) {
if (rawValue === undefined)
return undefined;
return parseInt(rawValue, 16);
}
var vmRegions = new Array(rawVmRegions.length);
for (var i = 0; i < rawVmRegions.length; i++) {
var rawVmRegion = rawVmRegions[i];
// See //base/trace_event/process_memory_maps.cc in Chromium.
var byteStats = new tr.model.VMRegionByteStats(
parseByteStat(rawVmRegion.bs.pc),
parseByteStat(rawVmRegion.bs.pd),
parseByteStat(rawVmRegion.bs.sc),
parseByteStat(rawVmRegion.bs.sd),
parseByteStat(rawVmRegion.bs.pss),
parseByteStat(rawVmRegion.bs.sw));
vmRegions[i] = new tr.model.VMRegion(
parseInt(rawVmRegion.sa, 16), // startAddress
parseInt(rawVmRegion.sz, 16), // sizeInBytes
rawVmRegion.pf, // protectionFlags
rawVmRegion.mf, // mappedFile
byteStats);
}
processMemoryDump.vmRegions = vmRegions;
},
parseMemoryDumpHeapDumps_: function(processMemoryDump, dumps, pid, dumpId) {
var rawHeapDumps = dumps.heaps;
if (rawHeapDumps === undefined)
return;
if (processMemoryDump.heapDumps !== undefined) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Heap dumps provided multiple times for' +
' process memory dump for PID=' + pid +
' and dump ID=' + dumpId + '.'
});
return;
}
var model = this.model_;
var idPrefix = 'p' + pid + ':';
var heapDumps = {};
for (var allocatorName in rawHeapDumps) {
var entries = rawHeapDumps[allocatorName].entries;
if (entries === undefined) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Missing heap entries in a ' + allocatorName +
' heap dump for PID=' + pid + ' and dump ID=' + dumpId + '.'
});
continue;
}
var heapDump = new tr.model.HeapDump(processMemoryDump, allocatorName);
for (var i = 0; i < entries.length; i++) {
var entry = entries[i];
var type = entry.type;
if (type !== undefined) {
// Heap dump entries with specified object type ID need to be
// ignored for the time being because Chrome doesn't dump the
// mapping from object type IDs to object type names yet. See
// crbug.com/524631.
continue;
}
var size = parseInt(entry.size, 16);
var leafStackFrameIndex = entry.bt;
var leafStackFrame;
if (leafStackFrameIndex === undefined) {
leafStackFrame = undefined;
} else {
var leafStackFrameId = idPrefix + leafStackFrameIndex;
leafStackFrame = model.stackFrames[leafStackFrameId];
if (leafStackFrame === undefined) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Missing leaf stack frame (ID ' + leafStackFrameId +
') of heap entry ' + i + ' (size ' + size + ') in a ' +
allocatorName + ' heap dump for PID=' + pid + '.'
});
continue;
}
}
heapDump.addEntry(leafStackFrame, size);
}
heapDumps[allocatorName] = heapDump;
}
processMemoryDump.heapDumps = heapDumps;
},
parseMemoryDumpLevelOfDetail_: function(levelOfDetailIndices, dumps, pid,
dumpId) {
var rawLevelOfDetail = dumps.level_of_detail;
// Determine the index of the level of detail of the dump.
var index = MEMORY_DUMP_LEVELS_OF_DETAIL.indexOf(rawLevelOfDetail);
if (index < 0) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'unknown level of detail \'' + rawLevelOfDetail +
'\' of process memory dump for PID=' + pid +
' and dump ID=' + dumpId + '.'
});
return;
}
// If the process memory dump events have different levels of detail
// (for the particular process and/or globally), show a warning and use
// the highest level.
if (levelOfDetailIndices.process === undefined) {
levelOfDetailIndices.process = index;
} else if (index !== levelOfDetailIndices.process) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'diffent levels of detail provided for process memory' +
' dump for PID=' + pid + ' (dump ID=' + dumpId + ').'
});
levelOfDetailIndices.process =
Math.max(levelOfDetailIndices.process, index);
}
if (levelOfDetailIndices.global === undefined) {
levelOfDetailIndices.global = index;
} else if (index !== levelOfDetailIndices.global) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'diffent levels of detail provided for global memory' +
' dump (dump ID=' + dumpId + ').'
});
levelOfDetailIndices.global =
Math.max(levelOfDetailIndices.global, index);
}
},
parseMemoryDumpAllocatorDumps_: function(processMemoryDump,
globalMemoryDump, processMemoryAllocatorDumpsByFullName,
globalMemoryAllocatorDumpsByFullName, allMemoryAllocatorDumpsByGuid,
dumps, pid, dumpId) {
var rawAllocatorDumps = dumps.allocators;
if (rawAllocatorDumps === undefined)
return;
// Construct the MemoryAllocatorDump objects without parent links
// and add them to the processMemoryAllocatorDumpsByName and
// globalMemoryAllocatorDumpsByName indices appropriately.
for (var fullName in rawAllocatorDumps) {
var rawAllocatorDump = rawAllocatorDumps[fullName];
// Every memory allocator dump should have a GUID. If not, then
// it cannot be associated with any edges.
var guid = rawAllocatorDump.guid;
if (guid === undefined) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Memory allocator dump ' + fullName + ' for PID=' + pid +
' and dump ID=' + dumpId + ' does not have a GUID.'
});
}
// Determine if this is a global memory allocator dump (check if
// it's prefixed with 'global/').
var containerMemoryDump;
var dstIndex;
if (fullName.startsWith(GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX)) {
// Global memory allocator dump.
fullName = fullName.substring(
GLOBAL_MEMORY_ALLOCATOR_DUMP_PREFIX.length);
containerMemoryDump = globalMemoryDump;
dstIndex = globalMemoryAllocatorDumpsByFullName;
} else {
// Process memory allocator dump.
containerMemoryDump = processMemoryDump;
dstIndex = processMemoryAllocatorDumpsByFullName;
}
// Construct or retrieve a memory allocator dump with the provided
// GUID.
var allocatorDump = allMemoryAllocatorDumpsByGuid[guid];
if (allocatorDump === undefined) {
if (fullName in dstIndex) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Multiple GUIDs provided for' +
' memory allocator dump ' + fullName + ': ' +
dstIndex[fullName].guid + ', ' + guid + ' (ignored) for' +
' PID=' + pid + ' and dump ID=' + dumpId + '.'
});
continue;
}
allocatorDump = new tr.model.MemoryAllocatorDump(
containerMemoryDump, fullName, guid);
dstIndex[fullName] = allocatorDump;
if (guid !== undefined)
allMemoryAllocatorDumpsByGuid[guid] = allocatorDump;
} else {
// A memory allocator dump with this GUID has already been
// dumped (so we will only add new attributes). Check that it
// belonged to the same process or was also global.
if (allocatorDump.containerMemoryDump !== containerMemoryDump) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Memory allocator dump ' + fullName +
' (GUID=' + guid + ') for PID=' + pid + ' and dump ID=' +
dumpId + ' dumped in different contexts.'
});
continue;
}
// Check that the names of the memory allocator dumps match.
if (allocatorDump.fullName !== fullName) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Memory allocator dump with GUID=' + guid + ' for PID=' +
pid + ' and dump ID=' + dumpId + ' has multiple names: ' +
allocatorDump.fullName + ', ' + fullName + ' (ignored).'
});
continue;
}
}
// Add all new attributes to the memory allocator dump.
var attributes = rawAllocatorDump.attrs;
if (attributes === undefined) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Memory allocator dump ' + fullName + ' (GUID=' + guid +
') for PID=' + pid + ' and dump ID=' + dumpId +
' does not have attributes.'
});
attributes = {};
}
for (var attrName in attributes) {
if (attrName in allocatorDump.attributes) {
// Skip existing attributes of the memory allocator dump.
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Multiple values provided for attribute ' + attrName +
' of memory allocator dump ' + fullName + ' (GUID=' + guid +
') for PID=' + pid + ' and dump ID=' + dumpId + '.'
});
continue;
}
var attrArgs = attributes[attrName];
var attrValue = tr.model.Attribute.fromDictIfPossible(attrArgs);
allocatorDump.addAttribute(attrName, attrValue);
}
}
},
inferMemoryAllocatorDumpTree_: function(memoryAllocatorDumpsByFullName) {
var rootAllocatorDumps = [];
var fullNames = Object.keys(memoryAllocatorDumpsByFullName);
fullNames.sort();
for (var i = 0; i < fullNames.length; i++) {
var fullName = fullNames[i];
var allocatorDump = memoryAllocatorDumpsByFullName[fullName];
// This is a loop because we might need to build implicit
// ancestors in case they were not present in the trace.
while (true) {
var lastSlashIndex = fullName.lastIndexOf('/');
if (lastSlashIndex === -1) {
// If the dump is a root, add it to the top-level
// rootAllocatorDumps list.
rootAllocatorDumps.push(allocatorDump);
break;
}
// If the dump is not a root, find its parent.
var parentFullName = fullName.substring(0, lastSlashIndex);
var parentAllocatorDump =
memoryAllocatorDumpsByFullName[parentFullName];
// If the parent dump does not exist yet, we build an implicit
// one and continue up the ancestor chain.
var parentAlreadyExisted = true;
if (parentAllocatorDump === undefined) {
parentAlreadyExisted = false;
parentAllocatorDump = new tr.model.MemoryAllocatorDump(
allocatorDump.containerMemoryDump, parentFullName);
memoryAllocatorDumpsByFullName[parentFullName] =
parentAllocatorDump;
}
// Setup the parent <-> children relationships
allocatorDump.parent = parentAllocatorDump;
parentAllocatorDump.children.push(allocatorDump);
// If the parent already existed, then its ancestors were/will be
// constructed in another iteration of the forEach loop.
if (parentAlreadyExisted)
break;
fullName = parentFullName;
allocatorDump = parentAllocatorDump;
}
}
return rootAllocatorDumps;
},
parseMemoryDumpAllocatorEdges_: function(allMemoryAllocatorDumpsByGuid,
dumpIdEvents, dumpId) {
for (var pid in dumpIdEvents) {
var processEvents = dumpIdEvents[pid];
for (var i = 0; i < processEvents.length; i++) {
var processEvent = processEvents[i];
var dumps = processEvent.args.dumps;
if (dumps === undefined)
continue;
var rawEdges = dumps.allocators_graph;
if (rawEdges === undefined)
continue;
for (var j = 0; j < rawEdges.length; j++) {
var rawEdge = rawEdges[j];
var sourceGuid = rawEdge.source;
var sourceDump = allMemoryAllocatorDumpsByGuid[sourceGuid];
if (sourceDump === undefined) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Edge for PID=' + pid + ' and dump ID=' + dumpId +
' is missing source memory allocator dump (GUID=' +
sourceGuid + ').'
});
continue;
}
var targetGuid = rawEdge.target;
var targetDump = allMemoryAllocatorDumpsByGuid[targetGuid];
if (targetDump === undefined) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Edge for PID=' + pid + ' and dump ID=' + dumpId +
' is missing target memory allocator dump (GUID=' +
targetGuid + ').'
});
continue;
}
var importance = rawEdge.importance;
var edge = new tr.model.MemoryAllocatorDumpLink(
sourceDump, targetDump, importance);
switch (rawEdge.type) {
case 'ownership':
if (sourceDump.owns !== undefined) {
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Memory allocator dump ' + sourceDump.fullName +
' (GUID=' + sourceGuid + ') already owns a memory' +
' allocator dump (' +
sourceDump.owns.target.fullName + ').'
});
} else {
sourceDump.owns = edge;
targetDump.ownedBy.push(edge);
}
break;
case 'retention':
sourceDump.retains.push(edge);
targetDump.retainedBy.push(edge);
break;
default:
this.model_.importWarning({
type: 'memory_dump_parse_error',
message: 'Invalid edge type: ' + rawEdge.type +
' (PID=' + pid + ', dump ID=' + dumpId +
', source=' + sourceGuid + ', target=' + targetGuid +
', importance=' + importance + ').'
});
}
}
}
}
},
joinObjectRefs_: function() {
tr.b.iterItems(this.model_.processes, function(pid, process) {
this.joinObjectRefsForProcess_(process);
}, this);
},
joinObjectRefsForProcess_: function(process) {
// Iterate the world, looking for id_refs
var patchupsToApply = [];
tr.b.iterItems(process.threads, function(tid, thread) {
thread.asyncSliceGroup.slices.forEach(function(item) {
this.searchItemForIDRefs_(
patchupsToApply, process.objects, 'start', item);
}, this);
thread.sliceGroup.slices.forEach(function(item) {
this.searchItemForIDRefs_(
patchupsToApply, process.objects, 'start', item);
}, this);
}, this);
process.objects.iterObjectInstances(function(instance) {
instance.snapshots.forEach(function(item) {
this.searchItemForIDRefs_(
patchupsToApply, process.objects, 'ts', item);
}, this);
}, this);
// Change all the fields pointing at id_refs to their real values.
patchupsToApply.forEach(function(patchup) {
patchup.object[patchup.field] = patchup.value;
});
},
searchItemForIDRefs_: function(patchupsToApply, objectCollection,
itemTimestampField, item) {
if (!item.args)
throw new Error('item is missing its args');
function handleField(object, fieldName, fieldValue) {
if (!fieldValue || (!fieldValue.id_ref && !fieldValue.idRef))
return;
var id = fieldValue.id_ref || fieldValue.idRef;
var ts = item[itemTimestampField];
var snapshot = objectCollection.getSnapshotAt(id, ts);
if (!snapshot)
return;
// We have to delay the actual change to the new value until after all
// refs have been located. Otherwise, we could end up recursing in
// ways we definitely didn't intend.
patchupsToApply.push({object: object,
field: fieldName,
value: snapshot});
}
function iterObjectFieldsRecursively(object) {
if (!(object instanceof Object))
return;
if ((object instanceof tr.model.ObjectSnapshot) ||
(object instanceof Float32Array) ||
(object instanceof tr.b.Quad))
return;
if (object instanceof Array) {
for (var i = 0; i < object.length; i++) {
handleField(object, i, object[i]);
iterObjectFieldsRecursively(object[i]);
}
return;
}
for (var key in object) {
var value = object[key];
handleField(object, key, value);
iterObjectFieldsRecursively(value);
}
}
iterObjectFieldsRecursively(item.args);
}
};
tr.importer.Importer.register(TraceEventImporter);
return {
TraceEventImporter: TraceEventImporter
};
});
</script>