// 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 }; });