// 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.
'use strict';
/**
* @fileoverview TimelineModel 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.
*
*/
cr.define('tracing', function() {
/**
* A TimelineSlice represents an interval of time plus parameters associated
* with that interval.
*
* All time units are stored in milliseconds.
* @constructor
*/
function TimelineSlice(title, colorId, start, args, opt_duration) {
this.title = title;
this.start = start;
this.colorId = colorId;
this.args = args;
this.didNotFinish = false;
if (opt_duration !== undefined)
this.duration = opt_duration;
}
TimelineSlice.prototype = {
selected: false,
duration: undefined,
get end() {
return this.start + this.duration;
}
};
/**
* A TimelineThreadSlice represents an interval of time on a thread resource
* with associated nestinged slice information.
*
* ThreadSlices are typically associated with a specific trace event pair on a
* specific thread.
* For example,
* TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms
* TRACE_EVENT_END0() at time=0.3ms
* This results in a single timeline slice from 0.1 with duration 0.2 on a
* specific thread.
*
* @constructor
*/
function TimelineThreadSlice(title, colorId, start, args, opt_duration) {
TimelineSlice.call(this, title, colorId, start, args, opt_duration);
this.subSlices = [];
}
TimelineThreadSlice.prototype = {
__proto__: TimelineSlice.prototype
};
/**
* A TimelineAsyncSlice represents an interval of time during which an
* asynchronous operation is in progress. An AsyncSlice consumes no CPU time
* itself and so is only associated with Threads at its start and end point.
*
* @constructor
*/
function TimelineAsyncSlice(title, colorId, start, args) {
TimelineSlice.call(this, title, colorId, start, args);
};
TimelineAsyncSlice.prototype = {
__proto__: TimelineSlice.prototype,
id: undefined,
startThread: undefined,
endThread: undefined,
subSlices: undefined
};
/**
* A TimelineThread stores all the trace events collected for a particular
* thread. We organize the synchronous slices on a thread by "subrows," where
* subrow 0 has all the root slices, subrow 1 those nested 1 deep, and so on.
* The asynchronous slices are stored in an TimelineAsyncSliceGroup object.
*
* The slices stored on a TimelineThread should be instances of
* TimelineThreadSlice.
*
* @constructor
*/
function TimelineThread(parent, tid) {
if (!parent)
throw 'Parent must be provided.';
this.parent = parent;
this.tid = tid;
this.subRows = [[]];
this.asyncSlices = new TimelineAsyncSliceGroup(this.ptid);
}
var ptidMap = {};
/**
* @return {String} A string that can be used as a unique key for a specific
* thread within a process.
*/
TimelineThread.getPTIDFromPidAndTid = function(pid, tid) {
if (!ptidMap[pid])
ptidMap[pid] = {};
if (!ptidMap[pid][tid])
ptidMap[pid][tid] = pid + ':' + tid;
return ptidMap[pid][tid];
}
TimelineThread.prototype = {
/**
* Name of the thread, if present.
*/
name: undefined,
/**
* @return {string} A concatenation of the parent id and the thread's
* tid. Can be used to uniquely identify a thread.
*/
get ptid() {
return TimelineThread.getPTIDFromPidAndTid(this.tid, this.parent.pid);
},
getSubrow: function(i) {
while (i >= this.subRows.length)
this.subRows.push([]);
return this.subRows[i];
},
shiftSubRow_: function(subRow, amount) {
for (var tS = 0; tS < subRow.length; tS++) {
var slice = subRow[tS];
slice.start = (slice.start + amount);
}
},
/**
* Shifts all the timestamps inside this thread forward by the amount
* specified.
*/
shiftTimestampsForward: function(amount) {
if (this.cpuSlices)
this.shiftSubRow_(this.cpuSlices, amount);
for (var tSR = 0; tSR < this.subRows.length; tSR++) {
this.shiftSubRow_(this.subRows[tSR], amount);
}
this.asyncSlices.shiftTimestampsForward(amount);
},
/**
* Updates the minTimestamp and maxTimestamp fields based on the
* current objects associated with the thread.
*/
updateBounds: function() {
var values = [];
var slices;
if (this.subRows[0].length != 0) {
slices = this.subRows[0];
values.push(slices[0].start);
values.push(slices[slices.length - 1].end);
}
if (this.asyncSlices.slices.length) {
this.asyncSlices.updateBounds();
values.push(this.asyncSlices.minTimestamp);
values.push(this.asyncSlices.maxTimestamp);
}
if (values.length) {
this.minTimestamp = Math.min.apply(Math, values);
this.maxTimestamp = Math.max.apply(Math, values);
} else {
this.minTimestamp = undefined;
this.maxTimestamp = undefined;
}
},
/**
* @return {String} A user-friendly name for this thread.
*/
get userFriendlyName() {
var tname = this.name || this.tid;
return this.parent.pid + ': ' + tname;
},
/**
* @return {String} User friendly details about this thread.
*/
get userFriendlyDetails() {
return 'pid: ' + this.parent.pid +
', tid: ' + this.tid +
(this.name ? ', name: ' + this.name : '');
}
};
/**
* Comparison between threads that orders first by pid,
* then by names, then by tid.
*/
TimelineThread.compare = function(x, y) {
if (x.parent.pid != y.parent.pid) {
return TimelineProcess.compare(x.parent, y.parent.pid);
}
if (x.name && y.name) {
var tmp = x.name.localeCompare(y.name);
if (tmp == 0)
return x.tid - y.tid;
return tmp;
} else if (x.name) {
return -1;
} else if (y.name) {
return 1;
} else {
return x.tid - y.tid;
}
};
/**
* Stores all the samples for a given counter.
* @constructor
*/
function TimelineCounter(parent, id, name) {
this.parent = parent;
this.id = id;
this.name = name;
this.seriesNames = [];
this.seriesColors = [];
this.timestamps = [];
this.samples = [];
}
TimelineCounter.prototype = {
__proto__: Object.prototype,
get numSeries() {
return this.seriesNames.length;
},
get numSamples() {
return this.timestamps.length;
},
/**
* Shifts all the timestamps inside this counter forward by the amount
* specified.
*/
shiftTimestampsForward: function(amount) {
for (var sI = 0; sI < this.timestamps.length; sI++)
this.timestamps[sI] = (this.timestamps[sI] + amount);
},
/**
* Updates the bounds for this counter based on the samples it contains.
*/
updateBounds: function() {
if (this.seriesNames.length != this.seriesColors.length)
throw 'seriesNames.length must match seriesColors.length';
if (this.numSeries * this.numSamples != this.samples.length)
throw 'samples.length must be a multiple of numSamples.';
this.totals = [];
if (this.samples.length == 0) {
this.minTimestamp = undefined;
this.maxTimestamp = undefined;
this.maxTotal = 0;
return;
}
this.minTimestamp = this.timestamps[0];
this.maxTimestamp = this.timestamps[this.timestamps.length - 1];
var numSeries = this.numSeries;
var maxTotal = -Infinity;
for (var i = 0; i < this.timestamps.length; i++) {
var total = 0;
for (var j = 0; j < numSeries; j++) {
total += this.samples[i * numSeries + j];
this.totals.push(total);
}
if (total > maxTotal)
maxTotal = total;
}
this.maxTotal = maxTotal;
}
};
/**
* Comparison between counters that orders by pid, then name.
*/
TimelineCounter.compare = function(x, y) {
if (x.parent.pid != y.parent.pid) {
return TimelineProcess.compare(x.parent, y.parent.pid);
}
var tmp = x.name.localeCompare(y.name);
if (tmp == 0)
return x.tid - y.tid;
return tmp;
};
/**
* The TimelineProcess represents a single process in the
* trace. Right now, we keep this around purely for bookkeeping
* reasons.
* @constructor
*/
function TimelineProcess(pid) {
this.pid = pid;
this.threads = {};
this.counters = {};
};
TimelineProcess.prototype = {
get numThreads() {
var n = 0;
for (var p in this.threads) {
n++;
}
return n;
},
/**
* Shifts all the timestamps inside this process forward by the amount
* specified.
*/
shiftTimestampsForward: function(amount) {
for (var tid in this.threads)
this.threads[tid].shiftTimestampsForward(amount);
for (var id in this.counters)
this.counters[id].shiftTimestampsForward(amount);
},
/**
* @return {TimlineThread} The thread identified by tid on this process,
* creating it if it doesn't exist.
*/
getOrCreateThread: function(tid) {
if (!this.threads[tid])
this.threads[tid] = new TimelineThread(this, tid);
return this.threads[tid];
},
/**
* @return {TimlineCounter} The counter on this process named 'name',
* creating it if it doesn't exist.
*/
getOrCreateCounter: function(cat, name) {
var id = cat + '.' + name;
if (!this.counters[id])
this.counters[id] = new TimelineCounter(this, id, name);
return this.counters[id];
}
};
/**
* Comparison between processes that orders by pid.
*/
TimelineProcess.compare = function(x, y) {
return x.pid - y.pid;
};
/**
* The TimelineCpu represents a Cpu from the kernel's point of view.
* @constructor
*/
function TimelineCpu(number) {
this.cpuNumber = number;
this.slices = [];
this.counters = {};
};
TimelineCpu.prototype = {
/**
* @return {TimlineCounter} The counter on this process named 'name',
* creating it if it doesn't exist.
*/
getOrCreateCounter: function(cat, name) {
var id;
if (cat.length)
id = cat + '.' + name;
else
id = name;
if (!this.counters[id])
this.counters[id] = new TimelineCounter(this, id, name);
return this.counters[id];
},
/**
* Shifts all the timestamps inside this CPU forward by the amount
* specified.
*/
shiftTimestampsForward: function(amount) {
for (var sI = 0; sI < this.slices.length; sI++)
this.slices[sI].start = (this.slices[sI].start + amount);
for (var id in this.counters)
this.counters[id].shiftTimestampsForward(amount);
},
/**
* Updates the minTimestamp and maxTimestamp fields based on the
* current slices attached to the cpu.
*/
updateBounds: function() {
var values = [];
if (this.slices.length) {
this.minTimestamp = this.slices[0].start;
this.maxTimestamp = this.slices[this.slices.length - 1].end;
} else {
this.minTimestamp = undefined;
this.maxTimestamp = undefined;
}
}
};
/**
* Comparison between processes that orders by cpuNumber.
*/
TimelineCpu.compare = function(x, y) {
return x.cpuNumber - y.cpuNumber;
};
/**
* A group of AsyncSlices.
* @constructor
*/
function TimelineAsyncSliceGroup(name) {
this.name = name;
this.slices = [];
}
TimelineAsyncSliceGroup.prototype = {
__proto__: Object.prototype,
/**
* Helper function that pushes the provided slice onto the slices array.
*/
push: function(slice) {
this.slices.push(slice);
},
/**
* @return {Number} The number of slices in this group.
*/
get length() {
return this.slices.length;
},
/**
* Built automatically by rebuildSubRows().
*/
subRows_: undefined,
/**
* Updates the bounds for this group based on the slices it contains.
*/
sortSlices_: function() {
this.slices.sort(function(x, y) {
return x.start - y.start;
});
},
/**
* Shifts all the timestamps inside this group forward by the amount
* specified.
*/
shiftTimestampsForward: function(amount) {
for (var sI = 0; sI < this.slices.length; sI++) {
var slice = this.slices[sI];
slice.start = (slice.start + amount);
for (var sJ = 0; sJ < slice.subSlices.length; sJ++)
slice.subSlices[sJ].start += amount;
}
},
/**
* Updates the bounds for this group based on the slices it contains.
*/
updateBounds: function() {
this.sortSlices_();
if (this.slices.length) {
this.minTimestamp = this.slices[0].start;
this.maxTimestamp = this.slices[this.slices.length - 1].end;
} else {
this.minTimestamp = undefined;
this.maxTimestamp = undefined;
}
this.subRows_ = undefined;
},
get subRows() {
if (!this.subRows_)
this.rebuildSubRows_();
return this.subRows_;
},
/**
* Breaks up the list of slices into N rows, each of which is a list of
* slices that are non overlapping.
*
* It uses a very simple approach: walk through the slices in sorted order
* by start time. For each slice, try to fit it in an existing subRow. If it
* doesn't fit in any subrow, make another subRow.
*/
rebuildSubRows_: function() {
this.sortSlices_();
var subRows = [];
for (var i = 0; i < this.slices.length; i++) {
var slice = this.slices[i];
var found = false;
for (var j = 0; j < subRows.length; j++) {
var subRow = subRows[j];
var lastSliceInSubRow = subRow[subRow.length - 1];
if (slice.start >= lastSliceInSubRow.end) {
found = true;
// Instead of plotting one big slice for the entire
// TimelineAsyncEvent, we plot each of the subSlices.
if (slice.subSlices === undefined || slice.subSlices.length < 1)
throw 'TimelineAsyncEvent missing subSlices: ' + slice.name;
for (var k = 0; k < slice.subSlices.length; k++)
subRow.push(slice.subSlices[k]);
break;
}
}
if (!found) {
var subRow = [];
if (slice.subSlices !== undefined) {
for (var k = 0; k < slice.subSlices.length; k++)
subRow.push(slice.subSlices[k]);
subRows.push(subRow);
}
}
}
this.subRows_ = subRows;
},
/**
* Breaks up this group into slices based on start thread.
*
* @return {Array} An array of TimelineAsyncSliceGroups where each group has
* slices that started on the same thread.
**/
computeSubGroups: function() {
var subGroupsByPTID = {};
for (var i = 0; i < this.slices.length; ++i) {
var slice = this.slices[i];
var slicePTID = slice.startThread.ptid;
if (!subGroupsByPTID[slicePTID])
subGroupsByPTID[slicePTID] = new TimelineAsyncSliceGroup(this.name);
subGroupsByPTID[slicePTID].slices.push(slice);
}
var groups = [];
for (var ptid in subGroupsByPTID) {
var group = subGroupsByPTID[ptid];
group.updateBounds();
groups.push(group);
}
return groups;
}
};
/**
* Comparison between counters that orders by pid, then name.
*/
TimelineCounter.compare = function(x, y) {
if (x.parent.pid != y.parent.pid) {
return TimelineProcess.compare(x.parent, y.parent.pid);
}
var tmp = x.name.localeCompare(y.name);
if (tmp == 0)
return x.tid - y.tid;
return tmp;
};
// The color pallette is split in half, with the upper
// half of the pallette being the "highlighted" verison
// of the base color. So, color 7's highlighted form is
// 7 + (pallette.length / 2).
//
// These bright versions of colors are automatically generated
// from the base colors.
//
// Within the color pallette, there are "regular" colors,
// which can be used for random color selection, and
// reserved colors, which are used when specific colors
// need to be used, e.g. where red is desired.
var palletteBase = [
{r: 138, g: 113, b: 152},
{r: 175, g: 112, b: 133},
{r: 127, g: 135, b: 225},
{r: 93, g: 81, b: 137},
{r: 116, g: 143, b: 119},
{r: 178, g: 214, b: 122},
{r: 87, g: 109, b: 147},
{r: 119, g: 155, b: 95},
{r: 114, g: 180, b: 160},
{r: 132, g: 85, b: 103},
{r: 157, g: 210, b: 150},
{r: 148, g: 94, b: 86},
{r: 164, g: 108, b: 138},
{r: 139, g: 191, b: 150},
{r: 110, g: 99, b: 145},
{r: 80, g: 129, b: 109},
{r: 125, g: 140, b: 149},
{r: 93, g: 124, b: 132},
{r: 140, g: 85, b: 140},
{r: 104, g: 163, b: 162},
{r: 132, g: 141, b: 178},
{r: 131, g: 105, b: 147},
{r: 135, g: 183, b: 98},
{r: 152, g: 134, b: 177},
{r: 141, g: 188, b: 141},
{r: 133, g: 160, b: 210},
{r: 126, g: 186, b: 148},
{r: 112, g: 198, b: 205},
{r: 180, g: 122, b: 195},
{r: 203, g: 144, b: 152},
// Reserved Entires
{r: 182, g: 125, b: 143},
{r: 126, g: 200, b: 148},
{r: 133, g: 160, b: 210},
{r: 240, g: 240, b: 240}];
// Make sure this number tracks the number of reserved entries in the
// pallette.
var numReservedColorIds = 4;
function brighten(c) {
var k;
if (c.r >= 240 && c.g >= 240 && c.b >= 240)
k = -0.20;
else
k = 0.45;
return {r: Math.min(255, c.r + Math.floor(c.r * k)),
g: Math.min(255, c.g + Math.floor(c.g * k)),
b: Math.min(255, c.b + Math.floor(c.b * k))};
}
function colorToString(c) {
return 'rgb(' + c.r + ',' + c.g + ',' + c.b + ')';
}
/**
* The number of color IDs that getStringColorId can choose from.
*/
var numRegularColorIds = palletteBase.length - numReservedColorIds;
var highlightIdBoost = palletteBase.length;
var pallette = palletteBase.concat(palletteBase.map(brighten)).
map(colorToString);
/**
* Computes a simplistic hashcode of the provide name. Used to chose colors
* for slices.
* @param {string} name The string to hash.
*/
function getStringHash(name) {
var hash = 0;
for (var i = 0; i < name.length; ++i)
hash = (hash + 37 * hash + 11 * name.charCodeAt(i)) % 0xFFFFFFFF;
return hash;
}
/**
* Gets the color pallette.
*/
function getPallette() {
return pallette;
}
/**
* @return {Number} The value to add to a color ID to get its highlighted
* colro ID. E.g. 7 + getPalletteHighlightIdBoost() yields a brightened from
* of 7's base color.
*/
function getPalletteHighlightIdBoost() {
return highlightIdBoost;
}
/**
* @param {String} name The color name.
* @return {Number} The color ID for the given color name.
*/
function getColorIdByName(name) {
if (name == 'iowait')
return numRegularColorIds;
if (name == 'running')
return numRegularColorIds + 1;
if (name == 'runnable')
return numRegularColorIds + 2;
if (name == 'sleeping')
return numRegularColorIds + 3;
throw 'Unrecognized color ' + name;
}
// Previously computed string color IDs. They are based on a stable hash, so
// it is safe to save them throughout the program time.
var stringColorIdCache = {};
/**
* @return {Number} A color ID that is stably associated to the provided via
* the getStringHash method. The color ID will be chosen from the regular
* ID space only, e.g. no reserved ID will be used.
*/
function getStringColorId(string) {
if (stringColorIdCache[string] === undefined) {
var hash = getStringHash(string);
stringColorIdCache[string] = hash % numRegularColorIds;
}
return stringColorIdCache[string];
}
/**
* Builds a model from an array of TraceEvent objects.
* @param {Object=} opt_data The event data to import into the new model.
* See TimelineModel.importEvents for details and more advanced ways to
* import data.
* @param {bool=} opt_zeroAndBoost Whether to align to zero and boost the
* by 15%. Defaults to true.
* @constructor
*/
function TimelineModel(opt_eventData, opt_zeroAndBoost) {
this.cpus = {};
this.processes = {};
this.importErrors = [];
this.asyncSliceGroups = {};
if (opt_eventData)
this.importEvents(opt_eventData, opt_zeroAndBoost);
}
var importerConstructors = [];
/**
* Registers an importer. All registered importers are considered
* when processing an import request.
*
* @param {Function} importerConstructor The importer's constructor function.
*/
TimelineModel.registerImporter = function(importerConstructor) {
importerConstructors.push(importerConstructor);
};
function TimelineModelEmptyImporter(events) {
};
TimelineModelEmptyImporter.canImport = function(eventData) {
if (eventData instanceof Array && eventData.length == 0)
return true;
if (typeof(eventData) === 'string' || eventData instanceof String) {
return eventData.length == 0;
}
return false;
};
TimelineModelEmptyImporter.prototype = {
__proto__: Object.prototype,
importEvents: function() {
},
finalizeImport: function() {
}
};
TimelineModel.registerImporter(TimelineModelEmptyImporter);
TimelineModel.prototype = {
__proto__: cr.EventTarget.prototype,
get numProcesses() {
var n = 0;
for (var p in this.processes)
n++;
return n;
},
/**
* @return {TimelineProcess} Gets a specific TimelineCpu or creates one if
* it does not exist.
*/
getOrCreateCpu: function(cpuNumber) {
if (!this.cpus[cpuNumber])
this.cpus[cpuNumber] = new TimelineCpu(cpuNumber);
return this.cpus[cpuNumber];
},
/**
* @return {TimelineProcess} Gets a TimlineProcess for a specified pid or
* creates one if it does not exist.
*/
getOrCreateProcess: function(pid) {
if (!this.processes[pid])
this.processes[pid] = new TimelineProcess(pid);
return this.processes[pid];
},
/**
* The import takes an array of json-ified TraceEvents and adds them into
* the TimelineModel as processes, threads, and slices.
*/
/**
* Removes threads from the model that are fully empty.
*/
pruneEmptyThreads: function() {
for (var pid in this.processes) {
var process = this.processes[pid];
var prunedThreads = {};
for (var tid in process.threads) {
var thread = process.threads[tid];
// Begin-events without matching end events leave a thread in a state
// where the toplevel subrows are empty but child subrows have
// entries. The autocloser will fix this up later. But, for the
// purposes of pruning, such threads need to be treated as having
// content.
var hasNonEmptySubrow = false;
for (var s = 0; s < thread.subRows.length; s++)
hasNonEmptySubrow |= thread.subRows[s].length > 0;
if (hasNonEmptySubrow || thread.asyncSlices.length > 0)
prunedThreads[tid] = thread;
}
process.threads = prunedThreads;
}
},
updateBounds: function() {
var wmin = Infinity;
var wmax = -wmin;
var hasData = false;
var threads = this.getAllThreads();
for (var tI = 0; tI < threads.length; tI++) {
var thread = threads[tI];
thread.updateBounds();
if (thread.minTimestamp != undefined &&
thread.maxTimestamp != undefined) {
wmin = Math.min(wmin, thread.minTimestamp);
wmax = Math.max(wmax, thread.maxTimestamp);
hasData = true;
}
}
var counters = this.getAllCounters();
for (var tI = 0; tI < counters.length; tI++) {
var counter = counters[tI];
counter.updateBounds();
if (counter.minTimestamp != undefined &&
counter.maxTimestamp != undefined) {
hasData = true;
wmin = Math.min(wmin, counter.minTimestamp);
wmax = Math.max(wmax, counter.maxTimestamp);
}
}
for (var cpuNumber in this.cpus) {
var cpu = this.cpus[cpuNumber];
cpu.updateBounds();
if (cpu.minTimestamp != undefined &&
cpu.maxTimestamp != undefined) {
hasData = true;
wmin = Math.min(wmin, cpu.minTimestamp);
wmax = Math.max(wmax, cpu.maxTimestamp);
}
}
if (hasData) {
this.minTimestamp = wmin;
this.maxTimestamp = wmax;
} else {
this.maxTimestamp = undefined;
this.minTimestamp = undefined;
}
},
shiftWorldToZero: function() {
if (this.minTimestamp === undefined)
return;
var timeBase = this.minTimestamp;
for (var pid in this.processes)
this.processes[pid].shiftTimestampsForward(-timeBase);
for (var cpuNumber in this.cpus)
this.cpus[cpuNumber].shiftTimestampsForward(-timeBase);
this.updateBounds();
},
getAllThreads: function() {
var threads = [];
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 cpus in the model.
*/
getAllCpus: function() {
var cpus = [];
for (var cpu in this.cpus)
cpus.push(this.cpus[cpu]);
return cpus;
},
/**
* @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 = [];
for (var pid in this.processes) {
var process = this.processes[pid];
for (var tid in process.counters) {
counters.push(process.counters[tid]);
}
}
for (var cpuNumber in this.cpus) {
var cpu = this.cpus[cpuNumber];
for (var counterName in cpu.counters)
counters.push(cpu.counters[counterName]);
}
return counters;
},
/**
* Imports the provided events into the model. The eventData type
* is undefined and will be passed to all the timeline importers registered
* via TimelineModel.registerImporter. The first importer that returns true
* for canImport(events) will be used to import the events.
*
* @param {Object} events Events to import.
* @param {boolean} isAdditionalImport True the eventData being imported is
* an additional trace after the primary eventData.
* @return {TimelineModelImporter} The importer used for the eventData.
*/
importOneTrace_: function(eventData, isAdditionalImport) {
var importerConstructor;
for (var i = 0; i < importerConstructors.length; ++i) {
if (importerConstructors[i].canImport(eventData)) {
importerConstructor = importerConstructors[i];
break;
}
}
if (!importerConstructor)
throw 'Could not find an importer for the provided eventData.';
var importer = new importerConstructor(
this, eventData, isAdditionalImport);
importer.importEvents();
return importer;
},
/**
* Imports the provided traces into the model. The eventData type
* is undefined and will be passed to all the timeline importers registered
* via TimelineModel.registerImporter. The first importer that returns true
* for canImport(events) will be used to import the events.
*
* The primary trace is provided via the eventData variable. If multiple
* traces are to be imported, specify the first one as events, and the
* remainder in the opt_additionalEventData array.
*
* @param {Object} eventData Events to import.
* @param {bool=} opt_zeroAndBoost Whether to align to zero and boost the
* by 15%. Defaults to true.
* @param {Array=} opt_additionalEventData An array of eventData objects
* (e.g. array of arrays) to
* import after importing the primary events.
*/
importEvents: function(eventData,
opt_zeroAndBoost, opt_additionalEventData) {
if (opt_zeroAndBoost === undefined)
opt_zeroAndBoost = true;
var activeImporters = [];
var importer = this.importOneTrace_(eventData, false);
activeImporters.push(importer);
if (opt_additionalEventData) {
for (var i = 0; i < opt_additionalEventData.length; ++i) {
importer = this.importOneTrace_(opt_additionalEventData[i], true);
activeImporters.push(importer);
}
}
for (var i = 0; i < activeImporters.length; ++i)
activeImporters[i].finalizeImport();
for (var i = 0; i < activeImporters.length; ++i)
this.pruneEmptyThreads();
this.updateBounds();
if (opt_zeroAndBoost)
this.shiftWorldToZero();
if (opt_zeroAndBoost &&
this.minTimestamp !== undefined &&
this.maxTimestamp !== undefined) {
var boost = (this.maxTimestamp - this.minTimestamp) * 0.15;
this.minTimestamp = this.minTimestamp - boost;
this.maxTimestamp = this.maxTimestamp + boost;
}
}
};
/**
* @constructor A filter that can be passed into
* Timeline.findAllObjectsMatchingFilter
*/
function TimelineFilter(text) {
this.text_ = text;
}
TimelineFilter.prototype = {
__proto__: Object.prototype,
matchSlice: function(slice) {
if (this.text_.length == 0)
return false;
return slice.title.indexOf(this.text_) != -1;
}
};
return {
getPallette: getPallette,
getPalletteHighlightIdBoost: getPalletteHighlightIdBoost,
getColorIdByName: getColorIdByName,
getStringHash: getStringHash,
getStringColorId: getStringColorId,
TimelineSlice: TimelineSlice,
TimelineThreadSlice: TimelineThreadSlice,
TimelineAsyncSlice: TimelineAsyncSlice,
TimelineThread: TimelineThread,
TimelineCounter: TimelineCounter,
TimelineProcess: TimelineProcess,
TimelineCpu: TimelineCpu,
TimelineAsyncSliceGroup: TimelineAsyncSliceGroup,
TimelineModel: TimelineModel,
TimelineFilter: TimelineFilter
};
});