| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 'use strict'; | |
| 6 | |
| 7 /** | |
| 8 * @fileoverview TimelineModel is a parsed representation of the | |
| 9 * TraceEvents obtained from base/trace_event in which the begin-end | |
| 10 * tokens are converted into a hierarchy of processes, threads, | |
| 11 * subrows, and slices. | |
| 12 * | |
| 13 * The building block of the model is a slice. A slice is roughly | |
| 14 * equivalent to function call executing on a specific thread. As a | |
| 15 * result, slices may have one or more subslices. | |
| 16 * | |
| 17 * A thread contains one or more subrows of slices. Row 0 corresponds to | |
| 18 * the "root" slices, e.g. the topmost slices. Row 1 contains slices that | |
| 19 * are nested 1 deep in the stack, and so on. We use these subrows to draw | |
| 20 * nesting tasks. | |
| 21 * | |
| 22 */ | |
| 23 cr.define('tracing', function() { | |
| 24 /** | |
| 25 * A TimelineSlice represents an interval of time plus parameters associated | |
| 26 * with that interval. | |
| 27 * | |
| 28 * All time units are stored in milliseconds. | |
| 29 * @constructor | |
| 30 */ | |
| 31 function TimelineSlice(title, colorId, start, args, opt_duration) { | |
| 32 this.title = title; | |
| 33 this.start = start; | |
| 34 this.colorId = colorId; | |
| 35 this.args = args; | |
| 36 this.didNotFinish = false; | |
| 37 if (opt_duration !== undefined) | |
| 38 this.duration = opt_duration; | |
| 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 TimelineThreadSlice represents an interval of time on a thread resource | |
| 53 * with associated nestinged slice information. | |
| 54 * | |
| 55 * ThreadSlices are typically associated with a specific trace event pair on a | |
| 56 * specific thread. | |
| 57 * For example, | |
| 58 * TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms | |
| 59 * TRACE_EVENT_END0() at time=0.3ms | |
| 60 * This results in a single timeline slice from 0.1 with duration 0.2 on a | |
| 61 * specific thread. | |
| 62 * | |
| 63 * @constructor | |
| 64 */ | |
| 65 function TimelineThreadSlice(title, colorId, start, args, opt_duration) { | |
| 66 TimelineSlice.call(this, title, colorId, start, args, opt_duration); | |
| 67 this.subSlices = []; | |
| 68 } | |
| 69 | |
| 70 TimelineThreadSlice.prototype = { | |
| 71 __proto__: TimelineSlice.prototype | |
| 72 }; | |
| 73 | |
| 74 /** | |
| 75 * A TimelineAsyncSlice represents an interval of time during which an | |
| 76 * asynchronous operation is in progress. An AsyncSlice consumes no CPU time | |
| 77 * itself and so is only associated with Threads at its start and end point. | |
| 78 * | |
| 79 * @constructor | |
| 80 */ | |
| 81 function TimelineAsyncSlice(title, colorId, start, args) { | |
| 82 TimelineSlice.call(this, title, colorId, start, args); | |
| 83 }; | |
| 84 | |
| 85 TimelineAsyncSlice.prototype = { | |
| 86 __proto__: TimelineSlice.prototype, | |
| 87 | |
| 88 id: undefined, | |
| 89 | |
| 90 startThread: undefined, | |
| 91 | |
| 92 endThread: undefined, | |
| 93 | |
| 94 subSlices: undefined | |
| 95 }; | |
| 96 | |
| 97 /** | |
| 98 * A TimelineThread stores all the trace events collected for a particular | |
| 99 * thread. We organize the synchronous slices on a thread by "subrows," where | |
| 100 * subrow 0 has all the root slices, subrow 1 those nested 1 deep, and so on. | |
| 101 * The asynchronous slices are stored in an TimelineAsyncSliceGroup object. | |
| 102 * | |
| 103 * The slices stored on a TimelineThread should be instances of | |
| 104 * TimelineThreadSlice. | |
| 105 * | |
| 106 * @constructor | |
| 107 */ | |
| 108 function TimelineThread(parent, tid) { | |
| 109 if (!parent) | |
| 110 throw 'Parent must be provided.'; | |
| 111 this.parent = parent; | |
| 112 this.tid = tid; | |
| 113 this.subRows = [[]]; | |
| 114 this.asyncSlices = new TimelineAsyncSliceGroup(this.ptid); | |
| 115 } | |
| 116 | |
| 117 var ptidMap = {}; | |
| 118 | |
| 119 /** | |
| 120 * @return {String} A string that can be used as a unique key for a specific | |
| 121 * thread within a process. | |
| 122 */ | |
| 123 TimelineThread.getPTIDFromPidAndTid = function(pid, tid) { | |
| 124 if (!ptidMap[pid]) | |
| 125 ptidMap[pid] = {}; | |
| 126 if (!ptidMap[pid][tid]) | |
| 127 ptidMap[pid][tid] = pid + ':' + tid; | |
| 128 return ptidMap[pid][tid]; | |
| 129 } | |
| 130 | |
| 131 TimelineThread.prototype = { | |
| 132 /** | |
| 133 * Name of the thread, if present. | |
| 134 */ | |
| 135 name: undefined, | |
| 136 | |
| 137 /** | |
| 138 * @return {string} A concatenation of the parent id and the thread's | |
| 139 * tid. Can be used to uniquely identify a thread. | |
| 140 */ | |
| 141 get ptid() { | |
| 142 return TimelineThread.getPTIDFromPidAndTid(this.tid, this.parent.pid); | |
| 143 }, | |
| 144 | |
| 145 getSubrow: function(i) { | |
| 146 while (i >= this.subRows.length) | |
| 147 this.subRows.push([]); | |
| 148 return this.subRows[i]; | |
| 149 }, | |
| 150 | |
| 151 | |
| 152 shiftSubRow_: function(subRow, amount) { | |
| 153 for (var tS = 0; tS < subRow.length; tS++) { | |
| 154 var slice = subRow[tS]; | |
| 155 slice.start = (slice.start + amount); | |
| 156 } | |
| 157 }, | |
| 158 | |
| 159 /** | |
| 160 * Shifts all the timestamps inside this thread forward by the amount | |
| 161 * specified. | |
| 162 */ | |
| 163 shiftTimestampsForward: function(amount) { | |
| 164 if (this.cpuSlices) | |
| 165 this.shiftSubRow_(this.cpuSlices, amount); | |
| 166 | |
| 167 for (var tSR = 0; tSR < this.subRows.length; tSR++) { | |
| 168 this.shiftSubRow_(this.subRows[tSR], amount); | |
| 169 } | |
| 170 | |
| 171 this.asyncSlices.shiftTimestampsForward(amount); | |
| 172 }, | |
| 173 | |
| 174 /** | |
| 175 * Updates the minTimestamp and maxTimestamp fields based on the | |
| 176 * current objects associated with the thread. | |
| 177 */ | |
| 178 updateBounds: function() { | |
| 179 var values = []; | |
| 180 var slices; | |
| 181 if (this.subRows[0].length != 0) { | |
| 182 slices = this.subRows[0]; | |
| 183 values.push(slices[0].start); | |
| 184 values.push(slices[slices.length - 1].end); | |
| 185 } | |
| 186 if (this.asyncSlices.slices.length) { | |
| 187 this.asyncSlices.updateBounds(); | |
| 188 values.push(this.asyncSlices.minTimestamp); | |
| 189 values.push(this.asyncSlices.maxTimestamp); | |
| 190 } | |
| 191 if (values.length) { | |
| 192 this.minTimestamp = Math.min.apply(Math, values); | |
| 193 this.maxTimestamp = Math.max.apply(Math, values); | |
| 194 } else { | |
| 195 this.minTimestamp = undefined; | |
| 196 this.maxTimestamp = undefined; | |
| 197 } | |
| 198 }, | |
| 199 | |
| 200 /** | |
| 201 * @return {String} A user-friendly name for this thread. | |
| 202 */ | |
| 203 get userFriendlyName() { | |
| 204 var tname = this.name || this.tid; | |
| 205 return this.parent.pid + ': ' + tname; | |
| 206 }, | |
| 207 | |
| 208 /** | |
| 209 * @return {String} User friendly details about this thread. | |
| 210 */ | |
| 211 get userFriendlyDetails() { | |
| 212 return 'pid: ' + this.parent.pid + | |
| 213 ', tid: ' + this.tid + | |
| 214 (this.name ? ', name: ' + this.name : ''); | |
| 215 } | |
| 216 }; | |
| 217 | |
| 218 /** | |
| 219 * Comparison between threads that orders first by pid, | |
| 220 * then by names, then by tid. | |
| 221 */ | |
| 222 TimelineThread.compare = function(x, y) { | |
| 223 if (x.parent.pid != y.parent.pid) { | |
| 224 return TimelineProcess.compare(x.parent, y.parent.pid); | |
| 225 } | |
| 226 | |
| 227 if (x.name && y.name) { | |
| 228 var tmp = x.name.localeCompare(y.name); | |
| 229 if (tmp == 0) | |
| 230 return x.tid - y.tid; | |
| 231 return tmp; | |
| 232 } else if (x.name) { | |
| 233 return -1; | |
| 234 } else if (y.name) { | |
| 235 return 1; | |
| 236 } else { | |
| 237 return x.tid - y.tid; | |
| 238 } | |
| 239 }; | |
| 240 | |
| 241 /** | |
| 242 * Stores all the samples for a given counter. | |
| 243 * @constructor | |
| 244 */ | |
| 245 function TimelineCounter(parent, id, name) { | |
| 246 this.parent = parent; | |
| 247 this.id = id; | |
| 248 this.name = name; | |
| 249 this.seriesNames = []; | |
| 250 this.seriesColors = []; | |
| 251 this.timestamps = []; | |
| 252 this.samples = []; | |
| 253 } | |
| 254 | |
| 255 TimelineCounter.prototype = { | |
| 256 __proto__: Object.prototype, | |
| 257 | |
| 258 get numSeries() { | |
| 259 return this.seriesNames.length; | |
| 260 }, | |
| 261 | |
| 262 get numSamples() { | |
| 263 return this.timestamps.length; | |
| 264 }, | |
| 265 | |
| 266 /** | |
| 267 * Shifts all the timestamps inside this counter forward by the amount | |
| 268 * specified. | |
| 269 */ | |
| 270 shiftTimestampsForward: function(amount) { | |
| 271 for (var sI = 0; sI < this.timestamps.length; sI++) | |
| 272 this.timestamps[sI] = (this.timestamps[sI] + amount); | |
| 273 }, | |
| 274 | |
| 275 /** | |
| 276 * Updates the bounds for this counter based on the samples it contains. | |
| 277 */ | |
| 278 updateBounds: function() { | |
| 279 if (this.seriesNames.length != this.seriesColors.length) | |
| 280 throw 'seriesNames.length must match seriesColors.length'; | |
| 281 if (this.numSeries * this.numSamples != this.samples.length) | |
| 282 throw 'samples.length must be a multiple of numSamples.'; | |
| 283 | |
| 284 this.totals = []; | |
| 285 if (this.samples.length == 0) { | |
| 286 this.minTimestamp = undefined; | |
| 287 this.maxTimestamp = undefined; | |
| 288 this.maxTotal = 0; | |
| 289 return; | |
| 290 } | |
| 291 this.minTimestamp = this.timestamps[0]; | |
| 292 this.maxTimestamp = this.timestamps[this.timestamps.length - 1]; | |
| 293 | |
| 294 var numSeries = this.numSeries; | |
| 295 var maxTotal = -Infinity; | |
| 296 for (var i = 0; i < this.timestamps.length; i++) { | |
| 297 var total = 0; | |
| 298 for (var j = 0; j < numSeries; j++) { | |
| 299 total += this.samples[i * numSeries + j]; | |
| 300 this.totals.push(total); | |
| 301 } | |
| 302 if (total > maxTotal) | |
| 303 maxTotal = total; | |
| 304 } | |
| 305 this.maxTotal = maxTotal; | |
| 306 } | |
| 307 | |
| 308 }; | |
| 309 | |
| 310 /** | |
| 311 * Comparison between counters that orders by pid, then name. | |
| 312 */ | |
| 313 TimelineCounter.compare = function(x, y) { | |
| 314 if (x.parent.pid != y.parent.pid) { | |
| 315 return TimelineProcess.compare(x.parent, y.parent.pid); | |
| 316 } | |
| 317 var tmp = x.name.localeCompare(y.name); | |
| 318 if (tmp == 0) | |
| 319 return x.tid - y.tid; | |
| 320 return tmp; | |
| 321 }; | |
| 322 | |
| 323 /** | |
| 324 * The TimelineProcess represents a single process in the | |
| 325 * trace. Right now, we keep this around purely for bookkeeping | |
| 326 * reasons. | |
| 327 * @constructor | |
| 328 */ | |
| 329 function TimelineProcess(pid) { | |
| 330 this.pid = pid; | |
| 331 this.threads = {}; | |
| 332 this.counters = {}; | |
| 333 }; | |
| 334 | |
| 335 TimelineProcess.prototype = { | |
| 336 get numThreads() { | |
| 337 var n = 0; | |
| 338 for (var p in this.threads) { | |
| 339 n++; | |
| 340 } | |
| 341 return n; | |
| 342 }, | |
| 343 | |
| 344 /** | |
| 345 * Shifts all the timestamps inside this process forward by the amount | |
| 346 * specified. | |
| 347 */ | |
| 348 shiftTimestampsForward: function(amount) { | |
| 349 for (var tid in this.threads) | |
| 350 this.threads[tid].shiftTimestampsForward(amount); | |
| 351 for (var id in this.counters) | |
| 352 this.counters[id].shiftTimestampsForward(amount); | |
| 353 }, | |
| 354 | |
| 355 /** | |
| 356 * @return {TimlineThread} The thread identified by tid on this process, | |
| 357 * creating it if it doesn't exist. | |
| 358 */ | |
| 359 getOrCreateThread: function(tid) { | |
| 360 if (!this.threads[tid]) | |
| 361 this.threads[tid] = new TimelineThread(this, tid); | |
| 362 return this.threads[tid]; | |
| 363 }, | |
| 364 | |
| 365 /** | |
| 366 * @return {TimlineCounter} The counter on this process named 'name', | |
| 367 * creating it if it doesn't exist. | |
| 368 */ | |
| 369 getOrCreateCounter: function(cat, name) { | |
| 370 var id = cat + '.' + name; | |
| 371 if (!this.counters[id]) | |
| 372 this.counters[id] = new TimelineCounter(this, id, name); | |
| 373 return this.counters[id]; | |
| 374 } | |
| 375 }; | |
| 376 | |
| 377 /** | |
| 378 * Comparison between processes that orders by pid. | |
| 379 */ | |
| 380 TimelineProcess.compare = function(x, y) { | |
| 381 return x.pid - y.pid; | |
| 382 }; | |
| 383 | |
| 384 /** | |
| 385 * The TimelineCpu represents a Cpu from the kernel's point of view. | |
| 386 * @constructor | |
| 387 */ | |
| 388 function TimelineCpu(number) { | |
| 389 this.cpuNumber = number; | |
| 390 this.slices = []; | |
| 391 this.counters = {}; | |
| 392 }; | |
| 393 | |
| 394 TimelineCpu.prototype = { | |
| 395 /** | |
| 396 * @return {TimlineCounter} The counter on this process named 'name', | |
| 397 * creating it if it doesn't exist. | |
| 398 */ | |
| 399 getOrCreateCounter: function(cat, name) { | |
| 400 var id; | |
| 401 if (cat.length) | |
| 402 id = cat + '.' + name; | |
| 403 else | |
| 404 id = name; | |
| 405 if (!this.counters[id]) | |
| 406 this.counters[id] = new TimelineCounter(this, id, name); | |
| 407 return this.counters[id]; | |
| 408 }, | |
| 409 | |
| 410 /** | |
| 411 * Shifts all the timestamps inside this CPU forward by the amount | |
| 412 * specified. | |
| 413 */ | |
| 414 shiftTimestampsForward: function(amount) { | |
| 415 for (var sI = 0; sI < this.slices.length; sI++) | |
| 416 this.slices[sI].start = (this.slices[sI].start + amount); | |
| 417 for (var id in this.counters) | |
| 418 this.counters[id].shiftTimestampsForward(amount); | |
| 419 }, | |
| 420 | |
| 421 /** | |
| 422 * Updates the minTimestamp and maxTimestamp fields based on the | |
| 423 * current slices attached to the cpu. | |
| 424 */ | |
| 425 updateBounds: function() { | |
| 426 var values = []; | |
| 427 if (this.slices.length) { | |
| 428 this.minTimestamp = this.slices[0].start; | |
| 429 this.maxTimestamp = this.slices[this.slices.length - 1].end; | |
| 430 } else { | |
| 431 this.minTimestamp = undefined; | |
| 432 this.maxTimestamp = undefined; | |
| 433 } | |
| 434 } | |
| 435 }; | |
| 436 | |
| 437 /** | |
| 438 * Comparison between processes that orders by cpuNumber. | |
| 439 */ | |
| 440 TimelineCpu.compare = function(x, y) { | |
| 441 return x.cpuNumber - y.cpuNumber; | |
| 442 }; | |
| 443 | |
| 444 /** | |
| 445 * A group of AsyncSlices. | |
| 446 * @constructor | |
| 447 */ | |
| 448 function TimelineAsyncSliceGroup(name) { | |
| 449 this.name = name; | |
| 450 this.slices = []; | |
| 451 } | |
| 452 | |
| 453 TimelineAsyncSliceGroup.prototype = { | |
| 454 __proto__: Object.prototype, | |
| 455 | |
| 456 /** | |
| 457 * Helper function that pushes the provided slice onto the slices array. | |
| 458 */ | |
| 459 push: function(slice) { | |
| 460 this.slices.push(slice); | |
| 461 }, | |
| 462 | |
| 463 /** | |
| 464 * @return {Number} The number of slices in this group. | |
| 465 */ | |
| 466 get length() { | |
| 467 return this.slices.length; | |
| 468 }, | |
| 469 | |
| 470 /** | |
| 471 * Built automatically by rebuildSubRows(). | |
| 472 */ | |
| 473 subRows_: undefined, | |
| 474 | |
| 475 /** | |
| 476 * Updates the bounds for this group based on the slices it contains. | |
| 477 */ | |
| 478 sortSlices_: function() { | |
| 479 this.slices.sort(function(x, y) { | |
| 480 return x.start - y.start; | |
| 481 }); | |
| 482 }, | |
| 483 | |
| 484 /** | |
| 485 * Shifts all the timestamps inside this group forward by the amount | |
| 486 * specified. | |
| 487 */ | |
| 488 shiftTimestampsForward: function(amount) { | |
| 489 for (var sI = 0; sI < this.slices.length; sI++) { | |
| 490 var slice = this.slices[sI]; | |
| 491 slice.start = (slice.start + amount); | |
| 492 for (var sJ = 0; sJ < slice.subSlices.length; sJ++) | |
| 493 slice.subSlices[sJ].start += amount; | |
| 494 } | |
| 495 }, | |
| 496 | |
| 497 /** | |
| 498 * Updates the bounds for this group based on the slices it contains. | |
| 499 */ | |
| 500 updateBounds: function() { | |
| 501 this.sortSlices_(); | |
| 502 if (this.slices.length) { | |
| 503 this.minTimestamp = this.slices[0].start; | |
| 504 this.maxTimestamp = this.slices[this.slices.length - 1].end; | |
| 505 } else { | |
| 506 this.minTimestamp = undefined; | |
| 507 this.maxTimestamp = undefined; | |
| 508 } | |
| 509 this.subRows_ = undefined; | |
| 510 }, | |
| 511 | |
| 512 get subRows() { | |
| 513 if (!this.subRows_) | |
| 514 this.rebuildSubRows_(); | |
| 515 return this.subRows_; | |
| 516 }, | |
| 517 | |
| 518 /** | |
| 519 * Breaks up the list of slices into N rows, each of which is a list of | |
| 520 * slices that are non overlapping. | |
| 521 * | |
| 522 * It uses a very simple approach: walk through the slices in sorted order | |
| 523 * by start time. For each slice, try to fit it in an existing subRow. If it | |
| 524 * doesn't fit in any subrow, make another subRow. | |
| 525 */ | |
| 526 rebuildSubRows_: function() { | |
| 527 this.sortSlices_(); | |
| 528 var subRows = []; | |
| 529 for (var i = 0; i < this.slices.length; i++) { | |
| 530 var slice = this.slices[i]; | |
| 531 | |
| 532 var found = false; | |
| 533 for (var j = 0; j < subRows.length; j++) { | |
| 534 var subRow = subRows[j]; | |
| 535 var lastSliceInSubRow = subRow[subRow.length - 1]; | |
| 536 if (slice.start >= lastSliceInSubRow.end) { | |
| 537 found = true; | |
| 538 // Instead of plotting one big slice for the entire | |
| 539 // TimelineAsyncEvent, we plot each of the subSlices. | |
| 540 if (slice.subSlices === undefined || slice.subSlices.length < 1) | |
| 541 throw 'TimelineAsyncEvent missing subSlices: ' + slice.name; | |
| 542 for (var k = 0; k < slice.subSlices.length; k++) | |
| 543 subRow.push(slice.subSlices[k]); | |
| 544 break; | |
| 545 } | |
| 546 } | |
| 547 if (!found) { | |
| 548 var subRow = []; | |
| 549 if (slice.subSlices !== undefined) { | |
| 550 for (var k = 0; k < slice.subSlices.length; k++) | |
| 551 subRow.push(slice.subSlices[k]); | |
| 552 subRows.push(subRow); | |
| 553 } | |
| 554 } | |
| 555 } | |
| 556 this.subRows_ = subRows; | |
| 557 }, | |
| 558 | |
| 559 /** | |
| 560 * Breaks up this group into slices based on start thread. | |
| 561 * | |
| 562 * @return {Array} An array of TimelineAsyncSliceGroups where each group has | |
| 563 * slices that started on the same thread. | |
| 564 **/ | |
| 565 computeSubGroups: function() { | |
| 566 var subGroupsByPTID = {}; | |
| 567 for (var i = 0; i < this.slices.length; ++i) { | |
| 568 var slice = this.slices[i]; | |
| 569 var slicePTID = slice.startThread.ptid; | |
| 570 if (!subGroupsByPTID[slicePTID]) | |
| 571 subGroupsByPTID[slicePTID] = new TimelineAsyncSliceGroup(this.name); | |
| 572 subGroupsByPTID[slicePTID].slices.push(slice); | |
| 573 } | |
| 574 var groups = []; | |
| 575 for (var ptid in subGroupsByPTID) { | |
| 576 var group = subGroupsByPTID[ptid]; | |
| 577 group.updateBounds(); | |
| 578 groups.push(group); | |
| 579 } | |
| 580 return groups; | |
| 581 } | |
| 582 }; | |
| 583 | |
| 584 /** | |
| 585 * Comparison between counters that orders by pid, then name. | |
| 586 */ | |
| 587 TimelineCounter.compare = function(x, y) { | |
| 588 if (x.parent.pid != y.parent.pid) { | |
| 589 return TimelineProcess.compare(x.parent, y.parent.pid); | |
| 590 } | |
| 591 var tmp = x.name.localeCompare(y.name); | |
| 592 if (tmp == 0) | |
| 593 return x.tid - y.tid; | |
| 594 return tmp; | |
| 595 }; | |
| 596 | |
| 597 // The color pallette is split in half, with the upper | |
| 598 // half of the pallette being the "highlighted" verison | |
| 599 // of the base color. So, color 7's highlighted form is | |
| 600 // 7 + (pallette.length / 2). | |
| 601 // | |
| 602 // These bright versions of colors are automatically generated | |
| 603 // from the base colors. | |
| 604 // | |
| 605 // Within the color pallette, there are "regular" colors, | |
| 606 // which can be used for random color selection, and | |
| 607 // reserved colors, which are used when specific colors | |
| 608 // need to be used, e.g. where red is desired. | |
| 609 var palletteBase = [ | |
| 610 {r: 138, g: 113, b: 152}, | |
| 611 {r: 175, g: 112, b: 133}, | |
| 612 {r: 127, g: 135, b: 225}, | |
| 613 {r: 93, g: 81, b: 137}, | |
| 614 {r: 116, g: 143, b: 119}, | |
| 615 {r: 178, g: 214, b: 122}, | |
| 616 {r: 87, g: 109, b: 147}, | |
| 617 {r: 119, g: 155, b: 95}, | |
| 618 {r: 114, g: 180, b: 160}, | |
| 619 {r: 132, g: 85, b: 103}, | |
| 620 {r: 157, g: 210, b: 150}, | |
| 621 {r: 148, g: 94, b: 86}, | |
| 622 {r: 164, g: 108, b: 138}, | |
| 623 {r: 139, g: 191, b: 150}, | |
| 624 {r: 110, g: 99, b: 145}, | |
| 625 {r: 80, g: 129, b: 109}, | |
| 626 {r: 125, g: 140, b: 149}, | |
| 627 {r: 93, g: 124, b: 132}, | |
| 628 {r: 140, g: 85, b: 140}, | |
| 629 {r: 104, g: 163, b: 162}, | |
| 630 {r: 132, g: 141, b: 178}, | |
| 631 {r: 131, g: 105, b: 147}, | |
| 632 {r: 135, g: 183, b: 98}, | |
| 633 {r: 152, g: 134, b: 177}, | |
| 634 {r: 141, g: 188, b: 141}, | |
| 635 {r: 133, g: 160, b: 210}, | |
| 636 {r: 126, g: 186, b: 148}, | |
| 637 {r: 112, g: 198, b: 205}, | |
| 638 {r: 180, g: 122, b: 195}, | |
| 639 {r: 203, g: 144, b: 152}, | |
| 640 // Reserved Entires | |
| 641 {r: 182, g: 125, b: 143}, | |
| 642 {r: 126, g: 200, b: 148}, | |
| 643 {r: 133, g: 160, b: 210}, | |
| 644 {r: 240, g: 240, b: 240}]; | |
| 645 | |
| 646 // Make sure this number tracks the number of reserved entries in the | |
| 647 // pallette. | |
| 648 var numReservedColorIds = 4; | |
| 649 | |
| 650 function brighten(c) { | |
| 651 var k; | |
| 652 if (c.r >= 240 && c.g >= 240 && c.b >= 240) | |
| 653 k = -0.20; | |
| 654 else | |
| 655 k = 0.45; | |
| 656 | |
| 657 return {r: Math.min(255, c.r + Math.floor(c.r * k)), | |
| 658 g: Math.min(255, c.g + Math.floor(c.g * k)), | |
| 659 b: Math.min(255, c.b + Math.floor(c.b * k))}; | |
| 660 } | |
| 661 function colorToString(c) { | |
| 662 return 'rgb(' + c.r + ',' + c.g + ',' + c.b + ')'; | |
| 663 } | |
| 664 | |
| 665 /** | |
| 666 * The number of color IDs that getStringColorId can choose from. | |
| 667 */ | |
| 668 var numRegularColorIds = palletteBase.length - numReservedColorIds; | |
| 669 var highlightIdBoost = palletteBase.length; | |
| 670 | |
| 671 var pallette = palletteBase.concat(palletteBase.map(brighten)). | |
| 672 map(colorToString); | |
| 673 /** | |
| 674 * Computes a simplistic hashcode of the provide name. Used to chose colors | |
| 675 * for slices. | |
| 676 * @param {string} name The string to hash. | |
| 677 */ | |
| 678 function getStringHash(name) { | |
| 679 var hash = 0; | |
| 680 for (var i = 0; i < name.length; ++i) | |
| 681 hash = (hash + 37 * hash + 11 * name.charCodeAt(i)) % 0xFFFFFFFF; | |
| 682 return hash; | |
| 683 } | |
| 684 | |
| 685 /** | |
| 686 * Gets the color pallette. | |
| 687 */ | |
| 688 function getPallette() { | |
| 689 return pallette; | |
| 690 } | |
| 691 | |
| 692 /** | |
| 693 * @return {Number} The value to add to a color ID to get its highlighted | |
| 694 * colro ID. E.g. 7 + getPalletteHighlightIdBoost() yields a brightened from | |
| 695 * of 7's base color. | |
| 696 */ | |
| 697 function getPalletteHighlightIdBoost() { | |
| 698 return highlightIdBoost; | |
| 699 } | |
| 700 | |
| 701 /** | |
| 702 * @param {String} name The color name. | |
| 703 * @return {Number} The color ID for the given color name. | |
| 704 */ | |
| 705 function getColorIdByName(name) { | |
| 706 if (name == 'iowait') | |
| 707 return numRegularColorIds; | |
| 708 if (name == 'running') | |
| 709 return numRegularColorIds + 1; | |
| 710 if (name == 'runnable') | |
| 711 return numRegularColorIds + 2; | |
| 712 if (name == 'sleeping') | |
| 713 return numRegularColorIds + 3; | |
| 714 throw 'Unrecognized color ' + name; | |
| 715 } | |
| 716 | |
| 717 // Previously computed string color IDs. They are based on a stable hash, so | |
| 718 // it is safe to save them throughout the program time. | |
| 719 var stringColorIdCache = {}; | |
| 720 | |
| 721 /** | |
| 722 * @return {Number} A color ID that is stably associated to the provided via | |
| 723 * the getStringHash method. The color ID will be chosen from the regular | |
| 724 * ID space only, e.g. no reserved ID will be used. | |
| 725 */ | |
| 726 function getStringColorId(string) { | |
| 727 if (stringColorIdCache[string] === undefined) { | |
| 728 var hash = getStringHash(string); | |
| 729 stringColorIdCache[string] = hash % numRegularColorIds; | |
| 730 } | |
| 731 return stringColorIdCache[string]; | |
| 732 } | |
| 733 | |
| 734 /** | |
| 735 * Builds a model from an array of TraceEvent objects. | |
| 736 * @param {Object=} opt_data The event data to import into the new model. | |
| 737 * See TimelineModel.importEvents for details and more advanced ways to | |
| 738 * import data. | |
| 739 * @param {bool=} opt_zeroAndBoost Whether to align to zero and boost the | |
| 740 * by 15%. Defaults to true. | |
| 741 * @constructor | |
| 742 */ | |
| 743 function TimelineModel(opt_eventData, opt_zeroAndBoost) { | |
| 744 this.cpus = {}; | |
| 745 this.processes = {}; | |
| 746 this.importErrors = []; | |
| 747 this.asyncSliceGroups = {}; | |
| 748 | |
| 749 if (opt_eventData) | |
| 750 this.importEvents(opt_eventData, opt_zeroAndBoost); | |
| 751 } | |
| 752 | |
| 753 var importerConstructors = []; | |
| 754 | |
| 755 /** | |
| 756 * Registers an importer. All registered importers are considered | |
| 757 * when processing an import request. | |
| 758 * | |
| 759 * @param {Function} importerConstructor The importer's constructor function. | |
| 760 */ | |
| 761 TimelineModel.registerImporter = function(importerConstructor) { | |
| 762 importerConstructors.push(importerConstructor); | |
| 763 }; | |
| 764 | |
| 765 function TimelineModelEmptyImporter(events) { | |
| 766 }; | |
| 767 | |
| 768 TimelineModelEmptyImporter.canImport = function(eventData) { | |
| 769 if (eventData instanceof Array && eventData.length == 0) | |
| 770 return true; | |
| 771 if (typeof(eventData) === 'string' || eventData instanceof String) { | |
| 772 return eventData.length == 0; | |
| 773 } | |
| 774 return false; | |
| 775 }; | |
| 776 | |
| 777 TimelineModelEmptyImporter.prototype = { | |
| 778 __proto__: Object.prototype, | |
| 779 | |
| 780 importEvents: function() { | |
| 781 }, | |
| 782 finalizeImport: function() { | |
| 783 } | |
| 784 }; | |
| 785 | |
| 786 TimelineModel.registerImporter(TimelineModelEmptyImporter); | |
| 787 | |
| 788 TimelineModel.prototype = { | |
| 789 __proto__: cr.EventTarget.prototype, | |
| 790 | |
| 791 get numProcesses() { | |
| 792 var n = 0; | |
| 793 for (var p in this.processes) | |
| 794 n++; | |
| 795 return n; | |
| 796 }, | |
| 797 | |
| 798 /** | |
| 799 * @return {TimelineProcess} Gets a specific TimelineCpu or creates one if | |
| 800 * it does not exist. | |
| 801 */ | |
| 802 getOrCreateCpu: function(cpuNumber) { | |
| 803 if (!this.cpus[cpuNumber]) | |
| 804 this.cpus[cpuNumber] = new TimelineCpu(cpuNumber); | |
| 805 return this.cpus[cpuNumber]; | |
| 806 }, | |
| 807 | |
| 808 /** | |
| 809 * @return {TimelineProcess} Gets a TimlineProcess for a specified pid or | |
| 810 * creates one if it does not exist. | |
| 811 */ | |
| 812 getOrCreateProcess: function(pid) { | |
| 813 if (!this.processes[pid]) | |
| 814 this.processes[pid] = new TimelineProcess(pid); | |
| 815 return this.processes[pid]; | |
| 816 }, | |
| 817 | |
| 818 /** | |
| 819 * The import takes an array of json-ified TraceEvents and adds them into | |
| 820 * the TimelineModel as processes, threads, and slices. | |
| 821 */ | |
| 822 | |
| 823 /** | |
| 824 * Removes threads from the model that are fully empty. | |
| 825 */ | |
| 826 pruneEmptyThreads: function() { | |
| 827 for (var pid in this.processes) { | |
| 828 var process = this.processes[pid]; | |
| 829 var prunedThreads = {}; | |
| 830 for (var tid in process.threads) { | |
| 831 var thread = process.threads[tid]; | |
| 832 | |
| 833 // Begin-events without matching end events leave a thread in a state | |
| 834 // where the toplevel subrows are empty but child subrows have | |
| 835 // entries. The autocloser will fix this up later. But, for the | |
| 836 // purposes of pruning, such threads need to be treated as having | |
| 837 // content. | |
| 838 var hasNonEmptySubrow = false; | |
| 839 for (var s = 0; s < thread.subRows.length; s++) | |
| 840 hasNonEmptySubrow |= thread.subRows[s].length > 0; | |
| 841 | |
| 842 if (hasNonEmptySubrow || thread.asyncSlices.length > 0) | |
| 843 prunedThreads[tid] = thread; | |
| 844 } | |
| 845 process.threads = prunedThreads; | |
| 846 } | |
| 847 }, | |
| 848 | |
| 849 updateBounds: function() { | |
| 850 var wmin = Infinity; | |
| 851 var wmax = -wmin; | |
| 852 var hasData = false; | |
| 853 | |
| 854 var threads = this.getAllThreads(); | |
| 855 for (var tI = 0; tI < threads.length; tI++) { | |
| 856 var thread = threads[tI]; | |
| 857 thread.updateBounds(); | |
| 858 if (thread.minTimestamp != undefined && | |
| 859 thread.maxTimestamp != undefined) { | |
| 860 wmin = Math.min(wmin, thread.minTimestamp); | |
| 861 wmax = Math.max(wmax, thread.maxTimestamp); | |
| 862 hasData = true; | |
| 863 } | |
| 864 } | |
| 865 var counters = this.getAllCounters(); | |
| 866 for (var tI = 0; tI < counters.length; tI++) { | |
| 867 var counter = counters[tI]; | |
| 868 counter.updateBounds(); | |
| 869 if (counter.minTimestamp != undefined && | |
| 870 counter.maxTimestamp != undefined) { | |
| 871 hasData = true; | |
| 872 wmin = Math.min(wmin, counter.minTimestamp); | |
| 873 wmax = Math.max(wmax, counter.maxTimestamp); | |
| 874 } | |
| 875 } | |
| 876 | |
| 877 for (var cpuNumber in this.cpus) { | |
| 878 var cpu = this.cpus[cpuNumber]; | |
| 879 cpu.updateBounds(); | |
| 880 if (cpu.minTimestamp != undefined && | |
| 881 cpu.maxTimestamp != undefined) { | |
| 882 hasData = true; | |
| 883 wmin = Math.min(wmin, cpu.minTimestamp); | |
| 884 wmax = Math.max(wmax, cpu.maxTimestamp); | |
| 885 } | |
| 886 } | |
| 887 | |
| 888 if (hasData) { | |
| 889 this.minTimestamp = wmin; | |
| 890 this.maxTimestamp = wmax; | |
| 891 } else { | |
| 892 this.maxTimestamp = undefined; | |
| 893 this.minTimestamp = undefined; | |
| 894 } | |
| 895 }, | |
| 896 | |
| 897 shiftWorldToZero: function() { | |
| 898 if (this.minTimestamp === undefined) | |
| 899 return; | |
| 900 var timeBase = this.minTimestamp; | |
| 901 for (var pid in this.processes) | |
| 902 this.processes[pid].shiftTimestampsForward(-timeBase); | |
| 903 for (var cpuNumber in this.cpus) | |
| 904 this.cpus[cpuNumber].shiftTimestampsForward(-timeBase); | |
| 905 this.updateBounds(); | |
| 906 }, | |
| 907 | |
| 908 getAllThreads: function() { | |
| 909 var threads = []; | |
| 910 for (var pid in this.processes) { | |
| 911 var process = this.processes[pid]; | |
| 912 for (var tid in process.threads) { | |
| 913 threads.push(process.threads[tid]); | |
| 914 } | |
| 915 } | |
| 916 return threads; | |
| 917 }, | |
| 918 | |
| 919 /** | |
| 920 * @return {Array} An array of all cpus in the model. | |
| 921 */ | |
| 922 getAllCpus: function() { | |
| 923 var cpus = []; | |
| 924 for (var cpu in this.cpus) | |
| 925 cpus.push(this.cpus[cpu]); | |
| 926 return cpus; | |
| 927 }, | |
| 928 | |
| 929 /** | |
| 930 * @return {Array} An array of all processes in the model. | |
| 931 */ | |
| 932 getAllProcesses: function() { | |
| 933 var processes = []; | |
| 934 for (var pid in this.processes) | |
| 935 processes.push(this.processes[pid]); | |
| 936 return processes; | |
| 937 }, | |
| 938 | |
| 939 /** | |
| 940 * @return {Array} An array of all the counters in the model. | |
| 941 */ | |
| 942 getAllCounters: function() { | |
| 943 var counters = []; | |
| 944 for (var pid in this.processes) { | |
| 945 var process = this.processes[pid]; | |
| 946 for (var tid in process.counters) { | |
| 947 counters.push(process.counters[tid]); | |
| 948 } | |
| 949 } | |
| 950 for (var cpuNumber in this.cpus) { | |
| 951 var cpu = this.cpus[cpuNumber]; | |
| 952 for (var counterName in cpu.counters) | |
| 953 counters.push(cpu.counters[counterName]); | |
| 954 } | |
| 955 return counters; | |
| 956 }, | |
| 957 | |
| 958 /** | |
| 959 * Imports the provided events into the model. The eventData type | |
| 960 * is undefined and will be passed to all the timeline importers registered | |
| 961 * via TimelineModel.registerImporter. The first importer that returns true | |
| 962 * for canImport(events) will be used to import the events. | |
| 963 * | |
| 964 * @param {Object} events Events to import. | |
| 965 * @param {boolean} isAdditionalImport True the eventData being imported is | |
| 966 * an additional trace after the primary eventData. | |
| 967 * @return {TimelineModelImporter} The importer used for the eventData. | |
| 968 */ | |
| 969 importOneTrace_: function(eventData, isAdditionalImport) { | |
| 970 var importerConstructor; | |
| 971 for (var i = 0; i < importerConstructors.length; ++i) { | |
| 972 if (importerConstructors[i].canImport(eventData)) { | |
| 973 importerConstructor = importerConstructors[i]; | |
| 974 break; | |
| 975 } | |
| 976 } | |
| 977 if (!importerConstructor) | |
| 978 throw 'Could not find an importer for the provided eventData.'; | |
| 979 | |
| 980 var importer = new importerConstructor( | |
| 981 this, eventData, isAdditionalImport); | |
| 982 importer.importEvents(); | |
| 983 return importer; | |
| 984 }, | |
| 985 | |
| 986 /** | |
| 987 * Imports the provided traces into the model. The eventData type | |
| 988 * is undefined and will be passed to all the timeline importers registered | |
| 989 * via TimelineModel.registerImporter. The first importer that returns true | |
| 990 * for canImport(events) will be used to import the events. | |
| 991 * | |
| 992 * The primary trace is provided via the eventData variable. If multiple | |
| 993 * traces are to be imported, specify the first one as events, and the | |
| 994 * remainder in the opt_additionalEventData array. | |
| 995 * | |
| 996 * @param {Object} eventData Events to import. | |
| 997 * @param {bool=} opt_zeroAndBoost Whether to align to zero and boost the | |
| 998 * by 15%. Defaults to true. | |
| 999 * @param {Array=} opt_additionalEventData An array of eventData objects | |
| 1000 * (e.g. array of arrays) to | |
| 1001 * import after importing the primary events. | |
| 1002 */ | |
| 1003 importEvents: function(eventData, | |
| 1004 opt_zeroAndBoost, opt_additionalEventData) { | |
| 1005 if (opt_zeroAndBoost === undefined) | |
| 1006 opt_zeroAndBoost = true; | |
| 1007 | |
| 1008 var activeImporters = []; | |
| 1009 var importer = this.importOneTrace_(eventData, false); | |
| 1010 activeImporters.push(importer); | |
| 1011 if (opt_additionalEventData) { | |
| 1012 for (var i = 0; i < opt_additionalEventData.length; ++i) { | |
| 1013 importer = this.importOneTrace_(opt_additionalEventData[i], true); | |
| 1014 activeImporters.push(importer); | |
| 1015 } | |
| 1016 } | |
| 1017 for (var i = 0; i < activeImporters.length; ++i) | |
| 1018 activeImporters[i].finalizeImport(); | |
| 1019 | |
| 1020 for (var i = 0; i < activeImporters.length; ++i) | |
| 1021 this.pruneEmptyThreads(); | |
| 1022 | |
| 1023 this.updateBounds(); | |
| 1024 | |
| 1025 if (opt_zeroAndBoost) | |
| 1026 this.shiftWorldToZero(); | |
| 1027 | |
| 1028 if (opt_zeroAndBoost && | |
| 1029 this.minTimestamp !== undefined && | |
| 1030 this.maxTimestamp !== undefined) { | |
| 1031 var boost = (this.maxTimestamp - this.minTimestamp) * 0.15; | |
| 1032 this.minTimestamp = this.minTimestamp - boost; | |
| 1033 this.maxTimestamp = this.maxTimestamp + boost; | |
| 1034 } | |
| 1035 } | |
| 1036 }; | |
| 1037 | |
| 1038 /** | |
| 1039 * @constructor A filter that can be passed into | |
| 1040 * Timeline.findAllObjectsMatchingFilter | |
| 1041 */ | |
| 1042 function TimelineFilter(text) { | |
| 1043 this.text_ = text; | |
| 1044 } | |
| 1045 TimelineFilter.prototype = { | |
| 1046 __proto__: Object.prototype, | |
| 1047 | |
| 1048 matchSlice: function(slice) { | |
| 1049 if (this.text_.length == 0) | |
| 1050 return false; | |
| 1051 return slice.title.indexOf(this.text_) != -1; | |
| 1052 } | |
| 1053 | |
| 1054 }; | |
| 1055 | |
| 1056 return { | |
| 1057 getPallette: getPallette, | |
| 1058 getPalletteHighlightIdBoost: getPalletteHighlightIdBoost, | |
| 1059 getColorIdByName: getColorIdByName, | |
| 1060 getStringHash: getStringHash, | |
| 1061 getStringColorId: getStringColorId, | |
| 1062 | |
| 1063 TimelineSlice: TimelineSlice, | |
| 1064 TimelineThreadSlice: TimelineThreadSlice, | |
| 1065 TimelineAsyncSlice: TimelineAsyncSlice, | |
| 1066 TimelineThread: TimelineThread, | |
| 1067 TimelineCounter: TimelineCounter, | |
| 1068 TimelineProcess: TimelineProcess, | |
| 1069 TimelineCpu: TimelineCpu, | |
| 1070 TimelineAsyncSliceGroup: TimelineAsyncSliceGroup, | |
| 1071 TimelineModel: TimelineModel, | |
| 1072 TimelineFilter: TimelineFilter | |
| 1073 }; | |
| 1074 | |
| 1075 }); | |
| OLD | NEW |