| 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 /** | |
| 69 * Name of the thread, if present. | |
| 70 */ | |
| 71 name: undefined, | |
| 72 | |
| 73 getSubrow: function(i) { | |
| 74 while (i >= this.subRows.length) | |
| 75 this.subRows.push([]); | |
| 76 return this.subRows[i]; | |
| 77 }, | |
| 78 | |
| 79 addNonNestedSlice: function(slice) { | |
| 80 for (var i = 0; i < this.nonNestedSubRows.length; i++) { | |
| 81 var currSubRow = this.nonNestedSubRows[i]; | |
| 82 var lastSlice = currSubRow[currSubRow.length - 1]; | |
| 83 if (slice.start >= lastSlice.start + lastSlice.duration) { | |
| 84 currSubRow.push(slice); | |
| 85 return; | |
| 86 } | |
| 87 } | |
| 88 this.nonNestedSubRows.push([slice]); | |
| 89 }, | |
| 90 | |
| 91 /** | |
| 92 * Updates the minTimestamp and maxTimestamp fields based on the | |
| 93 * current slices and nonNestedSubRows attached to the thread. | |
| 94 */ | |
| 95 updateBounds: function() { | |
| 96 var values = []; | |
| 97 var slices; | |
| 98 if (this.subRows[0].length != 0) { | |
| 99 slices = this.subRows[0]; | |
| 100 values.push(slices[0].start); | |
| 101 values.push(slices[slices.length - 1].end); | |
| 102 } | |
| 103 for (var i = 0; i < this.nonNestedSubRows.length; ++i) { | |
| 104 slices = this.nonNestedSubRows[i]; | |
| 105 values.push(slices[0].start); | |
| 106 values.push(slices[slices.length - 1].end); | |
| 107 } | |
| 108 if (values.length) { | |
| 109 this.minTimestamp = Math.min.apply(Math, values); | |
| 110 this.maxTimestamp = Math.max.apply(Math, values); | |
| 111 } else { | |
| 112 this.minTimestamp = undefined; | |
| 113 this.maxTimestamp = undefined; | |
| 114 } | |
| 115 } | |
| 116 | |
| 117 }; | |
| 118 | |
| 119 /** | |
| 120 * Comparison between threads that orders first by pid, | |
| 121 * then by names, then by tid. | |
| 122 */ | |
| 123 TimelineThread.compare = function(x,y) { | |
| 124 if(x.parent.pid != y.parent.pid) { | |
| 125 return x.parent.pid - y.parent.pid; | |
| 126 } | |
| 127 | |
| 128 if (x.name && y.name) { | |
| 129 var tmp = x.name.localeCompare(y.name); | |
| 130 if (tmp == 0) | |
| 131 return x.tid - y.tid; | |
| 132 return tmp; | |
| 133 } else if (x.name) { | |
| 134 return -1; | |
| 135 } else if (y.name){ | |
| 136 return 1; | |
| 137 } else { | |
| 138 return x.tid - y.tid; | |
| 139 } | |
| 140 }; | |
| 141 | |
| 142 | |
| 143 /** | |
| 144 * The TimelineProcess represents a single process in the | |
| 145 * trace. Right now, we keep this around purely for bookkeeping | |
| 146 * reasons. | |
| 147 * @constructor | |
| 148 */ | |
| 149 function TimelineProcess(pid) { | |
| 150 this.pid = pid; | |
| 151 this.threads = {}; | |
| 152 }; | |
| 153 | |
| 154 TimelineProcess.prototype = { | |
| 155 getThread: function(tid) { | |
| 156 if (!this.threads[tid]) | |
| 157 this.threads[tid] = new TimelineThread(this, tid); | |
| 158 return this.threads[tid]; | |
| 159 } | |
| 160 }; | |
| 161 | |
| 162 /** | |
| 163 * Builds a model from an array of TraceEvent objects. | |
| 164 * @param {Array} events An array of TraceEvents created by | |
| 165 * TraceEvent.ToJSON(). | |
| 166 * @constructor | |
| 167 */ | |
| 168 function TimelineModel(events) { | |
| 169 this.processes = {}; | |
| 170 this.importErrors = []; | |
| 171 | |
| 172 if (events) | |
| 173 this.importEvents(events); | |
| 174 } | |
| 175 | |
| 176 TimelineModel.prototype = { | |
| 177 __proto__: cr.EventTarget.prototype, | |
| 178 | |
| 179 getProcess: function(pid) { | |
| 180 if (!this.processes[pid]) | |
| 181 this.processes[pid] = new TimelineProcess(pid); | |
| 182 return this.processes[pid]; | |
| 183 }, | |
| 184 | |
| 185 /** | |
| 186 * The import takes an array of json-ified TraceEvents and adds them into | |
| 187 * the TimelineModel as processes, threads, and slices. | |
| 188 */ | |
| 189 importEvents: function(events) { | |
| 190 // A ptid is a pid and tid joined together x:y fashion, eg 1024:130 | |
| 191 // The ptid is a unique key for a thread in the trace. | |
| 192 this.importErrors = []; | |
| 193 | |
| 194 // Threadstate | |
| 195 const numColorIds = 30; | |
| 196 function ThreadState(tid) { | |
| 197 this.openSlices = []; | |
| 198 this.openNonNestedSlices = {}; | |
| 199 } | |
| 200 var threadStateByPTID = {}; | |
| 201 | |
| 202 var nameToColorMap = {}; | |
| 203 function getColor(name) { | |
| 204 if (!(name in nameToColorMap)) { | |
| 205 // Compute a simplistic hashcode of the string so we get consistent | |
| 206 // coloring across traces. | |
| 207 var hash = 0; | |
| 208 for (var i = 0; i < name.length; ++i) | |
| 209 hash = (hash + 37 * hash + 11 * name.charCodeAt(i)) % 0xFFFFFFFF; | |
| 210 nameToColorMap[name] = hash % numColorIds; | |
| 211 } | |
| 212 return nameToColorMap[name]; | |
| 213 } | |
| 214 | |
| 215 var self = this; | |
| 216 | |
| 217 /** | |
| 218 * Helper to process a 'begin' event (e.g. initiate a slice). | |
| 219 * @param {ThreadState} state Thread state (holds slices). | |
| 220 * @param {Object} event The current trace event. | |
| 221 */ | |
| 222 function processBegin(state, event) { | |
| 223 var colorId = getColor(event.name); | |
| 224 var slice = | |
| 225 { index: eI, | |
| 226 slice: new TimelineSlice(event.name, colorId, event.ts, | |
| 227 event.args) }; | |
| 228 if (event.args['ui-nest'] === '0') { | |
| 229 var sliceID = event.name; | |
| 230 for (var x in event.args) | |
| 231 sliceID += ';' + event.args[x]; | |
| 232 if (state.openNonNestedSlices[sliceID]) | |
| 233 this.importErrors.push('Event ' + sliceID + ' already open.'); | |
| 234 state.openNonNestedSlices[sliceID] = slice; | |
| 235 } else { | |
| 236 state.openSlices.push(slice); | |
| 237 } | |
| 238 } | |
| 239 | |
| 240 /** | |
| 241 * Helper to process an 'end' event (e.g. close a slice). | |
| 242 * @param {ThreadState} state Thread state (holds slices). | |
| 243 * @param {Object} event The current trace event. | |
| 244 */ | |
| 245 function processEnd(state, event) { | |
| 246 if (event.args['ui-nest'] === '0') { | |
| 247 var sliceID = event.name; | |
| 248 for (var x in event.args) | |
| 249 sliceID += ';' + event.args[x]; | |
| 250 var slice = state.openNonNestedSlices[sliceID]; | |
| 251 if (!slice) | |
| 252 return; | |
| 253 slice.slice.duration = event.ts - slice.slice.start; | |
| 254 | |
| 255 // Store the slice in a non-nested subrow. | |
| 256 var thread = self.getProcess(event.pid).getThread(event.tid); | |
| 257 thread.addNonNestedSlice(slice.slice); | |
| 258 delete state.openNonNestedSlices[name]; | |
| 259 } else { | |
| 260 if (state.openSlices.length == 0) { | |
| 261 // Ignore E events that are unmatched. | |
| 262 return; | |
| 263 } | |
| 264 var slice = state.openSlices.pop().slice; | |
| 265 slice.duration = event.ts - slice.start; | |
| 266 | |
| 267 // Store the slice on the correct subrow. | |
| 268 var thread = self.getProcess(event.pid).getThread(event.tid); | |
| 269 var subRowIndex = state.openSlices.length; | |
| 270 thread.getSubrow(subRowIndex).push(slice); | |
| 271 | |
| 272 // Add the slice to the subSlices array of its parent. | |
| 273 if (state.openSlices.length) { | |
| 274 var parentSlice = state.openSlices[state.openSlices.length - 1]; | |
| 275 parentSlice.slice.subSlices.push(slice); | |
| 276 } | |
| 277 } | |
| 278 } | |
| 279 | |
| 280 // Walk through events | |
| 281 for (var eI = 0; eI < events.length; eI++) { | |
| 282 var event = events[eI]; | |
| 283 var ptid = event.pid + ':' + event.tid; | |
| 284 | |
| 285 if (!(ptid in threadStateByPTID)) | |
| 286 threadStateByPTID[ptid] = new ThreadState(); | |
| 287 var state = threadStateByPTID[ptid]; | |
| 288 | |
| 289 if (event.ph == 'B') { | |
| 290 processBegin(state, event); | |
| 291 } else if (event.ph == 'E') { | |
| 292 processEnd(state, event); | |
| 293 } else if (event.ph == 'I') { | |
| 294 // Treat an Instant event as a duration 0 slice. | |
| 295 // TimelineSliceTrack's redraw() knows how to handle this. | |
| 296 processBegin(state, event); | |
| 297 processEnd(state, event); | |
| 298 } else if (event.ph == 'M') { | |
| 299 if (event.name == 'thread_name') { | |
| 300 var thread = this.getProcess(event.pid).getThread(event.tid); | |
| 301 thread.name = event.args.name; | |
| 302 } else { | |
| 303 this.importErrors.push('Unrecognized metadata name: ' + event.name); | |
| 304 } | |
| 305 } else { | |
| 306 this.importErrors.push('Unrecognized event phase: ' + event.ph + | |
| 307 '(' + event.name + ')'); | |
| 308 } | |
| 309 } | |
| 310 this.pruneEmptyThreads(); | |
| 311 this.updateBounds(); | |
| 312 | |
| 313 // Add end events for any events that are still on the stack. These | |
| 314 // are events that were still open when trace was ended, and can often | |
| 315 // indicate deadlock behavior. | |
| 316 for (var ptid in threadStateByPTID) { | |
| 317 var state = threadStateByPTID[ptid]; | |
| 318 while (state.openSlices.length > 0) { | |
| 319 var slice = state.openSlices.pop(); | |
| 320 slice.slice.duration = this.maxTimestamp - slice.slice.start; | |
| 321 slice.slice.didNotFinish = true; | |
| 322 var event = events[slice.index]; | |
| 323 | |
| 324 // Store the slice on the correct subrow. | |
| 325 var thread = this.getProcess(event.pid).getThread(event.tid); | |
| 326 var subRowIndex = state.openSlices.length; | |
| 327 thread.getSubrow(subRowIndex).push(slice.slice); | |
| 328 | |
| 329 // Add the slice to the subSlices array of its parent. | |
| 330 if (state.openSlices.length) { | |
| 331 var parentSlice = state.openSlices[state.openSlices.length - 1]; | |
| 332 parentSlice.slice.subSlices.push(slice.slice); | |
| 333 } | |
| 334 } | |
| 335 } | |
| 336 | |
| 337 this.shiftWorldToMicroseconds(); | |
| 338 | |
| 339 var boost = (this.maxTimestamp - this.minTimestamp) * 0.15; | |
| 340 this.minTimestamp = this.minTimestamp - boost; | |
| 341 this.maxTimestamp = this.maxTimestamp + boost; | |
| 342 }, | |
| 343 | |
| 344 /** | |
| 345 * Removes threads from the model that have no subrows. | |
| 346 */ | |
| 347 pruneEmptyThreads: function() { | |
| 348 for (var pid in this.processes) { | |
| 349 var process = this.processes[pid]; | |
| 350 var prunedThreads = []; | |
| 351 for (var tid in process.threads) { | |
| 352 var thread = process.threads[tid]; | |
| 353 if (thread.subRows[0].length || thread.nonNestedSubRows.legnth) | |
| 354 prunedThreads.push(thread); | |
| 355 } | |
| 356 process.threads = prunedThreads; | |
| 357 } | |
| 358 }, | |
| 359 | |
| 360 updateBounds: function() { | |
| 361 var wmin = Infinity; | |
| 362 var wmax = -wmin; | |
| 363 var threads = this.getAllThreads(); | |
| 364 for (var tI = 0; tI < threads.length; tI++) { | |
| 365 var thread = threads[tI]; | |
| 366 thread.updateBounds(); | |
| 367 if (thread.minTimestamp != undefined && | |
| 368 thread.maxTimestamp != undefined) { | |
| 369 wmin = Math.min(wmin, thread.minTimestamp); | |
| 370 wmax = Math.max(wmax, thread.maxTimestamp); | |
| 371 } | |
| 372 } | |
| 373 this.minTimestamp = wmin; | |
| 374 this.maxTimestamp = wmax; | |
| 375 }, | |
| 376 | |
| 377 shiftWorldToMicroseconds: function() { | |
| 378 var timeBase = this.minTimestamp; | |
| 379 var threads = this.getAllThreads(); | |
| 380 for (var tI = 0; tI < threads.length; tI++) { | |
| 381 var thread = threads[tI]; | |
| 382 var shiftSubRow = function(subRow) { | |
| 383 for (var tS = 0; tS < subRow.length; tS++) { | |
| 384 var slice = subRow[tS]; | |
| 385 slice.start = (slice.start - timeBase) / 1000; | |
| 386 slice.duration /= 1000; | |
| 387 } | |
| 388 }; | |
| 389 for (var tSR = 0; tSR < thread.subRows.length; tSR++) { | |
| 390 shiftSubRow(thread.subRows[tSR]); | |
| 391 } | |
| 392 for (var tSR = 0; tSR < thread.nonNestedSubRows.length; tSR++) { | |
| 393 shiftSubRow(thread.nonNestedSubRows[tSR]); | |
| 394 } | |
| 395 } | |
| 396 | |
| 397 this.updateBounds(); | |
| 398 }, | |
| 399 | |
| 400 getAllThreads: function() { | |
| 401 var threads = []; | |
| 402 for (var pid in this.processes) { | |
| 403 var process = this.processes[pid]; | |
| 404 for (var tid in process.threads) { | |
| 405 threads.push(process.threads[tid]); | |
| 406 } | |
| 407 } | |
| 408 return threads; | |
| 409 } | |
| 410 | |
| 411 }; | |
| 412 | |
| 413 return { | |
| 414 TimelineSlice: TimelineSlice, | |
| 415 TimelineThread: TimelineThread, | |
| 416 TimelineProcess: TimelineProcess, | |
| 417 TimelineModel: TimelineModel | |
| 418 }; | |
| 419 }); | |
| OLD | NEW |