| OLD | NEW |
| (Empty) |
| 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 */ | |
| 22 cr.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.didNotFinish = false; | |
| 39 this.subSlices = []; | |
| 40 } | |
| 41 | |
| 42 TimelineSlice.prototype = { | |
| 43 selected: false, | |
| 44 | |
| 45 duration: undefined, | |
| 46 | |
| 47 get end() { | |
| 48 return this.start + this.duration; | |
| 49 } | |
| 50 }; | |
| 51 | |
| 52 /** | |
| 53 * A TimelineThread stores all the trace events collected for a particular | |
| 54 * thread. We organize the slices on a thread by "subrows," where subrow 0 | |
| 55 * has all the root slices, subrow 1 those nested 1 deep, and so on. There | |
| 56 * is also a set of non-nested subrows. | |
| 57 * | |
| 58 * @constructor | |
| 59 */ | |
| 60 function TimelineThread(parent, tid) { | |
| 61 this.parent = parent; | |
| 62 this.tid = tid; | |
| 63 this.subRows = [[]]; | |
| 64 this.nonNestedSubRows = []; | |
| 65 } | |
| 66 | |
| 67 TimelineThread.prototype = { | |
| 68 getSubrow: function(i) { | |
| 69 while (i >= this.subRows.length) | |
| 70 this.subRows.push([]); | |
| 71 return this.subRows[i]; | |
| 72 }, | |
| 73 | |
| 74 addNonNestedSlice: function(slice) { | |
| 75 for (var i = 0; i < this.nonNestedSubRows.length; i++) { | |
| 76 var currSubRow = this.nonNestedSubRows[i]; | |
| 77 var lastSlice = currSubRow[currSubRow.length - 1]; | |
| 78 if (slice.start >= lastSlice.start + lastSlice.duration) { | |
| 79 currSubRow.push(slice); | |
| 80 return; | |
| 81 } | |
| 82 } | |
| 83 this.nonNestedSubRows.push([slice]); | |
| 84 }, | |
| 85 | |
| 86 updateBounds: function() { | |
| 87 var slices = this.subRows[0]; | |
| 88 if (slices.length != 0) { | |
| 89 this.minTimestamp = slices[0].start; | |
| 90 this.maxTimestamp = slices[slices.length - 1].end; | |
| 91 } else { | |
| 92 this.minTimestamp = undefined; | |
| 93 this.maxTimestamp = undefined; | |
| 94 } | |
| 95 } | |
| 96 | |
| 97 }; | |
| 98 | |
| 99 /** | |
| 100 * The TimelineProcess represents a single process in the | |
| 101 * trace. Right now, we keep this around purely for bookkeeping | |
| 102 * reasons. | |
| 103 * @constructor | |
| 104 */ | |
| 105 function TimelineProcess(pid) { | |
| 106 this.pid = pid; | |
| 107 this.threads = {}; | |
| 108 }; | |
| 109 | |
| 110 TimelineProcess.prototype = { | |
| 111 getThread: function(tid) { | |
| 112 if (!this.threads[tid]) | |
| 113 this.threads[tid] = new TimelineThread(this, tid); | |
| 114 return this.threads[tid]; | |
| 115 } | |
| 116 }; | |
| 117 | |
| 118 /** | |
| 119 * Builds a model from an array of TraceEvent objects. | |
| 120 * @param {Array} events An array of TraceEvents created by | |
| 121 * TraceEvent.ToJSON(). | |
| 122 * @constructor | |
| 123 */ | |
| 124 function TimelineModel(events) { | |
| 125 this.processes = {}; | |
| 126 | |
| 127 if (events) | |
| 128 this.importEvents(events); | |
| 129 } | |
| 130 | |
| 131 TimelineModel.prototype = { | |
| 132 __proto__: cr.EventTarget.prototype, | |
| 133 | |
| 134 getProcess: function(pid) { | |
| 135 if (!this.processes[pid]) | |
| 136 this.processes[pid] = new TimelineProcess(pid); | |
| 137 return this.processes[pid]; | |
| 138 }, | |
| 139 | |
| 140 /** | |
| 141 * The import takes an array of json-ified TraceEvents and adds them into | |
| 142 * the TimelineModel as processes, threads, and slices. | |
| 143 */ | |
| 144 importEvents: function(events) { | |
| 145 // A ptid is a pid and tid joined together x:y fashion, eg 1024:130 | |
| 146 // The ptid is a unique key for a thread in the trace. | |
| 147 | |
| 148 | |
| 149 // Threadstate | |
| 150 const numColorIds = 30; | |
| 151 function ThreadState(tid) { | |
| 152 this.openSlices = []; | |
| 153 this.openNonNestedSlices = {}; | |
| 154 } | |
| 155 var threadStateByPTID = {}; | |
| 156 | |
| 157 var nameToColorMap = {}; | |
| 158 function getColor(name) { | |
| 159 if (!(name in nameToColorMap)) { | |
| 160 // Compute a simplistic hashcode of the string so we get consistent | |
| 161 // coloring across traces. | |
| 162 var hash = 0; | |
| 163 for (var i = 0; i < name.length; ++i) | |
| 164 hash = (hash + 37 * hash + 11 * name.charCodeAt(i)) % 0xFFFFFFFF; | |
| 165 nameToColorMap[name] = hash % numColorIds; | |
| 166 } | |
| 167 return nameToColorMap[name]; | |
| 168 } | |
| 169 | |
| 170 var self = this; | |
| 171 | |
| 172 /** | |
| 173 * Helper to process a 'begin' event (e.g. initiate a slice). | |
| 174 * @param {ThreadState} state Thread state (holds slices). | |
| 175 * @param {Object} event The current trace event. | |
| 176 */ | |
| 177 function processBegin(state, event) { | |
| 178 var colorId = getColor(event.name); | |
| 179 var slice = | |
| 180 { index: eI, | |
| 181 slice: new TimelineSlice(event.name, colorId, event.ts, | |
| 182 event.args) }; | |
| 183 if (event.args['ui-nest'] === '0') { | |
| 184 var sliceID = event.name; | |
| 185 for (var x in event.args) | |
| 186 sliceID += ';' + event.args[x]; | |
| 187 if (state.openNonNestedSlices[sliceID]) | |
| 188 console.log('Event ' + sliceID + ' already open.'); | |
| 189 state.openNonNestedSlices[sliceID] = slice; | |
| 190 } else { | |
| 191 state.openSlices.push(slice); | |
| 192 } | |
| 193 } | |
| 194 | |
| 195 /** | |
| 196 * Helper to process an 'end' event (e.g. close a slice). | |
| 197 * @param {ThreadState} state Thread state (holds slices). | |
| 198 * @param {Object} event The current trace event. | |
| 199 */ | |
| 200 function processEnd(state, event) { | |
| 201 if (event.args['ui-nest'] === '0') { | |
| 202 var sliceID = event.name; | |
| 203 for (var x in event.args) | |
| 204 sliceID += ';' + event.args[x]; | |
| 205 var slice = state.openNonNestedSlices[sliceID]; | |
| 206 if (!slice) | |
| 207 return; | |
| 208 slice.slice.duration = event.ts - slice.slice.start; | |
| 209 | |
| 210 // Store the slice in a non-nested subrow. | |
| 211 var thread = self.getProcess(event.pid).getThread(event.tid); | |
| 212 thread.addNonNestedSlice(slice.slice); | |
| 213 delete state.openNonNestedSlices[name]; | |
| 214 } else { | |
| 215 if (state.openSlices.length == 0) { | |
| 216 // Ignore E events that are unmatched. | |
| 217 return; | |
| 218 } | |
| 219 var slice = state.openSlices.pop().slice; | |
| 220 slice.duration = event.ts - slice.start; | |
| 221 | |
| 222 // Store the slice on the correct subrow. | |
| 223 var thread = self.getProcess(event.pid).getThread(event.tid); | |
| 224 var subRowIndex = state.openSlices.length; | |
| 225 thread.getSubrow(subRowIndex).push(slice); | |
| 226 | |
| 227 // Add the slice to the subSlices array of its parent. | |
| 228 if (state.openSlices.length) { | |
| 229 var parentSlice = state.openSlices[state.openSlices.length - 1]; | |
| 230 parentSlice.slice.subSlices.push(slice); | |
| 231 } | |
| 232 } | |
| 233 } | |
| 234 | |
| 235 // Walk through events | |
| 236 for (var eI = 0; eI < events.length; eI++) { | |
| 237 var event = events[eI]; | |
| 238 var ptid = event.pid + ':' + event.tid; | |
| 239 | |
| 240 if (!(ptid in threadStateByPTID)) | |
| 241 threadStateByPTID[ptid] = new ThreadState(); | |
| 242 var state = threadStateByPTID[ptid]; | |
| 243 | |
| 244 if (event.ph == 'B') { | |
| 245 processBegin(state, event); | |
| 246 } else if (event.ph == 'E') { | |
| 247 processEnd(state, event); | |
| 248 } else if (event.ph == 'I') { | |
| 249 // Treat an Instant event as a duration 0 slice. | |
| 250 // TimelineSliceTrack's redraw() knows how to handle this. | |
| 251 processBegin(state, event); | |
| 252 processEnd(state, event); | |
| 253 } else { | |
| 254 throw new Error('Unrecognized event phase: ' + event.ph + | |
| 255 '(' + event.name + ')'); | |
| 256 } | |
| 257 } | |
| 258 | |
| 259 this.updateBounds(); | |
| 260 | |
| 261 // Add end events for any events that are still on the stack. These | |
| 262 // are events that were still open when trace was ended, and can often | |
| 263 // indicate deadlock behavior. | |
| 264 for (var ptid in threadStateByPTID) { | |
| 265 var state = threadStateByPTID[ptid]; | |
| 266 while (state.openSlices.length > 0) { | |
| 267 var slice = state.openSlices.pop(); | |
| 268 slice.slice.duration = this.maxTimestamp - slice.slice.start; | |
| 269 slice.slice.didNotFinish = true; | |
| 270 var event = events[slice.index]; | |
| 271 | |
| 272 // Store the slice on the correct subrow. | |
| 273 var thread = this.getProcess(event.pid).getThread(event.tid); | |
| 274 var subRowIndex = state.openSlices.length; | |
| 275 thread.getSubrow(subRowIndex).push(slice.slice); | |
| 276 | |
| 277 // Add the slice to the subSlices array of its parent. | |
| 278 if (state.openSlices.length) { | |
| 279 var parentSlice = state.openSlices[state.openSlices.length - 1]; | |
| 280 parentSlice.slice.subSlices.push(slice.slice); | |
| 281 } | |
| 282 } | |
| 283 } | |
| 284 | |
| 285 this.shiftWorldToMicroseconds(); | |
| 286 | |
| 287 var boost = (this.maxTimestamp - this.minTimestamp) * 0.15; | |
| 288 this.minTimestamp = this.minTimestamp - boost; | |
| 289 this.maxTimestamp = this.maxTimestamp + boost; | |
| 290 }, | |
| 291 | |
| 292 updateBounds: function() { | |
| 293 var wmin = Infinity; | |
| 294 var wmax = -wmin; | |
| 295 var threads = this.getAllThreads(); | |
| 296 for (var tI = 0; tI < threads.length; tI++) { | |
| 297 var thread = threads[tI]; | |
| 298 thread.updateBounds(); | |
| 299 if (thread.minTimestamp && thread.maxTimestamp) { | |
| 300 wmin = Math.min(wmin, thread.minTimestamp); | |
| 301 wmax = Math.max(wmax, thread.maxTimestamp); | |
| 302 } | |
| 303 } | |
| 304 this.minTimestamp = wmin; | |
| 305 this.maxTimestamp = wmax; | |
| 306 }, | |
| 307 | |
| 308 shiftWorldToMicroseconds: function() { | |
| 309 var timeBase = this.minTimestamp; | |
| 310 var threads = this.getAllThreads(); | |
| 311 for (var tI = 0; tI < threads.length; tI++) { | |
| 312 var thread = threads[tI]; | |
| 313 var shiftSubRow = function(subRow) { | |
| 314 for (var tS = 0; tS < subRow.length; tS++) { | |
| 315 var slice = subRow[tS]; | |
| 316 slice.start = (slice.start - timeBase) / 1000; | |
| 317 slice.duration /= 1000; | |
| 318 } | |
| 319 }; | |
| 320 for (var tSR = 0; tSR < thread.subRows.length; tSR++) { | |
| 321 shiftSubRow(thread.subRows[tSR]); | |
| 322 } | |
| 323 for (var tSR = 0; tSR < thread.nonNestedSubRows.length; tSR++) { | |
| 324 shiftSubRow(thread.nonNestedSubRows[tSR]); | |
| 325 } | |
| 326 } | |
| 327 | |
| 328 this.updateBounds(); | |
| 329 }, | |
| 330 | |
| 331 getAllThreads: function() { | |
| 332 var threads = []; | |
| 333 for (var pid in this.processes) { | |
| 334 var process = this.processes[pid]; | |
| 335 for (var tid in process.threads) { | |
| 336 threads.push(process.threads[tid]); | |
| 337 } | |
| 338 } | |
| 339 return threads; | |
| 340 } | |
| 341 | |
| 342 }; | |
| 343 | |
| 344 return { | |
| 345 TimelineSlice: TimelineSlice, | |
| 346 TimelineThread: TimelineThread, | |
| 347 TimelineProcess: TimelineProcess, | |
| 348 TimelineModel: TimelineModel | |
| 349 }; | |
| 350 }); | |
| OLD | NEW |