1// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5 6/** 7 * @fileoverview TimelineModel is a parsed representation of the 8 * TraceEvents obtained from base/trace_event in which the begin-end 9 * tokens are converted into a hierarchy of processes, threads, 10 * subrows, and slices. 11 * 12 * The building block of the model is a slice. A slice is roughly 13 * equivalent to function call executing on a specific thread. As a 14 * result, slices may have one or more subslices. 15 * 16 * A thread contains one or more subrows of slices. Row 0 corresponds to 17 * the "root" slices, e.g. the topmost slices. Row 1 contains slices that 18 * are nested 1 deep in the stack, and so on. We use these subrows to draw 19 * nesting tasks. 20 * 21 */ 22cr.define('gpu', function() { 23 /** 24 * A TimelineSlice represents an interval of time on a given thread 25 * associated with a specific trace event. For example, 26 * TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms 27 * TRACE_EVENT_END() at time=0.3ms 28 * Results in a single timeline slice from 0.1 with duration 0.2. 29 * 30 * All time units are stored in milliseconds. 31 * @constructor 32 */ 33 function TimelineSlice(title, colorId, start, args) { 34 this.title = title; 35 this.start = start; 36 this.colorId = colorId; 37 this.args = args; 38 this.subSlices = []; 39 } 40 41 TimelineSlice.prototype = { 42 selected: false, 43 44 duration: undefined, 45 46 get end() { 47 return this.start + this.duration; 48 } 49 }; 50 51 /** 52 * A TimelineThread stores all the trace events collected for a particular 53 * thread. We organize the slices on a thread by "subrows," where subrow 0 54 * has all the root slices, subrow 1 those nested 1 deep, and so on. 55 * 56 * @constructor 57 */ 58 function TimelineThread(parent, tid) { 59 this.parent = parent; 60 this.tid = tid; 61 this.subRows = [[]]; 62 } 63 64 TimelineThread.prototype = { 65 getSubrow: function(i) { 66 while (i >= this.subRows.length) 67 this.subRows.push([]); 68 return this.subRows[i]; 69 }, 70 71 updateBounds: function() { 72 var slices = this.subRows[0]; 73 if (slices.length != 0) { 74 this.minTimestamp = slices[0].start; 75 this.maxTimestamp = slices[slices.length - 1].end; 76 } else { 77 this.minTimestamp = undefined; 78 this.maxTimestamp = undefined; 79 } 80 } 81 82 }; 83 84 /** 85 * The TimelineProcess represents a single process in the 86 * trace. Right now, we keep this around purely for bookkeeping 87 * reasons. 88 * @constructor 89 */ 90 function TimelineProcess(pid) { 91 this.pid = pid; 92 this.threads = {}; 93 }; 94 95 TimelineProcess.prototype = { 96 getThread: function(tid) { 97 if (!this.threads[tid]) 98 this.threads[tid] = new TimelineThread(this, tid); 99 return this.threads[tid]; 100 } 101 }; 102 103 /** 104 * Builds a model from an array of TraceEvent objects. 105 * @param {Array} events An array of TraceEvents created by 106 * TraceEvent.ToJSON(). 107 * @constructor 108 */ 109 function TimelineModel(events) { 110 this.processes = {}; 111 112 if (events) 113 this.importEvents(events); 114 } 115 116 TimelineModel.prototype = { 117 __proto__: cr.EventTarget.prototype, 118 119 getProcess: function(pid) { 120 if (!this.processes[pid]) 121 this.processes[pid] = new TimelineProcess(pid); 122 return this.processes[pid]; 123 }, 124 125 /** 126 * The import takes an array of json-ified TraceEvents and adds them into 127 * the TimelineModel as processes, threads, and slices. 128 */ 129 importEvents: function(events) { 130 // A ptid is a pid and tid joined together x:y fashion, eg 1024:130 131 // The ptid is a unique key for a thread in the trace. 132 133 134 // Threadstate 135 const numColorIds = 12; 136 function ThreadState(tid) { 137 this.openSlices = []; 138 } 139 var threadStateByPTID = {}; 140 141 var nameToColorMap = {}; 142 function getColor(name) { 143 if (!(name in nameToColorMap)) { 144 // Compute a simplistic hashcode of the string so we get consistent 145 // coloring across traces. 146 var hash = 0; 147 for (var i = 0; i < name.length; ++i) 148 hash = (hash + 37 * hash + name.charCodeAt(i)) % 0xFFFFFFFF; 149 nameToColorMap[name] = hash % numColorIds; 150 } 151 return nameToColorMap[name]; 152 } 153 154 // Walk through events 155 for (var eI = 0; eI < events.length; eI++) { 156 var event = events[eI]; 157 var ptid = event.pid + ':' + event.tid; 158 if (!(ptid in threadStateByPTID)) { 159 threadStateByPTID[ptid] = new ThreadState(); 160 } 161 var state = threadStateByPTID[ptid]; 162 if (event.ph == 'B') { 163 var colorId = getColor(event.name); 164 var slice = new TimelineSlice(event.name, colorId, event.ts, 165 event.args); 166 state.openSlices.push(slice); 167 } else if (event.ph == 'E') { 168 if (state.openSlices.length == 0) { 169 // Ignore E events that that are unmatched. 170 continue; 171 } 172 var slice = state.openSlices.pop(); 173 slice.duration = event.ts - slice.start; 174 175 // Store the slice on the right subrow. 176 var thread = this.getProcess(event.pid).getThread(event.tid); 177 var subRowIndex = state.openSlices.length; 178 thread.getSubrow(subRowIndex).push(slice); 179 180 // Add the slice to the subSlices array of its parent. 181 if (state.openSlices.length) { 182 var parentSlice = state.openSlices[state.openSlices.length - 1]; 183 parentSlice.subSlices.push(slice); 184 } 185 } else if (event.ph == 'I') { 186 // TODO(nduca): Implement parsing of immediate events. 187 console.log('Parsing of I-type events not implemented.'); 188 } else { 189 throw new Error('Unrecognized event phase: ' + event.ph + 190 '(' + event.name + ')'); 191 } 192 } 193 this.updateBounds(); 194 195 this.shiftWorldToMicroseconds(); 196 197 var boost = (this.maxTimestamp - this.minTimestamp) * 0.15; 198 this.minTimestamp = this.minTimestamp - boost; 199 this.maxTimestamp = this.maxTimestamp + boost; 200 }, 201 202 updateBounds: function() { 203 var wmin = Infinity; 204 var wmax = -wmin; 205 var threads = this.getAllThreads(); 206 for (var tI = 0; tI < threads.length; tI++) { 207 var thread = threads[tI]; 208 thread.updateBounds(); 209 wmin = Math.min(wmin, thread.minTimestamp); 210 wmax = Math.max(wmax, thread.maxTimestamp); 211 } 212 this.minTimestamp = wmin; 213 this.maxTimestamp = wmax; 214 }, 215 216 shiftWorldToMicroseconds: function() { 217 var timeBase = this.minTimestamp; 218 var threads = this.getAllThreads(); 219 for (var tI = 0; tI < threads.length; tI++) { 220 var thread = threads[tI]; 221 for (var tSR = 0; tSR < thread.subRows.length; tSR++) { 222 var subRow = thread.subRows[tSR]; 223 for (var tS = 0; tS < subRow.length; tS++) { 224 var slice = subRow[tS]; 225 slice.start = (slice.start - timeBase) / 1000; 226 slice.duration /= 1000; 227 } 228 } 229 } 230 231 this.updateBounds(); 232 }, 233 234 getAllThreads: function() { 235 var threads = []; 236 for (var pid in this.processes) { 237 var process = this.processes[pid]; 238 for (var tid in process.threads) { 239 threads.push(process.threads[tid]); 240 } 241 } 242 return threads; 243 } 244 245 }; 246 247 return { 248 TimelineSlice: TimelineSlice, 249 TimelineThread: TimelineThread, 250 TimelineProcess: TimelineProcess, 251 TimelineModel: TimelineModel 252 }; 253}); 254