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 */ 23cr.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}); 1076