blob: ee213f737739a4666c75c9c9c22d3e5a57ab0dba [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/range.html">
<link rel="import" href="/tracing/base/task.html">
<link rel="import" href="/tracing/base/units/units.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/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/interaction_record.html">
<link rel="import" href="/tracing/model/kernel.html">
<link rel="import" href="/tracing/model/model_indices.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/ui/base/overlay.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 InteractionRecord = tr.model.InteractionRecord;
var Sample = tr.model.Sample;
function ClockSyncRecord(name, ts, args) {
this.name = name;
this.ts = ts;
this.args = args;
}
/**
* @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.clockSyncRecords = [];
this.intrinsicTimeUnit_ = undefined;
this.stackFrames = {};
this.samples = [];
this.alerts = [];
this.interactionRecords = [];
this.flowIntervalTree = new tr.b.IntervalTree(
function(f) { return f.start; },
function(f) { return f.end; });
this.globalMemoryDumps = [];
this.userFriendlyCategoryDrivers_ = [];
this.annotationsByGuid_ = {};
this.modelIndices = undefined;
this.importWarnings_ = [];
this.reportedImportWarnings_ = {};
this.isTimeHighResolution_ = undefined;
}
Model.prototype = {
__proto__: tr.model.EventContainer.prototype,
iterateAllEventsInThisContainer: function(eventTypePredicate,
callback, opt_this) {
if (eventTypePredicate.call(opt_this, GlobalMemoryDump))
this.globalMemoryDumps.forEach(callback, opt_this);
if (eventTypePredicate.call(opt_this, GlobalInstantEvent))
this.instantEvents.forEach(callback, opt_this);
if (eventTypePredicate.call(opt_this, FlowEvent))
this.flowEvents.forEach(callback, opt_this);
if (eventTypePredicate.call(opt_this, Alert))
this.alerts.forEach(callback, opt_this);
if (eventTypePredicate.call(opt_this, InteractionRecord))
this.interactionRecords.forEach(callback, opt_this);
if (eventTypePredicate.call(opt_this, Sample))
this.samples.forEach(callback, opt_this);
},
iterateAllChildEventContainers: function(callback, opt_this) {
callback.call(opt_this, this.device);
callback.call(opt_this, this.kernel);
for (var pid in this.processes)
callback.call(opt_this, this.processes[pid]);
},
/**
* 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;
this.iterateAllChildEventContainers(function(ec) {
ec.updateBounds();
bounds.addRange(ec.bounds);
});
this.iterateAllEventsInThisContainer(
function(eventConstructor) { return true; },
function(event) {
event.addBoundsToRange(bounds);
});
},
shiftWorldToZero: function() {
var shiftAmount = -this.bounds.min;
this.timestampShiftToZeroAmount_ = shiftAmount;
this.iterateAllChildEventContainers(function(ec) {
ec.shiftTimestampsForward(shiftAmount);
});
this.iterateAllEventsInThisContainer(
function(eventConstructor) { return true; },
function(event) {
event.start += shiftAmount;
});
this.updateBounds();
},
convertTimestampToModelTime: function(sourceClockDomainName, ts) {
if (sourceClockDomainName !== 'traceEventClock')
throw new Error('Only traceEventClock is supported.');
return tr.b.u.Units.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];
},
pushInstantEvent: function(instantEvent) {
this.instantEvents.push(instantEvent);
},
addStackFrame: function(stackFrame) {
if (this.stackFrames[stackFrame.id])
throw new Error('Stack frame already exists');
this.stackFrames[stackFrame.id] = stackFrame;
return stackFrame;
},
addInteractionRecord: function(ir) {
this.interactionRecords.push(ir);
return ir;
},
getClockSyncRecordsNamed: function(name) {
return this.clockSyncRecords.filter(function(x) {
return x.name === name;
});
},
/**
* Generates the set of categories from the slices and counters.
*/
updateCategories_: function() {
var 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;
},
/**
* @return {Array} An array of all processes in the model.
*/
getAllProcesses: function() {
var processes = [];
for (var pid in this.processes)
processes.push(this.processes[pid]);
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;
},
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.u.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() {
if (this.isTimeHighResolution_ === undefined)
this.isTimeHighResolution_ = this.isTimeHighResolutionHeuristic_();
return this.isTimeHighResolution_;
},
set isTimeHighResolution(value) {
if (this.isTimeHighResolution_ === value)
return;
if (this.isTimeHighResolution_ !== undefined)
throw new Error('isTimeHighResolution already set');
this.isTimeHighResolution_ = 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();
},
calculateMemoryGraphAttributes: function() {
this.globalMemoryDumps.forEach(function(dump) {
dump.calculateGraphAttributes();
});
},
buildEventIndices: function() {
this.modelIndices = new tr.model.ModelIndices(this);
},
sortInteractionRecords: function() {
this.interactionRecords.sort(function(x, y) {
return x.start - y.start;
});
},
sortAlerts: function() {
this.alerts.sort(function(x, y) {
return x.start - y.start;
});
},
isTimeHighResolutionHeuristic_: function() {
if (this.intrinsicTimeUnit !== tr.b.u.TimeDisplayModes.ms)
return false;
// If the timer is only precise to the millisecond, then almost all event
// will be precisely X ms apart. We check that by looking at the
// decimal part of each event's start time. We create 100 bins for
// these fractions. If at least 90% of the events are in the same bin then
// the timer is deemed to be low resolution.
var nbEvents = 0;
var nbPerBin = [];
var maxEvents = 0;
for (var i = 0; i < 100; ++i)
nbPerBin.push(0);
this.iterateAllEvents(function(event) {
nbEvents++;
if (event.start !== undefined) {
// Compute
var remainder = Math.floor(
(event.start - Math.floor(event.start)) * 100);
nbPerBin[remainder]++;
maxEvents = Math.max(maxEvents, nbPerBin[remainder]);
}
});
// If there are too few events our heuristic is not very good, assume the
// timer is high resolution.
if (nbEvents < 100)
return true;
// If more than 90% of the events are snapped precisely on milliseconds
// boundary we got a trace with a low resolution timer.
return (maxEvents / nbEvents) < 0.9;
}
};
return {
ClockSyncRecord: ClockSyncRecord,
Model: Model
};
});
</script>