| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 | 5 |
| 6 /** | 6 /** |
| 7 * @fileoverview TimelineModel is a parsed representation of the | 7 * @fileoverview TimelineModel is a parsed representation of the |
| 8 * TraceEvents obtained from base/trace_event in which the begin-end | 8 * TraceEvents obtained from base/trace_event in which the begin-end |
| 9 * tokens are converted into a hierarchy of processes, threads, | 9 * tokens are converted into a hierarchy of processes, threads, |
| 10 * subrows, and slices. | 10 * subrows, and slices. |
| 11 * | 11 * |
| 12 * The building block of the model is a slice. A slice is roughly | 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 | 13 * equivalent to function call executing on a specific thread. As a |
| 14 * result, slices may have one or more subslices. | 14 * result, slices may have one or more subslices. |
| 15 * | 15 * |
| 16 * A thread contains one or more subrows of slices. Row 0 corresponds to | 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 | 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 | 18 * are nested 1 deep in the stack, and so on. We use these subrows to draw |
| 19 * nesting tasks. | 19 * nesting tasks. |
| 20 * | 20 * |
| 21 */ | 21 */ |
| 22 cr.define('tracing', function() { | 22 cr.define('tracing', function() { |
| 23 /** | 23 /** |
| 24 * A TimelineSlice represents an interval of time on a given resource plus | 24 * A TimelineSlice represents an interval of time plus parameters associated |
| 25 * parameters associated with that interval. | 25 * with that interval. |
| 26 * | |
| 27 * A slice is typically associated with a specific trace event pair on a | |
| 28 * specific thread. | |
| 29 * For example, | |
| 30 * TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms | |
| 31 * TRACE_EVENT_END() at time=0.3ms | |
| 32 * This results in a single timeline slice from 0.1 with duration 0.2 on a | |
| 33 * specific thread. | |
| 34 * | |
| 35 * A slice can also be an interval of time on a Cpu on a TimelineCpu. | |
| 36 * | 26 * |
| 37 * All time units are stored in milliseconds. | 27 * All time units are stored in milliseconds. |
| 38 * @constructor | 28 * @constructor |
| 39 */ | 29 */ |
| 40 function TimelineSlice(title, colorId, start, args, opt_duration) { | 30 function TimelineSlice(title, colorId, start, args, opt_duration) { |
| 41 this.title = title; | 31 this.title = title; |
| 42 this.start = start; | 32 this.start = start; |
| 43 this.colorId = colorId; | 33 this.colorId = colorId; |
| 44 this.args = args; | 34 this.args = args; |
| 45 this.didNotFinish = false; | 35 this.didNotFinish = false; |
| 46 this.subSlices = []; | |
| 47 if (opt_duration !== undefined) | 36 if (opt_duration !== undefined) |
| 48 this.duration = opt_duration; | 37 this.duration = opt_duration; |
| 49 } | 38 } |
| 50 | 39 |
| 51 TimelineSlice.prototype = { | 40 TimelineSlice.prototype = { |
| 52 selected: false, | 41 selected: false, |
| 53 | 42 |
| 54 duration: undefined, | 43 duration: undefined, |
| 55 | 44 |
| 56 get end() { | 45 get end() { |
| 57 return this.start + this.duration; | 46 return this.start + this.duration; |
| 58 } | 47 } |
| 59 }; | 48 }; |
| 60 | 49 |
| 61 /** | 50 /** |
| 51 * A TimelineThreadSlice represents an interval of time on a thread resource |
| 52 * with associated nestinged slice information. |
| 53 * |
| 54 * ThreadSlices are typically associated with a specific trace event pair on a |
| 55 * specific thread. |
| 56 * For example, |
| 57 * TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms |
| 58 * TRACE_EVENT_END0() at time=0.3ms |
| 59 * This results in a single timeline slice from 0.1 with duration 0.2 on a |
| 60 * specific thread. |
| 61 * |
| 62 * @constructor |
| 63 */ |
| 64 function TimelineThreadSlice(title, colorId, start, args, opt_duration) { |
| 65 TimelineSlice.call(this, title, colorId, start, args, opt_duration); |
| 66 this.subSlices = []; |
| 67 } |
| 68 |
| 69 TimelineThreadSlice.prototype = { |
| 70 __proto__: TimelineSlice.prototype |
| 71 }; |
| 72 |
| 73 /** |
| 74 * A TimelineAsyncSlice represents an interval of time during which an |
| 75 * asynchronous operation is in progress. An AsyncSlice consumes no CPU time |
| 76 * itself and so is only associated with Threads at its start and end point. |
| 77 * |
| 78 * @constructor |
| 79 */ |
| 80 function TimelineAsyncSlice(title, colorId, start, args) { |
| 81 TimelineSlice.call(this, title, colorId, start, args); |
| 82 }; |
| 83 |
| 84 TimelineAsyncSlice.prototype = { |
| 85 __proto__: TimelineSlice.prototype, |
| 86 |
| 87 id: undefined, |
| 88 |
| 89 startThread: undefined, |
| 90 |
| 91 endThread: undefined |
| 92 }; |
| 93 |
| 94 /** |
| 62 * A TimelineThread stores all the trace events collected for a particular | 95 * A TimelineThread stores all the trace events collected for a particular |
| 63 * thread. We organize the slices on a thread by "subrows," where subrow 0 | 96 * thread. We organize the synchronous slices on a thread by "subrows," where |
| 64 * has all the root slices, subrow 1 those nested 1 deep, and so on. There | 97 * subrow 0 has all the root slices, subrow 1 those nested 1 deep, and so on. |
| 65 * is also a set of non-nested subrows. | 98 * The asynchronous slices are stored in an TimelineAsyncSliceGroup object. |
| 99 * |
| 100 * The slices stored on a TimelineThread should be instances of |
| 101 * TimelineThreadSlice. |
| 66 * | 102 * |
| 67 * @constructor | 103 * @constructor |
| 68 */ | 104 */ |
| 69 function TimelineThread(parent, tid) { | 105 function TimelineThread(parent, tid) { |
| 106 if (!parent) |
| 107 throw 'Parent must be provided.'; |
| 70 this.parent = parent; | 108 this.parent = parent; |
| 71 this.tid = tid; | 109 this.tid = tid; |
| 72 this.subRows = [[]]; | 110 this.subRows = [[]]; |
| 73 this.nonNestedSubRows = []; | 111 this.asyncSlices = new TimelineAsyncSliceGroup(this.ptid); |
| 112 } |
| 113 |
| 114 var ptidMap = {}; |
| 115 |
| 116 /** |
| 117 * @return {String} A string that can be used as a unique key for a specific |
| 118 * thread within a process. |
| 119 */ |
| 120 TimelineThread.getPTIDFromPidAndTid = function(pid, tid) { |
| 121 if (!ptidMap[pid]) |
| 122 ptidMap[pid] = {}; |
| 123 if (!ptidMap[pid][tid]) |
| 124 ptidMap[pid][tid] = pid + ':' + tid; |
| 125 return ptidMap[pid][tid]; |
| 74 } | 126 } |
| 75 | 127 |
| 76 TimelineThread.prototype = { | 128 TimelineThread.prototype = { |
| 77 /** | 129 /** |
| 78 * Name of the thread, if present. | 130 * Name of the thread, if present. |
| 79 */ | 131 */ |
| 80 name: undefined, | 132 name: undefined, |
| 81 | 133 |
| 134 /** |
| 135 * @return {string} A concatenation of the parent id and the thread's |
| 136 * tid. Can be used to uniquely identify a thread. |
| 137 */ |
| 138 get ptid() { |
| 139 return TimelineThread.getPTIDFromPidAndTid(this.tid, this.parent.pid); |
| 140 }, |
| 141 |
| 82 getSubrow: function(i) { | 142 getSubrow: function(i) { |
| 83 while (i >= this.subRows.length) | 143 while (i >= this.subRows.length) |
| 84 this.subRows.push([]); | 144 this.subRows.push([]); |
| 85 return this.subRows[i]; | 145 return this.subRows[i]; |
| 86 }, | 146 }, |
| 87 | 147 |
| 88 addNonNestedSlice: function(slice) { | 148 |
| 89 for (var i = 0; i < this.nonNestedSubRows.length; i++) { | 149 shiftSubRow_: function(subRow, amount) { |
| 90 var currSubRow = this.nonNestedSubRows[i]; | 150 for (var tS = 0; tS < subRow.length; tS++) { |
| 91 var lastSlice = currSubRow[currSubRow.length - 1]; | 151 var slice = subRow[tS]; |
| 92 if (slice.start >= lastSlice.start + lastSlice.duration) { | 152 slice.start = (slice.start + amount); |
| 93 currSubRow.push(slice); | |
| 94 return; | |
| 95 } | |
| 96 } | 153 } |
| 97 this.nonNestedSubRows.push([slice]); | 154 }, |
| 155 |
| 156 /** |
| 157 * Shifts all the timestamps inside this thread forward by the amount |
| 158 * specified. |
| 159 */ |
| 160 shiftTimestampsForward: function(amount) { |
| 161 if (this.cpuSlices) |
| 162 this.shiftSubRow_(this.cpuSlices, amount); |
| 163 |
| 164 for (var tSR = 0; tSR < this.subRows.length; tSR++) { |
| 165 this.shiftSubRow_(this.subRows[tSR], amount); |
| 166 } |
| 167 |
| 168 this.asyncSlices.shiftTimestampsForward(amount); |
| 98 }, | 169 }, |
| 99 | 170 |
| 100 /** | 171 /** |
| 101 * Updates the minTimestamp and maxTimestamp fields based on the | 172 * Updates the minTimestamp and maxTimestamp fields based on the |
| 102 * current slices and nonNestedSubRows attached to the thread. | 173 * current objects associated with the thread. |
| 103 */ | 174 */ |
| 104 updateBounds: function() { | 175 updateBounds: function() { |
| 105 var values = []; | 176 var values = []; |
| 106 var slices; | 177 var slices; |
| 107 if (this.subRows[0].length != 0) { | 178 if (this.subRows[0].length != 0) { |
| 108 slices = this.subRows[0]; | 179 slices = this.subRows[0]; |
| 109 values.push(slices[0].start); | 180 values.push(slices[0].start); |
| 110 values.push(slices[slices.length - 1].end); | 181 values.push(slices[slices.length - 1].end); |
| 111 } | 182 } |
| 112 for (var i = 0; i < this.nonNestedSubRows.length; ++i) { | 183 if (this.asyncSlices.slices.length) { |
| 113 slices = this.nonNestedSubRows[i]; | 184 this.asyncSlices.updateBounds(); |
| 114 values.push(slices[0].start); | 185 values.push(this.asyncSlices.minTimestamp); |
| 115 values.push(slices[slices.length - 1].end); | 186 values.push(this.asyncSlices.maxTimestamp); |
| 116 } | 187 } |
| 117 if (values.length) { | 188 if (values.length) { |
| 118 this.minTimestamp = Math.min.apply(Math, values); | 189 this.minTimestamp = Math.min.apply(Math, values); |
| 119 this.maxTimestamp = Math.max.apply(Math, values); | 190 this.maxTimestamp = Math.max.apply(Math, values); |
| 120 } else { | 191 } else { |
| 121 this.minTimestamp = undefined; | 192 this.minTimestamp = undefined; |
| 122 this.maxTimestamp = undefined; | 193 this.maxTimestamp = undefined; |
| 123 } | 194 } |
| 124 }, | 195 }, |
| 125 | 196 |
| 126 /** | 197 /** |
| 127 * @return {String} A user-friendly name for this thread. | 198 * @return {String} A user-friendly name for this thread. |
| 128 */ | 199 */ |
| 129 get userFriendlyName() { | 200 get userFriendlyName() { |
| 130 var tname = this.name || this.tid; | 201 var tname = this.name || this.tid; |
| 131 return this.parent.pid + ': ' + tname; | 202 return this.parent.pid + ': ' + tname; |
| 132 }, | 203 }, |
| 133 | 204 |
| 134 /** | 205 /** |
| 135 * @return {String} User friendly details about this thread. | 206 * @return {String} User friendly details about this thread. |
| 136 */ | 207 */ |
| 137 get userFriendlyDetials() { | 208 get userFriendlyDetails() { |
| 138 return 'pid: ' + this.parent.pid + | 209 return 'pid: ' + this.parent.pid + |
| 139 ', tid: ' + this.tid + | 210 ', tid: ' + this.tid + |
| 140 (this.name ? ', name: ' + this.name : ''); | 211 (this.name ? ', name: ' + this.name : ''); |
| 141 } | 212 } |
| 142 | 213 |
| 143 }; | 214 }; |
| 144 | 215 |
| 145 /** | 216 /** |
| 146 * Comparison between threads that orders first by pid, | 217 * Comparison between threads that orders first by pid, |
| 147 * then by names, then by tid. | 218 * then by names, then by tid. |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 184 | 255 |
| 185 get numSeries() { | 256 get numSeries() { |
| 186 return this.seriesNames.length; | 257 return this.seriesNames.length; |
| 187 }, | 258 }, |
| 188 | 259 |
| 189 get numSamples() { | 260 get numSamples() { |
| 190 return this.timestamps.length; | 261 return this.timestamps.length; |
| 191 }, | 262 }, |
| 192 | 263 |
| 193 /** | 264 /** |
| 265 * Shifts all the timestamps inside this counter forward by the amount |
| 266 * specified. |
| 267 */ |
| 268 shiftTimestampsForward: function(amount) { |
| 269 for (var sI = 0; sI < this.timestamps.length; sI++) |
| 270 this.timestamps[sI] = (this.timestamps[sI] + amount); |
| 271 }, |
| 272 |
| 273 /** |
| 194 * Updates the bounds for this counter based on the samples it contains. | 274 * Updates the bounds for this counter based on the samples it contains. |
| 195 */ | 275 */ |
| 196 updateBounds: function() { | 276 updateBounds: function() { |
| 197 if (this.seriesNames.length != this.seriesColors.length) | 277 if (this.seriesNames.length != this.seriesColors.length) |
| 198 throw 'seriesNames.length must match seriesColors.length'; | 278 throw 'seriesNames.length must match seriesColors.length'; |
| 199 if (this.numSeries * this.numSamples != this.samples.length) | 279 if (this.numSeries * this.numSamples != this.samples.length) |
| 200 throw 'samples.length must be a multiple of numSamples.'; | 280 throw 'samples.length must be a multiple of numSamples.'; |
| 201 | 281 |
| 202 this.totals = []; | 282 this.totals = []; |
| 203 if (this.samples.length == 0) { | 283 if (this.samples.length == 0) { |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 253 TimelineProcess.prototype = { | 333 TimelineProcess.prototype = { |
| 254 get numThreads() { | 334 get numThreads() { |
| 255 var n = 0; | 335 var n = 0; |
| 256 for (var p in this.threads) { | 336 for (var p in this.threads) { |
| 257 n++; | 337 n++; |
| 258 } | 338 } |
| 259 return n; | 339 return n; |
| 260 }, | 340 }, |
| 261 | 341 |
| 262 /** | 342 /** |
| 343 * Shifts all the timestamps inside this process forward by the amount |
| 344 * specified. |
| 345 */ |
| 346 shiftTimestampsForward: function(amount) { |
| 347 for (var tid in this.threads) |
| 348 this.threads[tid].shiftTimestampsForward(amount); |
| 349 for (var id in this.counters) |
| 350 this.counters[id].shiftTimestampsForward(amount); |
| 351 }, |
| 352 |
| 353 /** |
| 263 * @return {TimlineThread} The thread identified by tid on this process, | 354 * @return {TimlineThread} The thread identified by tid on this process, |
| 264 * creating it if it doesn't exist. | 355 * creating it if it doesn't exist. |
| 265 */ | 356 */ |
| 266 getOrCreateThread: function(tid) { | 357 getOrCreateThread: function(tid) { |
| 267 if (!this.threads[tid]) | 358 if (!this.threads[tid]) |
| 268 this.threads[tid] = new TimelineThread(this, tid); | 359 this.threads[tid] = new TimelineThread(this, tid); |
| 269 return this.threads[tid]; | 360 return this.threads[tid]; |
| 270 }, | 361 }, |
| 271 | 362 |
| 272 /** | 363 /** |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 308 if (cat.length) | 399 if (cat.length) |
| 309 id = cat + '.' + name; | 400 id = cat + '.' + name; |
| 310 else | 401 else |
| 311 id = name; | 402 id = name; |
| 312 if (!this.counters[id]) | 403 if (!this.counters[id]) |
| 313 this.counters[id] = new TimelineCounter(this, id, name); | 404 this.counters[id] = new TimelineCounter(this, id, name); |
| 314 return this.counters[id]; | 405 return this.counters[id]; |
| 315 }, | 406 }, |
| 316 | 407 |
| 317 /** | 408 /** |
| 409 * Shifts all the timestamps inside this CPU forward by the amount |
| 410 * specified. |
| 411 */ |
| 412 shiftTimestampsForward: function(amount) { |
| 413 for (var sI = 0; sI < this.slices.length; sI++) |
| 414 this.slices[sI].start = (this.slices[sI].start + amount); |
| 415 for (var id in this.counters) |
| 416 this.counters[id].shiftTimestampsForward(amount); |
| 417 }, |
| 418 |
| 419 /** |
| 318 * Updates the minTimestamp and maxTimestamp fields based on the | 420 * Updates the minTimestamp and maxTimestamp fields based on the |
| 319 * current slices attached to the cpu. | 421 * current slices attached to the cpu. |
| 320 */ | 422 */ |
| 321 updateBounds: function() { | 423 updateBounds: function() { |
| 322 var values = []; | 424 var values = []; |
| 323 if (this.slices.length) { | 425 if (this.slices.length) { |
| 324 this.minTimestamp = this.slices[0].start; | 426 this.minTimestamp = this.slices[0].start; |
| 325 this.maxTimestamp = this.slices[this.slices.length - 1].end; | 427 this.maxTimestamp = this.slices[this.slices.length - 1].end; |
| 326 } else { | 428 } else { |
| 327 this.minTimestamp = undefined; | 429 this.minTimestamp = undefined; |
| 328 this.maxTimestamp = undefined; | 430 this.maxTimestamp = undefined; |
| 329 } | 431 } |
| 330 } | 432 } |
| 331 }; | 433 }; |
| 332 | 434 |
| 333 /** | 435 /** |
| 334 * Comparison between processes that orders by cpuNumber. | 436 * Comparison between processes that orders by cpuNumber. |
| 335 */ | 437 */ |
| 336 TimelineCpu.compare = function(x, y) { | 438 TimelineCpu.compare = function(x, y) { |
| 337 return x.cpuNumber - y.cpuNumber; | 439 return x.cpuNumber - y.cpuNumber; |
| 338 }; | 440 }; |
| 339 | 441 |
| 442 /** |
| 443 * A group of AsyncSlices. |
| 444 * @constructor |
| 445 */ |
| 446 function TimelineAsyncSliceGroup(name) { |
| 447 this.name = name; |
| 448 this.slices = []; |
| 449 } |
| 450 |
| 451 TimelineAsyncSliceGroup.prototype = { |
| 452 __proto__: Object.prototype, |
| 453 |
| 454 /** |
| 455 * Helper function that pushes the provided slice onto the slices array. |
| 456 */ |
| 457 push: function(slice) { |
| 458 this.slices.push(slice); |
| 459 }, |
| 460 |
| 461 /** |
| 462 * @return {Number} The number of slices in this group. |
| 463 */ |
| 464 get length() { |
| 465 return this.slices.length; |
| 466 }, |
| 467 |
| 468 /** |
| 469 * Built automatically by rebuildSubRows(). |
| 470 */ |
| 471 subRows_: undefined, |
| 472 |
| 473 /** |
| 474 * Updates the bounds for this group based on the slices it contains. |
| 475 */ |
| 476 sortSlices_: function() { |
| 477 this.slices.sort(function(x, y) { |
| 478 return x.start - y.start; |
| 479 }); |
| 480 }, |
| 481 |
| 482 /** |
| 483 * Shifts all the timestamps inside this group forward by the amount |
| 484 * specified. |
| 485 */ |
| 486 shiftTimestampsForward: function(amount) { |
| 487 for (var sI = 0; sI < this.slices.length; sI++) |
| 488 this.slices[sI].start = (this.slices[sI].start + amount); |
| 489 }, |
| 490 |
| 491 /** |
| 492 * Updates the bounds for this group based on the slices it contains. |
| 493 */ |
| 494 updateBounds: function() { |
| 495 this.sortSlices_(); |
| 496 if (this.slices.length) { |
| 497 this.minTimestamp = this.slices[0].start; |
| 498 this.maxTimestamp = this.slices[this.slices.length - 1].end; |
| 499 } else { |
| 500 this.minTimestamp = undefined; |
| 501 this.maxTimestamp = undefined; |
| 502 } |
| 503 this.subRows_ = undefined; |
| 504 }, |
| 505 |
| 506 get subRows() { |
| 507 if (!this.subRows_) |
| 508 this.rebuildSubRows_(); |
| 509 return this.subRows_; |
| 510 }, |
| 511 |
| 512 /** |
| 513 * Breaks up the list of slices into N rows, each of which is a list of |
| 514 * slices that are non overlapping. |
| 515 * |
| 516 * It uses a very simple approach: walk through the slices in sorted order |
| 517 * by start time. For each slice, try to fit it in an existing subRow. If it |
| 518 * doesn't fit in any subrow, make another subRow. |
| 519 */ |
| 520 rebuildSubRows_: function() { |
| 521 this.sortSlices_(); |
| 522 var subRows = []; |
| 523 for (var i = 0; i < this.slices.length; i++) { |
| 524 var slice = this.slices[i]; |
| 525 |
| 526 var found = false; |
| 527 for (var j = 0; j < subRows.length; j++) { |
| 528 var subRow = subRows[j]; |
| 529 var lastSliceInSubRow = subRow[subRow.length - 1]; |
| 530 if (slice.start >= lastSliceInSubRow.end) { |
| 531 found = true; |
| 532 subRow.push(slice); |
| 533 } |
| 534 } |
| 535 if (!found) { |
| 536 subRows.push([slice]); |
| 537 } |
| 538 } |
| 539 this.subRows_ = subRows; |
| 540 }, |
| 541 |
| 542 /** |
| 543 * Breaks up this group into slices based on start thread. |
| 544 * |
| 545 * @return {Array} An array of TimelineAsyncSliceGroups where each group has |
| 546 * slices that started on the same thread. |
| 547 **/ |
| 548 computeSubGroups: function() { |
| 549 var subGroupsByPTID = {}; |
| 550 for (var i = 0; i < this.slices.length; ++i) { |
| 551 var slice = this.slices[i]; |
| 552 var slicePTID = slice.startThread.ptid; |
| 553 if (!subGroupsByPTID[slicePTID]) |
| 554 subGroupsByPTID[slicePTID] = new TimelineAsyncSliceGroup(this.name); |
| 555 subGroupsByPTID[slicePTID].slices.push(slice); |
| 556 } |
| 557 var groups = []; |
| 558 for (var ptid in subGroupsByPTID) { |
| 559 var group = subGroupsByPTID[ptid]; |
| 560 group.updateBounds(); |
| 561 groups.push(group); |
| 562 } |
| 563 return groups; |
| 564 } |
| 565 |
| 566 }; |
| 567 |
| 568 /** |
| 569 * Comparison between counters that orders by pid, then name. |
| 570 */ |
| 571 TimelineCounter.compare = function(x, y) { |
| 572 if (x.parent.pid != y.parent.pid) { |
| 573 return TimelineProcess.compare(x.parent, y.parent.pid); |
| 574 } |
| 575 var tmp = x.name.localeCompare(y.name); |
| 576 if (tmp == 0) |
| 577 return x.tid - y.tid; |
| 578 return tmp; |
| 579 }; |
| 580 |
| 340 // The color pallette is split in half, with the upper | 581 // The color pallette is split in half, with the upper |
| 341 // half of the pallette being the "highlighted" verison | 582 // half of the pallette being the "highlighted" verison |
| 342 // of the base color. So, color 7's highlighted form is | 583 // of the base color. So, color 7's highlighted form is |
| 343 // 7 + (pallette.length / 2). | 584 // 7 + (pallette.length / 2). |
| 344 // | 585 // |
| 345 // These bright versions of colors are automatically generated | 586 // These bright versions of colors are automatically generated |
| 346 // from the base colors. | 587 // from the base colors. |
| 347 // | 588 // |
| 348 // Within the color pallette, there are "regular" colors, | 589 // Within the color pallette, there are "regular" colors, |
| 349 // which can be used for random color selection, and | 590 // which can be used for random color selection, and |
| (...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 480 * See TimelineModel.importEvents for details and more advanced ways to | 721 * See TimelineModel.importEvents for details and more advanced ways to |
| 481 * import data. | 722 * import data. |
| 482 * @param {bool=} opt_zeroAndBoost Whether to align to zero and boost the | 723 * @param {bool=} opt_zeroAndBoost Whether to align to zero and boost the |
| 483 * by 15%. Defaults to true. | 724 * by 15%. Defaults to true. |
| 484 * @constructor | 725 * @constructor |
| 485 */ | 726 */ |
| 486 function TimelineModel(opt_eventData, opt_zeroAndBoost) { | 727 function TimelineModel(opt_eventData, opt_zeroAndBoost) { |
| 487 this.cpus = {}; | 728 this.cpus = {}; |
| 488 this.processes = {}; | 729 this.processes = {}; |
| 489 this.importErrors = []; | 730 this.importErrors = []; |
| 731 this.asyncSliceGroups = {}; |
| 490 | 732 |
| 491 if (opt_eventData) | 733 if (opt_eventData) |
| 492 this.importEvents(opt_eventData, opt_zeroAndBoost); | 734 this.importEvents(opt_eventData, opt_zeroAndBoost); |
| 493 } | 735 } |
| 494 | 736 |
| 495 var importerConstructors = []; | 737 var importerConstructors = []; |
| 496 | 738 |
| 497 /** | 739 /** |
| 498 * Registers an importer. All registered importers are considered | 740 * Registers an importer. All registered importers are considered |
| 499 * when processing an import request. | 741 * when processing an import request. |
| (...skipping 12 matching lines...) Expand all Loading... |
| 512 return true; | 754 return true; |
| 513 if (typeof(eventData) === 'string' || eventData instanceof String) { | 755 if (typeof(eventData) === 'string' || eventData instanceof String) { |
| 514 return eventData.length == 0; | 756 return eventData.length == 0; |
| 515 } | 757 } |
| 516 return false; | 758 return false; |
| 517 }; | 759 }; |
| 518 | 760 |
| 519 TimelineModelEmptyImporter.prototype = { | 761 TimelineModelEmptyImporter.prototype = { |
| 520 __proto__: Object.prototype, | 762 __proto__: Object.prototype, |
| 521 | 763 |
| 522 importEvents : function() { | 764 importEvents: function() { |
| 765 }, |
| 766 finalizeImport: function() { |
| 523 } | 767 } |
| 524 }; | 768 }; |
| 525 | 769 |
| 526 TimelineModel.registerImporter(TimelineModelEmptyImporter); | 770 TimelineModel.registerImporter(TimelineModelEmptyImporter); |
| 527 | 771 |
| 528 TimelineModel.prototype = { | 772 TimelineModel.prototype = { |
| 529 __proto__: cr.EventTarget.prototype, | 773 __proto__: cr.EventTarget.prototype, |
| 530 | 774 |
| 531 get numProcesses() { | 775 get numProcesses() { |
| 532 var n = 0; | 776 var n = 0; |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 572 | 816 |
| 573 // Begin-events without matching end events leave a thread in a state | 817 // Begin-events without matching end events leave a thread in a state |
| 574 // where the toplevel subrows are empty but child subrows have | 818 // where the toplevel subrows are empty but child subrows have |
| 575 // entries. The autocloser will fix this up later. But, for the | 819 // entries. The autocloser will fix this up later. But, for the |
| 576 // purposes of pruning, such threads need to be treated as having | 820 // purposes of pruning, such threads need to be treated as having |
| 577 // content. | 821 // content. |
| 578 var hasNonEmptySubrow = false; | 822 var hasNonEmptySubrow = false; |
| 579 for (var s = 0; s < thread.subRows.length; s++) | 823 for (var s = 0; s < thread.subRows.length; s++) |
| 580 hasNonEmptySubrow |= thread.subRows[s].length > 0; | 824 hasNonEmptySubrow |= thread.subRows[s].length > 0; |
| 581 | 825 |
| 582 if (hasNonEmptySubrow || thread.nonNestedSubRows.legnth) | 826 if (hasNonEmptySubrow || thread.asyncSlices.length > 0) |
| 583 prunedThreads[tid] = thread; | 827 prunedThreads[tid] = thread; |
| 584 } | 828 } |
| 585 process.threads = prunedThreads; | 829 process.threads = prunedThreads; |
| 586 } | 830 } |
| 587 }, | 831 }, |
| 588 | 832 |
| 589 updateBounds: function() { | 833 updateBounds: function() { |
| 590 var wmin = Infinity; | 834 var wmin = Infinity; |
| 591 var wmax = -wmin; | 835 var wmax = -wmin; |
| 592 var hasData = false; | 836 var hasData = false; |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 631 } else { | 875 } else { |
| 632 this.maxTimestamp = undefined; | 876 this.maxTimestamp = undefined; |
| 633 this.minTimestamp = undefined; | 877 this.minTimestamp = undefined; |
| 634 } | 878 } |
| 635 }, | 879 }, |
| 636 | 880 |
| 637 shiftWorldToZero: function() { | 881 shiftWorldToZero: function() { |
| 638 if (this.minTimestamp === undefined) | 882 if (this.minTimestamp === undefined) |
| 639 return; | 883 return; |
| 640 var timeBase = this.minTimestamp; | 884 var timeBase = this.minTimestamp; |
| 641 var threads = this.getAllThreads(); | 885 for (var pid in this.processes) |
| 642 for (var tI = 0; tI < threads.length; tI++) { | 886 this.processes[pid].shiftTimestampsForward(-timeBase); |
| 643 var thread = threads[tI]; | 887 for (var cpuNumber in this.cpus) |
| 644 var shiftSubRow = function(subRow) { | 888 this.cpus[cpuNumber].shiftTimestampsForward(-timeBase); |
| 645 for (var tS = 0; tS < subRow.length; tS++) { | |
| 646 var slice = subRow[tS]; | |
| 647 slice.start = (slice.start - timeBase); | |
| 648 } | |
| 649 }; | |
| 650 | |
| 651 if (thread.cpuSlices) | |
| 652 shiftSubRow(thread.cpuSlices); | |
| 653 | |
| 654 for (var tSR = 0; tSR < thread.subRows.length; tSR++) { | |
| 655 shiftSubRow(thread.subRows[tSR]); | |
| 656 } | |
| 657 for (var tSR = 0; tSR < thread.nonNestedSubRows.length; tSR++) { | |
| 658 shiftSubRow(thread.nonNestedSubRows[tSR]); | |
| 659 } | |
| 660 } | |
| 661 var counters = this.getAllCounters(); | |
| 662 for (var tI = 0; tI < counters.length; tI++) { | |
| 663 var counter = counters[tI]; | |
| 664 for (var sI = 0; sI < counter.timestamps.length; sI++) | |
| 665 counter.timestamps[sI] = (counter.timestamps[sI] - timeBase); | |
| 666 } | |
| 667 var cpus = this.getAllCpus(); | |
| 668 for (var tI = 0; tI < cpus.length; tI++) { | |
| 669 var cpu = cpus[tI]; | |
| 670 for (var sI = 0; sI < cpu.slices.length; sI++) | |
| 671 cpu.slices[sI].start = (cpu.slices[sI].start - timeBase); | |
| 672 } | |
| 673 this.updateBounds(); | 889 this.updateBounds(); |
| 674 }, | 890 }, |
| 675 | 891 |
| 676 getAllThreads: function() { | 892 getAllThreads: function() { |
| 677 var threads = []; | 893 var threads = []; |
| 678 for (var pid in this.processes) { | 894 for (var pid in this.processes) { |
| 679 var process = this.processes[pid]; | 895 var process = this.processes[pid]; |
| 680 for (var tid in process.threads) { | 896 for (var tid in process.threads) { |
| 681 threads.push(process.threads[tid]); | 897 threads.push(process.threads[tid]); |
| 682 } | 898 } |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 725 | 941 |
| 726 /** | 942 /** |
| 727 * Imports the provided events into the model. The eventData type | 943 * Imports the provided events into the model. The eventData type |
| 728 * is undefined and will be passed to all the timeline importers registered | 944 * is undefined and will be passed to all the timeline importers registered |
| 729 * via TimelineModel.registerImporter. The first importer that returns true | 945 * via TimelineModel.registerImporter. The first importer that returns true |
| 730 * for canImport(events) will be used to import the events. | 946 * for canImport(events) will be used to import the events. |
| 731 * | 947 * |
| 732 * @param {Object} events Events to import. | 948 * @param {Object} events Events to import. |
| 733 * @param {boolean} isChildImport True the eventData being imported is an | 949 * @param {boolean} isChildImport True the eventData being imported is an |
| 734 * additional trace after the primary eventData. | 950 * additional trace after the primary eventData. |
| 951 * @return {TimelineModelImporter} The importer used for the eventData. |
| 735 */ | 952 */ |
| 736 importOneTrace_: function(eventData, isAdditionalImport) { | 953 importOneTrace_: function(eventData, isAdditionalImport) { |
| 737 var importerConstructor; | 954 var importerConstructor; |
| 738 for (var i = 0; i < importerConstructors.length; ++i) { | 955 for (var i = 0; i < importerConstructors.length; ++i) { |
| 739 if (importerConstructors[i].canImport(eventData)) { | 956 if (importerConstructors[i].canImport(eventData)) { |
| 740 importerConstructor = importerConstructors[i]; | 957 importerConstructor = importerConstructors[i]; |
| 741 break; | 958 break; |
| 742 } | 959 } |
| 743 } | 960 } |
| 744 if (!importerConstructor) | 961 if (!importerConstructor) |
| 745 throw 'Could not find an importer for the provided eventData.'; | 962 throw 'Could not find an importer for the provided eventData.'; |
| 746 | 963 |
| 747 var importer = new importerConstructor( | 964 var importer = new importerConstructor( |
| 748 this, eventData, isAdditionalImport); | 965 this, eventData, isAdditionalImport); |
| 749 importer.importEvents(); | 966 importer.importEvents(); |
| 750 this.pruneEmptyThreads(); | 967 return importer; |
| 751 }, | 968 }, |
| 752 | 969 |
| 753 /** | 970 /** |
| 754 * Imports the provided traces into the model. The eventData type | 971 * Imports the provided traces into the model. The eventData type |
| 755 * is undefined and will be passed to all the timeline importers registered | 972 * is undefined and will be passed to all the timeline importers registered |
| 756 * via TimelineModel.registerImporter. The first importer that returns true | 973 * via TimelineModel.registerImporter. The first importer that returns true |
| 757 * for canImport(events) will be used to import the events. | 974 * for canImport(events) will be used to import the events. |
| 758 * | 975 * |
| 759 * The primary trace is provided via the eventData variable. If multiple | 976 * The primary trace is provided via the eventData variable. If multiple |
| 760 * traces are to be imported, specify the first one as events, and the | 977 * traces are to be imported, specify the first one as events, and the |
| 761 * remainder in the opt_additionalEventData array. | 978 * remainder in the opt_additionalEventData array. |
| 762 * | 979 * |
| 763 * @param {Object} eventData Events to import. | 980 * @param {Object} eventData Events to import. |
| 764 * @param {bool=} opt_zeroAndBoost Whether to align to zero and boost the | 981 * @param {bool=} opt_zeroAndBoost Whether to align to zero and boost the |
| 765 * by 15%. Defaults to true. | 982 * by 15%. Defaults to true. |
| 766 * @param {Array=} opt_additionalEventData An array of eventData objects | 983 * @param {Array=} opt_additionalEventData An array of eventData objects |
| 767 * (e.g. array of arrays) to | 984 * (e.g. array of arrays) to |
| 768 * import after importing the primary events. | 985 * import after importing the primary events. |
| 769 */ | 986 */ |
| 770 importEvents: function(eventData, | 987 importEvents: function(eventData, |
| 771 opt_zeroAndBoost, opt_additionalEventData) { | 988 opt_zeroAndBoost, opt_additionalEventData) { |
| 772 if (opt_zeroAndBoost === undefined) | 989 if (opt_zeroAndBoost === undefined) |
| 773 opt_zeroAndBoost = true; | 990 opt_zeroAndBoost = true; |
| 774 | 991 |
| 775 this.importOneTrace_(eventData, false); | 992 activeImporters = []; |
| 993 var importer = this.importOneTrace_(eventData, false); |
| 994 activeImporters.push(importer); |
| 776 if (opt_additionalEventData) { | 995 if (opt_additionalEventData) { |
| 777 for (var i = 0; i < opt_additionalEventData.length; ++i) { | 996 for (var i = 0; i < opt_additionalEventData.length; ++i) { |
| 778 this.importOneTrace_(opt_additionalEventData[i], true); | 997 importer = this.importOneTrace_(opt_additionalEventData[i], true); |
| 998 activeImporters.push(importer); |
| 779 } | 999 } |
| 780 } | 1000 } |
| 1001 for (var i = 0; i < activeImporters.length; ++i) |
| 1002 activeImporters[i].finalizeImport(); |
| 1003 |
| 1004 for (var i = 0; i < activeImporters.length; ++i) |
| 1005 this.pruneEmptyThreads(); |
| 781 | 1006 |
| 782 this.updateBounds(); | 1007 this.updateBounds(); |
| 783 | 1008 |
| 784 if (opt_zeroAndBoost) | 1009 if (opt_zeroAndBoost) |
| 785 this.shiftWorldToZero(); | 1010 this.shiftWorldToZero(); |
| 786 | 1011 |
| 787 if (opt_zeroAndBoost && | 1012 if (opt_zeroAndBoost && |
| 788 this.minTimestamp !== undefined && | 1013 this.minTimestamp !== undefined && |
| 789 this.maxTimestamp !== undefined) { | 1014 this.maxTimestamp !== undefined) { |
| 790 var boost = (this.maxTimestamp - this.minTimestamp) * 0.15; | 1015 var boost = (this.maxTimestamp - this.minTimestamp) * 0.15; |
| 791 this.minTimestamp = this.minTimestamp - boost; | 1016 this.minTimestamp = this.minTimestamp - boost; |
| 792 this.maxTimestamp = this.maxTimestamp + boost; | 1017 this.maxTimestamp = this.maxTimestamp + boost; |
| 793 } | 1018 } |
| 794 } | 1019 } |
| 795 }; | 1020 }; |
| 796 | 1021 |
| 797 return { | 1022 return { |
| 798 getPallette: getPallette, | 1023 getPallette: getPallette, |
| 799 getPalletteHighlightIdBoost: getPalletteHighlightIdBoost, | 1024 getPalletteHighlightIdBoost: getPalletteHighlightIdBoost, |
| 800 getColorIdByName: getColorIdByName, | 1025 getColorIdByName: getColorIdByName, |
| 801 getStringHash: getStringHash, | 1026 getStringHash: getStringHash, |
| 802 getStringColorId: getStringColorId, | 1027 getStringColorId: getStringColorId, |
| 803 | 1028 |
| 804 TimelineSlice: TimelineSlice, | 1029 TimelineSlice: TimelineSlice, |
| 1030 TimelineThreadSlice: TimelineThreadSlice, |
| 1031 TimelineAsyncSlice: TimelineAsyncSlice, |
| 805 TimelineThread: TimelineThread, | 1032 TimelineThread: TimelineThread, |
| 806 TimelineCounter: TimelineCounter, | 1033 TimelineCounter: TimelineCounter, |
| 807 TimelineProcess: TimelineProcess, | 1034 TimelineProcess: TimelineProcess, |
| 808 TimelineCpu: TimelineCpu, | 1035 TimelineCpu: TimelineCpu, |
| 1036 TimelineAsyncSliceGroup: TimelineAsyncSliceGroup, |
| 809 TimelineModel: TimelineModel | 1037 TimelineModel: TimelineModel |
| 810 }; | 1038 }; |
| 811 | 1039 |
| 812 }); | 1040 }); |
| OLD | NEW |