blob: 2f20a225c69b9e26343dafc284833ba48b476d2f [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/base.html">
<link rel="import" href="/tracing/base/event.html">
<link rel="import" href="/tracing/base/interval_tree.html">
<link rel="import" href="/tracing/base/quad.html">
<link rel="import" href="/tracing/base/range.html">
<link rel="import" href="/tracing/base/task.html">
<link rel="import" href="/tracing/base/time_display_modes.html">
<link rel="import" href="/tracing/base/unit.html">
<link rel="import" href="/tracing/core/auditor.html">
<link rel="import" href="/tracing/core/filter.html">
<link rel="import" href="/tracing/model/alert.html">
<link rel="import" href="/tracing/model/clock_sync_manager.html">
<link rel="import" href="/tracing/model/constants.html">
<link rel="import" href="/tracing/model/device.html">
<link rel="import" href="/tracing/model/flow_event.html">
<link rel="import" href="/tracing/model/frame.html">
<link rel="import" href="/tracing/model/global_memory_dump.html">
<link rel="import" href="/tracing/model/instant_event.html">
<link rel="import" href="/tracing/model/kernel.html">
<link rel="import" href="/tracing/model/model_indices.html">
<link rel="import" href="/tracing/model/model_stats.html">
<link rel="import" href="/tracing/model/object_snapshot.html">
<link rel="import" href="/tracing/model/process.html">
<link rel="import" href="/tracing/model/process_memory_dump.html">
<link rel="import" href="/tracing/model/sample.html">
<link rel="import" href="/tracing/model/stack_frame.html">
<link rel="import" href="/tracing/model/user_model/user_expectation.html">
<link rel="import" href="/tracing/model/user_model/user_model.html">
<script>
'use strict';
/**
* @fileoverview Model is a parsed representation of the
* TraceEvents obtained from base/trace_event in which the begin-end
* tokens are converted into a hierarchy of processes, threads,
* subrows, and slices.
*
* The building block of the model is a slice. A slice is roughly
* equivalent to function call executing on a specific thread. As a
* result, slices may have one or more subslices.
*
* A thread contains one or more subrows of slices. Row 0 corresponds to
* the "root" slices, e.g. the topmost slices. Row 1 contains slices that
* are nested 1 deep in the stack, and so on. We use these subrows to draw
* nesting tasks.
*
*/
tr.exportTo('tr', function() {
var Process = tr.model.Process;
var Device = tr.model.Device;
var Kernel = tr.model.Kernel;
var GlobalMemoryDump = tr.model.GlobalMemoryDump;
var GlobalInstantEvent = tr.model.GlobalInstantEvent;
var FlowEvent = tr.model.FlowEvent;
var Alert = tr.model.Alert;
var Sample = tr.model.Sample;
/**
* @constructor
*/
function Model() {
tr.model.EventContainer.call(this);
tr.b.EventTarget.decorate(this);
this.timestampShiftToZeroAmount_ = 0;
this.faviconHue = 'blue'; // Should be a key from favicons.html
this.device = new Device(this);
this.kernel = new Kernel(this);
this.processes = {};
this.metadata = [];
this.categories = [];
this.instantEvents = [];
this.flowEvents = [];
this.clockSyncManager = new tr.model.ClockSyncManager();
this.intrinsicTimeUnit_ = undefined;
this.stackFrames = {};
this.samples = [];
this.alerts = [];
this.userModel = new tr.model.um.UserModel(this);
this.flowIntervalTree = new tr.b.IntervalTree((f) => f.start, (f) => f.end);
this.globalMemoryDumps = [];
this.userFriendlyCategoryDrivers_ = [];
this.annotationsByGuid_ = {};
this.modelIndices = undefined;
this.stats = new tr.model.ModelStats();
this.importWarnings_ = [];
this.reportedImportWarnings_ = {};
this.isTimeHighResolution_ = true;
this.patchupsToApply_ = [];
this.doesHelperGUIDSupportThisModel_ = {};
this.helpersByConstructorGUID_ = {};
this.eventsByStableId_ = undefined;
}
Model.prototype = {
__proto__: tr.model.EventContainer.prototype,
getEventByStableId: function(stableId) {
if (this.eventsByStableId_ === undefined) {
this.eventsByStableId_ = {};
for (var event of this.getDescendantEvents()) {
this.eventsByStableId_[event.stableId] = event;
}
}
return this.eventsByStableId_[stableId];
},
getOrCreateHelper: function(constructor) {
if (!constructor.guid)
throw new Error('Helper constructors must have GUIDs');
if (this.helpersByConstructorGUID_[constructor.guid] === undefined) {
if (this.doesHelperGUIDSupportThisModel_[constructor.guid] ===
undefined) {
this.doesHelperGUIDSupportThisModel_[constructor.guid] =
constructor.supportsModel(this);
}
if (!this.doesHelperGUIDSupportThisModel_[constructor.guid])
return undefined;
this.helpersByConstructorGUID_[constructor.guid] = new constructor(
this);
}
return this.helpersByConstructorGUID_[constructor.guid];
},
childEvents: function*() {
yield * this.globalMemoryDumps;
yield * this.instantEvents;
yield * this.flowEvents;
yield * this.alerts;
yield * this.samples;
},
childEventContainers: function*() {
yield this.userModel;
yield this.device;
yield this.kernel;
yield * tr.b.dictionaryValues(this.processes);
},
/**
* Some objects in the model can persist their state in ModelSettings.
*
* This iterates through them.
*/
iterateAllPersistableObjects: function(callback) {
this.kernel.iterateAllPersistableObjects(callback);
for (var pid in this.processes)
this.processes[pid].iterateAllPersistableObjects(callback);
},
updateBounds: function() {
this.bounds.reset();
var bounds = this.bounds;
for (var ec of this.childEventContainers()) {
ec.updateBounds();
bounds.addRange(ec.bounds);
}
for (var event of this.childEvents())
event.addBoundsToRange(bounds);
},
shiftWorldToZero: function() {
var shiftAmount = -this.bounds.min;
this.timestampShiftToZeroAmount_ = shiftAmount;
for (var ec of this.childEventContainers())
ec.shiftTimestampsForward(shiftAmount);
for (var event of this.childEvents())
event.start += shiftAmount;
this.updateBounds();
},
convertTimestampToModelTime: function(sourceClockDomainName, ts) {
if (sourceClockDomainName !== 'traceEventClock')
throw new Error('Only traceEventClock is supported.');
return tr.b.Unit.timestampFromUs(ts) +
this.timestampShiftToZeroAmount_;
},
get numProcesses() {
var n = 0;
for (var p in this.processes)
n++;
return n;
},
/**
* @return {Process} Gets a TimelineProcess for a specified pid. Returns
* undefined if the process doesn't exist.
*/
getProcess: function(pid) {
return this.processes[pid];
},
/**
* @return {Process} Gets a TimelineProcess for a specified pid or
* creates one if it does not exist.
*/
getOrCreateProcess: function(pid) {
if (!this.processes[pid])
this.processes[pid] = new Process(this, pid);
return this.processes[pid];
},
addStackFrame: function(stackFrame) {
if (this.stackFrames[stackFrame.id])
throw new Error('Stack frame already exists');
this.stackFrames[stackFrame.id] = stackFrame;
return stackFrame;
},
/**
* Generates the set of categories from the slices and counters.
*/
updateCategories_: function() {
var categoriesDict = {};
this.userModel.addCategoriesToDict(categoriesDict);
this.device.addCategoriesToDict(categoriesDict);
this.kernel.addCategoriesToDict(categoriesDict);
for (var pid in this.processes)
this.processes[pid].addCategoriesToDict(categoriesDict);
this.categories = [];
for (var category in categoriesDict)
if (category != '')
this.categories.push(category);
},
getAllThreads: function() {
var threads = [];
for (var tid in this.kernel.threads) {
threads.push(process.threads[tid]);
}
for (var pid in this.processes) {
var process = this.processes[pid];
for (var tid in process.threads) {
threads.push(process.threads[tid]);
}
}
return threads;
},
/**
* @param {(!function(!tr.model.Process): boolean)=} opt_predicate Optional
* predicate for filtering the returned processes. If undefined, all
* process in the model will be returned.
* @return {!Array<!tr.model.Process>} An array of processes in the model.
*/
getAllProcesses: function(opt_predicate) {
var processes = [];
for (var pid in this.processes) {
var process = this.processes[pid];
if (opt_predicate === undefined || opt_predicate(process))
processes.push(process);
}
return processes;
},
/**
* @return {Array} An array of all the counters in the model.
*/
getAllCounters: function() {
var counters = [];
counters.push.apply(
counters, tr.b.dictionaryValues(this.device.counters));
counters.push.apply(
counters, tr.b.dictionaryValues(this.kernel.counters));
for (var pid in this.processes) {
var process = this.processes[pid];
for (var tid in process.counters) {
counters.push(process.counters[tid]);
}
}
return counters;
},
getAnnotationByGUID: function(guid) {
return this.annotationsByGuid_[guid];
},
addAnnotation: function(annotation) {
if (!annotation.guid)
throw new Error('Annotation with undefined guid given');
this.annotationsByGuid_[annotation.guid] = annotation;
tr.b.dispatchSimpleEvent(this, 'annotationChange');
},
removeAnnotation: function(annotation) {
this.annotationsByGuid_[annotation.guid].onRemove();
delete this.annotationsByGuid_[annotation.guid];
tr.b.dispatchSimpleEvent(this, 'annotationChange');
},
getAllAnnotations: function() {
return tr.b.dictionaryValues(this.annotationsByGuid_);
},
addUserFriendlyCategoryDriver: function(ufcd) {
this.userFriendlyCategoryDrivers_.push(ufcd);
},
/**
* Gets the user friendly category string from an event.
*
* Returns undefined if none is known.
*/
getUserFriendlyCategoryFromEvent: function(event) {
for (var i = 0; i < this.userFriendlyCategoryDrivers_.length; i++) {
var ufc = this.userFriendlyCategoryDrivers_[i].fromEvent(event);
if (ufc !== undefined)
return ufc;
}
return undefined;
},
/**
* @param {String} The name of the thread to find.
* @return {Array} An array of all the matched threads.
*/
findAllThreadsNamed: function(name) {
var namedThreads = [];
namedThreads.push.apply(
namedThreads,
this.kernel.findAllThreadsNamed(name));
for (var pid in this.processes) {
namedThreads.push.apply(
namedThreads,
this.processes[pid].findAllThreadsNamed(name));
}
return namedThreads;
},
get importOptions() {
return this.importOptions_;
},
set importOptions(options) {
this.importOptions_ = options;
},
/**
* Returns a time unit that is used to format values and determines the
* precision of the timestamp values.
*/
get intrinsicTimeUnit() {
if (this.intrinsicTimeUnit_ === undefined)
return tr.b.TimeDisplayModes.ms;
return this.intrinsicTimeUnit_;
},
set intrinsicTimeUnit(value) {
if (this.intrinsicTimeUnit_ === value)
return;
if (this.intrinsicTimeUnit_ !== undefined)
throw new Error('Intrinsic time unit already set');
this.intrinsicTimeUnit_ = value;
},
get isTimeHighResolution() {
return this.isTimeHighResolution_;
},
set isTimeHighResolution(value) {
this.isTimeHighResolution_ = value;
},
/**
* Returns a link to a trace data file that this model was imported from.
* This is NOT the URL of a site being traced, but instead an indicator of
* where the data is stored.
*/
get canonicalUrl() {
return this.canonicalUrl_;
},
set canonicalUrl(value) {
if (this.canonicalUrl_ === value)
return;
if (this.canonicalUrl_ !== undefined)
throw new Error('canonicalUrl already set');
this.canonicalUrl_ = value;
},
/**
* Saves a warning that happened during import.
*
* Warnings are typically logged to the console, and optionally, the
* more critical ones are shown to the user.
*
* @param {Object} data The import warning data. Data must provide two
* accessors: type, message. The types are used to determine if we
* should output the message, we'll only output one message of each type.
* The message is the actual warning content.
*/
importWarning: function(data) {
data.showToUser = !!data.showToUser;
this.importWarnings_.push(data);
// Only log each warning type once. We may want to add some kind of
// flag to allow reporting all importer warnings.
if (this.reportedImportWarnings_[data.type] === true)
return;
if (this.importOptions_.showImportWarnings)
console.warn(data.message);
this.reportedImportWarnings_[data.type] = true;
},
get hasImportWarnings() {
return (this.importWarnings_.length > 0);
},
get importWarnings() {
return this.importWarnings_;
},
get importWarningsThatShouldBeShownToUser() {
return this.importWarnings_.filter(function(warning) {
return warning.showToUser;
});
},
autoCloseOpenSlices: function() {
// Sort the samples.
this.samples.sort(function(x, y) {
return x.start - y.start;
});
this.updateBounds();
this.kernel.autoCloseOpenSlices();
for (var pid in this.processes)
this.processes[pid].autoCloseOpenSlices();
},
createSubSlices: function() {
this.kernel.createSubSlices();
for (var pid in this.processes)
this.processes[pid].createSubSlices();
},
preInitializeObjects: function() {
for (var pid in this.processes)
this.processes[pid].preInitializeObjects();
},
initializeObjects: function() {
for (var pid in this.processes)
this.processes[pid].initializeObjects();
},
pruneEmptyContainers: function() {
this.kernel.pruneEmptyContainers();
for (var pid in this.processes)
this.processes[pid].pruneEmptyContainers();
},
mergeKernelWithUserland: function() {
for (var pid in this.processes)
this.processes[pid].mergeKernelWithUserland();
},
computeWorldBounds: function(shiftWorldToZero) {
this.updateBounds();
this.updateCategories_();
if (shiftWorldToZero)
this.shiftWorldToZero();
},
buildFlowEventIntervalTree: function() {
for (var i = 0; i < this.flowEvents.length; ++i) {
var flowEvent = this.flowEvents[i];
this.flowIntervalTree.insert(flowEvent);
}
this.flowIntervalTree.updateHighValues();
},
cleanupUndeletedObjects: function() {
for (var pid in this.processes)
this.processes[pid].autoDeleteObjects(this.bounds.max);
},
sortMemoryDumps: function() {
this.globalMemoryDumps.sort(function(x, y) {
return x.start - y.start;
});
for (var pid in this.processes)
this.processes[pid].sortMemoryDumps();
},
finalizeMemoryGraphs: function() {
this.globalMemoryDumps.forEach(function(dump) {
dump.finalizeGraph();
});
},
buildEventIndices: function() {
this.modelIndices = new tr.model.ModelIndices(this);
},
sortAlerts: function() {
this.alerts.sort(function(x, y) {
return x.start - y.start;
});
},
applyObjectRefPatchups: function() {
// Change all the fields pointing at id_refs to their real values.
var unresolved = [];
this.patchupsToApply_.forEach(function(patchup) {
if (patchup.pidRef in this.processes) {
var snapshot = this.processes[patchup.pidRef].objects.getSnapshotAt(
patchup.scopedId, patchup.ts);
if (snapshot) {
patchup.object[patchup.field] = snapshot;
snapshot.referencedAt(patchup.item, patchup.object, patchup.field);
return;
}
}
unresolved.push(patchup);
}, this);
this.patchupsToApply_ = unresolved;
},
replacePIDRefsInPatchups: function(old_pid_ref, new_pid_ref) {
this.patchupsToApply_.forEach(function(patchup) {
if (patchup.pidRef === old_pid_ref)
patchup.pidRef = new_pid_ref;
});
},
/**
* Called by the model to join references between objects, after final model
* bounds have been computed.
*/
joinRefs: function() {
this.joinObjectRefs_();
this.applyObjectRefPatchups();
},
joinObjectRefs_: function() {
tr.b.iterItems(this.processes, function(pid, process) {
this.joinObjectRefsForProcess_(pid, process);
}, this);
},
joinObjectRefsForProcess_: function(pid, process) {
// Iterate the world, looking for id_refs
tr.b.iterItems(process.threads, function(tid, thread) {
thread.asyncSliceGroup.slices.forEach(function(item) {
this.searchItemForIDRefs_(pid, 'start', item);
}, this);
thread.sliceGroup.slices.forEach(function(item) {
this.searchItemForIDRefs_(pid, 'start', item);
}, this);
}, this);
process.objects.iterObjectInstances(function(instance) {
instance.snapshots.forEach(function(item) {
this.searchItemForIDRefs_(pid, 'ts', item);
}, this);
}, this);
},
searchItemForIDRefs_: function(pid, itemTimestampField, item) {
if (!item.args && !item.contexts)
return;
var patchupsToApply = this.patchupsToApply_;
function handleField(object, fieldName, fieldValue) {
if (!fieldValue || (!fieldValue.id_ref && !fieldValue.idRef))
return;
var scope = fieldValue.scope || tr.model.OBJECT_DEFAULT_SCOPE;
var idRef = fieldValue.id_ref || fieldValue.idRef;
var scopedId = new tr.model.ScopedId(scope, idRef);
var pidRef = fieldValue.pid_ref || fieldValue.pidRef || pid;
var ts = item[itemTimestampField];
// 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({
item: item,
object: object,
field: fieldName,
pidRef: pidRef,
scopedId: scopedId,
ts: ts});
}
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);
iterObjectFieldsRecursively(item.contexts);
}
};
return {
Model: Model
};
});
</script>