// Copyright (c) 2011 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.
/**
* @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('gpu', function() {
/**
* A TimelineSlice represents an interval of time on a given thread
* associated with a specific trace event. For example,
* TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms
* TRACE_EVENT_END() at time=0.3ms
* Results in a single timeline slice from 0.1 with duration 0.2.
*
* All time units are stored in milliseconds.
* @constructor
*/
function TimelineSlice(title, colorId, start, args) {
this.title = title;
this.start = start;
this.colorId = colorId;
this.args = args;
this.subSlices = [];
}
TimelineSlice.prototype = {
selected: false,
duration: undefined,
get end() {
return this.start + this.duration;
}
};
/**
* A TimelineThread stores all the trace events collected for a particular
* thread. We organize the slices on a thread by "subrows," where subrow 0
* has all the root slices, subrow 1 those nested 1 deep, and so on.
*
* @constructor
*/
function TimelineThread(parent, tid) {
this.parent = parent;
this.tid = tid;
this.subRows = [[]];
}
TimelineThread.prototype = {
getSubrow: function(i) {
while (i >= this.subRows.length)
this.subRows.push([]);
return this.subRows[i];
},
updateBounds: function() {
var slices = this.subRows[0];
if (slices.length != 0) {
this.minTimestamp = slices[0].start;
this.maxTimestamp = slices[slices.length - 1].end;
} else {
this.minTimestamp = undefined;
this.maxTimestamp = undefined;
}
}
};
/**
* 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 = {};
};
TimelineProcess.prototype = {
getThread: function(tid) {
if (!this.threads[tid])
this.threads[tid] = new TimelineThread(this, tid);
return this.threads[tid];
}
};
/**
* Builds a model from an array of TraceEvent objects.
* @param {Array} events An array of TraceEvents created by
* TraceEvent.ToJSON().
* @constructor
*/
function TimelineModel(events) {
this.processes = {};
if (events)
this.importEvents(events);
}
TimelineModel.prototype = {
__proto__: cr.EventTarget.prototype,
getProcess: 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.
*/
importEvents: function(events) {
// A ptid is a pid and tid joined together x:y fashion, eg 1024:130
// The ptid is a unique key for a thread in the trace.
// Threadstate
const numColorIds = 12;
function ThreadState(tid) {
this.openSlices = [];
}
var threadStateByPTID = {};
var nameToColorMap = {};
function getColor(name) {
if (!(name in nameToColorMap)) {
// Compute a simplistic hashcode of the string so we get consistent
// coloring across traces.
var hash = 0;
for (var i = 0; i < name.length; ++i)
hash = (hash + 37 * hash + name.charCodeAt(i)) % 0xFFFFFFFF;
nameToColorMap[name] = hash % numColorIds;
}
return nameToColorMap[name];
}
// Walk through events
for (var eI = 0; eI < events.length; eI++) {
var event = events[eI];
var ptid = event.pid + ':' + event.tid;
if (!(ptid in threadStateByPTID)) {
threadStateByPTID[ptid] = new ThreadState();
}
var state = threadStateByPTID[ptid];
if (event.ph == 'B') {
var colorId = getColor(event.name);
var slice = new TimelineSlice(event.name, colorId, event.ts,
event.args);
state.openSlices.push(slice);
} else if (event.ph == 'E') {
if (state.openSlices.length == 0) {
// Ignore E events that that are unmatched.
continue;
}
var slice = state.openSlices.pop();
slice.duration = event.ts - slice.start;
// Store the slice on the right subrow.
var thread = this.getProcess(event.pid).getThread(event.tid);
var subRowIndex = state.openSlices.length;
thread.getSubrow(subRowIndex).push(slice);
// Add the slice to the subSlices array of its parent.
if (state.openSlices.length) {
var parentSlice = state.openSlices[state.openSlices.length - 1];
parentSlice.subSlices.push(slice);
}
} else if (event.ph == 'I') {
// TODO(nduca): Implement parsing of immediate events.
console.log('Parsing of I-type events not implemented.');
} else {
throw new Error('Unrecognized event phase: ' + event.ph +
'(' + event.name + ')');
}
}
this.updateBounds();
this.shiftWorldToMicroseconds();
var boost = (this.maxTimestamp - this.minTimestamp) * 0.15;
this.minTimestamp = this.minTimestamp - boost;
this.maxTimestamp = this.maxTimestamp + boost;
},
updateBounds: function() {
var wmin = Infinity;
var wmax = -wmin;
var threads = this.getAllThreads();
for (var tI = 0; tI < threads.length; tI++) {
var thread = threads[tI];
thread.updateBounds();
wmin = Math.min(wmin, thread.minTimestamp);
wmax = Math.max(wmax, thread.maxTimestamp);
}
this.minTimestamp = wmin;
this.maxTimestamp = wmax;
},
shiftWorldToMicroseconds: function() {
var timeBase = this.minTimestamp;
var threads = this.getAllThreads();
for (var tI = 0; tI < threads.length; tI++) {
var thread = threads[tI];
for (var tSR = 0; tSR < thread.subRows.length; tSR++) {
var subRow = thread.subRows[tSR];
for (var tS = 0; tS < subRow.length; tS++) {
var slice = subRow[tS];
slice.start = (slice.start - timeBase) / 1000;
slice.duration /= 1000;
}
}
}
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 {
TimelineSlice: TimelineSlice,
TimelineThread: TimelineThread,
TimelineProcess: TimelineProcess,
TimelineModel: TimelineModel
};
});