1<!DOCTYPE html> 2<!-- 3Copyright (c) 2015 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7 8<link rel="import" href="/tracing/base/color_scheme.html"> 9<link rel="import" href="/tracing/base/iteration_helpers.html"> 10<link rel="import" href="/tracing/model/memory_allocator_dump.html"> 11<link rel="import" 12 href="/tracing/ui/analysis/memory_dump_allocator_details_pane.html"> 13<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> 14<link rel="import" 15 href="/tracing/ui/analysis/memory_dump_vm_regions_details_pane.html"> 16<link rel="import" href="/tracing/ui/analysis/stacked_pane.html"> 17<link rel="import" href="/tracing/ui/base/color_legend.html"> 18<link rel="import" href="/tracing/ui/base/dom_helpers.html"> 19<link rel="import" href="/tracing/ui/base/table.html"> 20<link rel="import" href="/tracing/ui/view_specific_brushing_state.html"> 21<link rel="import" href="/tracing/value/numeric.html"> 22<link rel="import" href="/tracing/value/unit.html"> 23 24<polymer-element name="tr-ui-a-memory-dump-overview-pane" 25 extends="tr-ui-a-stacked-pane"> 26 <template> 27 <style> 28 :host { 29 display: flex; 30 flex-direction: column; 31 } 32 33 #label { 34 flex: 0 0 auto; 35 padding: 8px; 36 37 background-color: #eee; 38 border-bottom: 1px solid #8e8e8e; 39 border-top: 1px solid white; 40 41 font-size: 15px; 42 font-weight: bold; 43 } 44 45 #contents { 46 flex: 1 0 auto; 47 align-self: stretch; 48 font-size: 12px; 49 } 50 51 #info_text { 52 padding: 8px; 53 color: #666; 54 font-style: italic; 55 text-align: center; 56 } 57 58 #table { 59 display: none; /* Hide until memory dumps are set. */ 60 flex: 1 0 auto; 61 align-self: stretch; 62 } 63 </style> 64 <tr-ui-b-view-specific-brushing-state id="state" 65 view-id="analysis.memory_dump_overview_pane"> 66 </tr-ui-b-view-specific-brushing-state> 67 <div id="label">Overview</div> 68 <div id="contents"> 69 <div id="info_text">No memory memory dumps selected</div> 70 <tr-ui-b-table id="table"></tr-ui-b-table> 71 </div> 72 </template> 73</polymer-element> 74<script> 75'use strict'; 76 77tr.exportTo('tr.ui.analysis', function() { 78 79 var ColorScheme = tr.b.ColorScheme; 80 var ScalarNumeric = tr.v.ScalarNumeric; 81 var sizeInBytes_smallerIsBetter = 82 tr.v.Unit.byName.sizeInBytes_smallerIsBetter; 83 84 var PLATFORM_SPECIFIC_TOTAL_NAME_SUFFIX = '_bytes'; 85 86 var DISPLAYED_SIZE_NUMERIC_NAME = 87 tr.model.MemoryAllocatorDump.DISPLAYED_SIZE_NUMERIC_NAME; 88 var SOME_TIMESTAMPS_INFO_QUANTIFIER = 89 tr.ui.analysis.MemoryColumn.SOME_TIMESTAMPS_INFO_QUANTIFIER; 90 91 // Unicode symbols used for memory cell info icons and messages. 92 var RIGHTWARDS_ARROW_WITH_HOOK = String.fromCharCode(0x21AA); 93 var RIGHTWARDS_ARROW_FROM_BAR = String.fromCharCode(0x21A6); 94 var GREATER_THAN_OR_EQUAL_TO = String.fromCharCode(0x2265); 95 var UNMARRIED_PARTNERSHIP_SYMBOL = String.fromCharCode(0x26AF); 96 var TRIGRAM_FOR_HEAVEN = String.fromCharCode(0x2630); 97 98 // TODO(petrcermak): Move this to iteration_helpers.html. 99 function lazyMap(list, fn, opt_this) { 100 opt_this = opt_this || this; 101 var result = undefined; 102 list.forEach(function(item, index) { 103 var value = fn.call(opt_this, item, index); 104 if (value === undefined) 105 return; 106 if (result === undefined) 107 result = new Array(list.length); 108 result[index] = value; 109 }); 110 return result; 111 } 112 113 /** @constructor */ 114 function ProcessNameColumn() { 115 tr.ui.analysis.TitleColumn.call(this, 'Process'); 116 } 117 118 ProcessNameColumn.prototype = { 119 __proto__: tr.ui.analysis.TitleColumn.prototype, 120 121 formatTitle: function(row) { 122 if (row.contexts === undefined) 123 return row.title; // Total row. 124 var titleEl = document.createElement('tr-ui-b-color-legend'); 125 titleEl.label = row.title; 126 return titleEl; 127 } 128 }; 129 130 /** @constructor */ 131 function UsedMemoryColumn(name, cellPath, aggregationMode) { 132 tr.ui.analysis.NumericMemoryColumn.call( 133 this, name, cellPath, aggregationMode); 134 } 135 136 UsedMemoryColumn.COLOR = 137 ColorScheme.getColorForReservedNameAsString('used_memory_column'); 138 UsedMemoryColumn.OLDER_COLOR = 139 ColorScheme.getColorForReservedNameAsString('older_used_memory_column'); 140 141 UsedMemoryColumn.prototype = { 142 __proto__: tr.ui.analysis.NumericMemoryColumn.prototype, 143 144 get title() { 145 return tr.ui.b.createSpan({ 146 textContent: this.name, 147 color: UsedMemoryColumn.COLOR 148 }); 149 }, 150 151 color: function(numerics, processMemoryDumps) { 152 return UsedMemoryColumn.COLOR; 153 }, 154 155 getChildPaneBuilder: function(processMemoryDumps) { 156 if (processMemoryDumps === undefined) 157 return undefined; 158 159 var vmRegions = lazyMap(processMemoryDumps, function(pmd) { 160 if (pmd === undefined) 161 return undefined; 162 return pmd.mostRecentVmRegions; 163 }); 164 if (vmRegions === undefined) 165 return undefined; 166 167 return function() { 168 var pane = document.createElement( 169 'tr-ui-a-memory-dump-vm-regions-details-pane'); 170 pane.vmRegions = vmRegions; 171 pane.aggregationMode = this.aggregationMode; 172 return pane; 173 }.bind(this); 174 } 175 }; 176 177 /** @constructor */ 178 function PeakMemoryColumn(name, cellPath, aggregationMode) { 179 UsedMemoryColumn.call(this, name, cellPath, aggregationMode); 180 } 181 182 PeakMemoryColumn.prototype = { 183 __proto__: UsedMemoryColumn.prototype, 184 185 addInfos: function(numerics, processMemoryDumps, infos) { 186 if (processMemoryDumps === undefined) 187 return; // Total row. 188 189 var resettableValueCount = 0; 190 var nonResettableValueCount = 0; 191 for (var i = 0; i < numerics.length; i++) { 192 if (numerics[i] === undefined) 193 continue; 194 if (processMemoryDumps[i].arePeakResidentBytesResettable) 195 resettableValueCount++; 196 else 197 nonResettableValueCount++; 198 } 199 200 if (resettableValueCount > 0 && nonResettableValueCount > 0) { 201 infos.push(tr.ui.analysis.createWarningInfo('Both resettable and ' + 202 'non-resettable peak RSS values were provided by the process')); 203 } else if (resettableValueCount > 0) { 204 infos.push({ 205 icon: RIGHTWARDS_ARROW_WITH_HOOK, 206 message: 'Peak RSS since previous memory dump.' 207 }); 208 } else { 209 infos.push({ 210 icon: RIGHTWARDS_ARROW_FROM_BAR, 211 message: 'Peak RSS since process startup. Finer grained ' + 212 'peaks require a Linux kernel version ' + 213 GREATER_THAN_OR_EQUAL_TO + ' 4.0.' 214 }); 215 } 216 } 217 }; 218 219 /** @constructor */ 220 function ByteStatColumn(name, cellPath, aggregationMode) { 221 UsedMemoryColumn.call(this, name, cellPath, aggregationMode); 222 } 223 224 ByteStatColumn.prototype = { 225 __proto__: UsedMemoryColumn.prototype, 226 227 color: function(numerics, processMemoryDumps) { 228 if (processMemoryDumps === undefined) 229 return UsedMemoryColumn.COLOR; // Total row. 230 231 var allOlderValues = processMemoryDumps.every( 232 function(processMemoryDump) { 233 if (processMemoryDump === undefined) 234 return true; 235 return !processMemoryDump.hasOwnVmRegions; 236 }); 237 238 // Show the cell in lighter blue if all values were older (i.e. none of 239 // the defined process memory dumps had own VM regions). 240 if (allOlderValues) 241 return UsedMemoryColumn.OLDER_COLOR; 242 else 243 return UsedMemoryColumn.COLOR; 244 }, 245 246 addInfos: function(numerics, processMemoryDumps, infos) { 247 if (processMemoryDumps === undefined) 248 return; // Total row. 249 250 var olderValueCount = 0; 251 for (var i = 0; i < numerics.length; i++) { 252 var processMemoryDump = processMemoryDumps[i]; 253 if (processMemoryDump !== undefined && 254 !processMemoryDump.hasOwnVmRegions) { 255 olderValueCount++; 256 } 257 } 258 259 if (olderValueCount === 0) 260 return; // There are no older values. 261 262 var infoQuantifier = olderValueCount < numerics.length ? 263 ' ' + SOME_TIMESTAMPS_INFO_QUANTIFIER /* some values are older */ : 264 '' /* all values are older */; 265 266 // Emit an info if there was at least one older value (i.e. at least one 267 // defined process memory dump did not have own VM regions). 268 infos.push({ 269 message: 'Older value' + infoQuantifier + 270 ' (only heavy (purple) memory dumps contain memory maps).', 271 icon: UNMARRIED_PARTNERSHIP_SYMBOL 272 }); 273 } 274 }; 275 276 // Rules for constructing and sorting used memory columns. 277 UsedMemoryColumn.RULES = [ 278 { 279 condition: 'Total resident', 280 importance: 10, 281 columnConstructor: UsedMemoryColumn 282 }, 283 { 284 condition: 'Peak total resident', 285 importance: 9, 286 columnConstructor: PeakMemoryColumn 287 }, 288 { 289 condition: 'PSS', 290 importance: 8, 291 columnConstructor: ByteStatColumn 292 }, 293 { 294 condition: 'Private dirty', 295 importance: 7, 296 columnConstructor: ByteStatColumn 297 }, 298 { 299 condition: 'Swapped', 300 importance: 6, 301 columnConstructor: ByteStatColumn 302 }, 303 { 304 // All other columns. 305 importance: 0, 306 columnConstructor: UsedMemoryColumn 307 } 308 ]; 309 310 // Map from ProcessMemoryDump totals fields to column names. 311 UsedMemoryColumn.TOTALS_MAP = { 312 'residentBytes': 'Total resident', 313 'peakResidentBytes': 'Peak total resident' 314 }; 315 316 // Map from VMRegionByteStats field names to column names. 317 UsedMemoryColumn.BYTE_STAT_MAP = { 318 'proportionalResident': 'PSS', 319 'privateDirtyResident': 'Private dirty', 320 'swapped': 'Swapped' 321 }; 322 323 /** @constructor */ 324 function AllocatorColumn(name, cellPath, aggregationMode) { 325 tr.ui.analysis.NumericMemoryColumn.call( 326 this, name, cellPath, aggregationMode); 327 } 328 329 AllocatorColumn.prototype = { 330 __proto__: tr.ui.analysis.NumericMemoryColumn.prototype, 331 332 get title() { 333 var titleEl = document.createElement('tr-ui-b-color-legend'); 334 titleEl.label = this.name; 335 return titleEl; 336 }, 337 338 addInfos: function(numerics, processMemoryDumps, infos) { 339 if (processMemoryDumps === undefined) 340 return; 341 342 var heapDumpCount = 0; 343 for (var i = 0; i < processMemoryDumps.length; i++) { 344 var processMemoryDump = processMemoryDumps[i]; 345 if (processMemoryDump === undefined) 346 continue; 347 var heapDumps = processMemoryDump.heapDumps; 348 if (heapDumps === undefined) 349 continue; 350 if (heapDumps[this.name] !== undefined) 351 heapDumpCount++; 352 } 353 354 if (heapDumpCount === 0) 355 return; // There are no heap dumps. 356 357 var infoQuantifier = heapDumpCount < numerics.length ? 358 ' ' + SOME_TIMESTAMPS_INFO_QUANTIFIER : ''; 359 360 // Emit a heap dump info if at least one of the process memory dumps has 361 // a heap dump associated with this allocator. 362 infos.push({ 363 message: 'Heap dump provided' + infoQuantifier + '.', 364 icon: TRIGRAM_FOR_HEAVEN 365 }); 366 }, 367 368 getChildPaneBuilder: function(processMemoryDumps) { 369 if (processMemoryDumps === undefined) 370 return undefined; 371 372 var memoryAllocatorDumps = lazyMap(processMemoryDumps, function(pmd) { 373 if (pmd === undefined) 374 return undefined; 375 return pmd.getMemoryAllocatorDumpByFullName(this.name); 376 }, this); 377 if (memoryAllocatorDumps === undefined) 378 return undefined; 379 380 var heapDumps = lazyMap(processMemoryDumps, function(pmd) { 381 if (pmd === undefined || pmd.heapDumps === undefined) 382 return undefined; 383 return pmd.heapDumps[this.name]; 384 }, this); 385 386 return function() { 387 var pane = document.createElement( 388 'tr-ui-a-memory-dump-allocator-details-pane'); 389 pane.memoryAllocatorDumps = memoryAllocatorDumps; 390 pane.heapDumps = heapDumps; 391 pane.aggregationMode = this.aggregationMode; 392 return pane; 393 }.bind(this); 394 } 395 }; 396 397 /** @constructor */ 398 function TracingColumn(name, cellPath, aggregationMode) { 399 AllocatorColumn.call(this, name, cellPath, aggregationMode); 400 } 401 402 TracingColumn.COLOR = 403 ColorScheme.getColorForReservedNameAsString('tracing_memory_column'); 404 405 TracingColumn.prototype = { 406 __proto__: AllocatorColumn.prototype, 407 408 get title() { 409 return tr.ui.b.createSpan({ 410 textContent: this.name, 411 color: TracingColumn.COLOR 412 }); 413 }, 414 415 color: function(numerics, processMemoryDumps) { 416 return TracingColumn.COLOR; 417 } 418 }; 419 420 // Rules for constructing and sorting allocator columns. 421 AllocatorColumn.RULES = [ 422 { 423 condition: 'tracing', 424 importance: 0, 425 columnConstructor: TracingColumn 426 }, 427 { 428 // All other columns. 429 importance: 1, 430 columnConstructor: AllocatorColumn 431 } 432 ]; 433 434 Polymer('tr-ui-a-memory-dump-overview-pane', { 435 created: function() { 436 this.processMemoryDumps_ = undefined; 437 this.aggregationMode_ = undefined; 438 }, 439 440 ready: function() { 441 this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.CELL; 442 this.$.table.addEventListener('selection-changed', 443 function(tableEvent) { 444 tableEvent.stopPropagation(); 445 this.changeChildPane_(); 446 }.bind(this)); 447 }, 448 449 /** 450 * Sets the process memory dumps and schedules rebuilding the pane. 451 * 452 * The provided value should be a chronological list of dictionaries 453 * mapping process IDs to process memory dumps. Example: 454 * 455 * [ 456 * { 457 * // PMDs at timestamp 1. 458 * 42: tr.model.ProcessMemoryDump {} 459 * }, 460 * { 461 * // PMDs at timestamp 2. 462 * 42: tr.model.ProcessMemoryDump {}, 463 * 89: tr.model.ProcessMemoryDump {} 464 * } 465 * ] 466 */ 467 set processMemoryDumps(processMemoryDumps) { 468 this.processMemoryDumps_ = processMemoryDumps; 469 this.scheduleRebuildPane_(); 470 }, 471 472 get processMemoryDumps() { 473 return this.processMemoryDumps_; 474 }, 475 476 set aggregationMode(aggregationMode) { 477 this.aggregationMode_ = aggregationMode; 478 this.scheduleRebuildPane_(); 479 }, 480 481 get aggregationMode() { 482 return this.aggregationMode_; 483 }, 484 485 get selectedMemoryCell() { 486 if (this.processMemoryDumps_ === undefined || 487 this.processMemoryDumps_.length === 0) { 488 return undefined; 489 } 490 491 var selectedTableRow = this.$.table.selectedTableRow; 492 if (!selectedTableRow) 493 return undefined; 494 495 var selectedColumnIndex = this.$.table.selectedColumnIndex; 496 if (selectedColumnIndex === undefined) 497 return undefined; 498 499 var selectedColumn = this.$.table.tableColumns[selectedColumnIndex]; 500 var selectedMemoryCell = selectedColumn.cell(selectedTableRow); 501 return selectedMemoryCell; 502 }, 503 504 changeChildPane_: function() { 505 this.storeSelection_(); 506 this.childPaneBuilder = this.determineChildPaneBuilderFromSelection_(); 507 }, 508 509 determineChildPaneBuilderFromSelection_: function() { 510 if (this.processMemoryDumps_ === undefined || 511 this.processMemoryDumps_.length === 0) { 512 return undefined; 513 } 514 515 var selectedTableRow = this.$.table.selectedTableRow; 516 if (!selectedTableRow) 517 return undefined; 518 519 var selectedColumnIndex = this.$.table.selectedColumnIndex; 520 if (selectedColumnIndex === undefined) 521 return undefined; 522 var selectedColumn = this.$.table.tableColumns[selectedColumnIndex]; 523 524 return selectedColumn.getChildPaneBuilder(selectedTableRow.contexts); 525 }, 526 527 rebuildPane_: function() { 528 if (this.processMemoryDumps_ === undefined || 529 this.processMemoryDumps_.length === 0) { 530 // Show the info text (hide the table). 531 this.$.info_text.style.display = 'block'; 532 this.$.table.style.display = 'none'; 533 534 this.$.table.clear(); 535 this.$.table.rebuild(); 536 return; 537 } 538 539 // Show the table (hide the info text). 540 this.$.info_text.style.display = 'none'; 541 this.$.table.style.display = 'block'; 542 543 var rows = this.createRows_(); 544 var columns = this.createColumns_(rows); 545 var footerRows = this.createFooterRows_(rows, columns); 546 547 this.$.table.tableRows = rows; 548 this.$.table.footerRows = footerRows; 549 this.$.table.tableColumns = columns; 550 this.$.table.rebuild(); 551 552 this.restoreSelection_(); 553 }, 554 555 createRows_: function() { 556 // Timestamp (list index) -> Process ID (dict key) -> PMD. 557 var timeToPidToProcessMemoryDump = this.processMemoryDumps_; 558 559 // Process ID (dict key) -> Timestamp (list index) -> PMD or undefined. 560 var pidToTimeToProcessMemoryDump = tr.b.invertArrayOfDicts( 561 timeToPidToProcessMemoryDump); 562 563 // Process (list index) -> Component (dict key) -> Cell. 564 return tr.b.dictionaryValues(tr.b.mapItems( 565 pidToTimeToProcessMemoryDump, function(pid, timeToDump) { 566 // Get the process associated with the dumps. We can use any defined 567 // process memory dump in timeToDump since they all have the same pid. 568 var process = tr.b.findFirstInArray(timeToDump).process; 569 570 // Used memory (total resident, PSS, ...). 571 var usedMemoryCells = tr.ui.analysis.createCells(timeToDump, 572 function(dump) { 573 var sizes = {}; 574 575 var totals = dump.totals; 576 if (totals !== undefined) { 577 // Common totals. 578 tr.b.iterItems(UsedMemoryColumn.TOTALS_MAP, 579 function(totalName, cellName) { 580 var total = totals[totalName]; 581 if (total === undefined) 582 return; 583 sizes[cellName] = new ScalarNumeric( 584 sizeInBytes_smallerIsBetter, total); 585 }); 586 587 // Platform-specific totals (e.g. private resident on Mac). 588 var platformSpecific = totals.platformSpecific; 589 if (platformSpecific !== undefined) { 590 tr.b.iterItems(platformSpecific, function(name, size) { 591 // Change raw OS-specific total name to a user-friendly 592 // column title (e.g. 'private_bytes' -> 'Private'). 593 if (name.endsWith(PLATFORM_SPECIFIC_TOTAL_NAME_SUFFIX)) { 594 name = name.substring(0, name.length - 595 PLATFORM_SPECIFIC_TOTAL_NAME_SUFFIX.length); 596 } 597 name = name.replace('_', ' ').trim(); 598 name = name.charAt(0).toUpperCase() + name.slice(1); 599 sizes[name] = new ScalarNumeric( 600 sizeInBytes_smallerIsBetter, size); 601 }); 602 } 603 } 604 605 // VM regions byte stats. 606 var vmRegions = dump.mostRecentVmRegions; 607 if (vmRegions !== undefined) { 608 tr.b.iterItems(UsedMemoryColumn.BYTE_STAT_MAP, 609 function(byteStatName, cellName) { 610 var byteStat = vmRegions.byteStats[byteStatName]; 611 if (byteStat === undefined) 612 return; 613 sizes[cellName] = new ScalarNumeric( 614 sizeInBytes_smallerIsBetter, byteStat); 615 }); 616 } 617 618 return sizes; 619 }); 620 621 // Allocator memory (v8, oilpan, ...). 622 var allocatorCells = tr.ui.analysis.createCells(timeToDump, 623 function(dump) { 624 var memoryAllocatorDumps = dump.memoryAllocatorDumps; 625 if (memoryAllocatorDumps === undefined) 626 return undefined; 627 var sizes = {}; 628 memoryAllocatorDumps.forEach(function(allocatorDump) { 629 var rootDisplayedSizeNumeric = allocatorDump.numerics[ 630 DISPLAYED_SIZE_NUMERIC_NAME]; 631 if (rootDisplayedSizeNumeric !== undefined) 632 sizes[allocatorDump.fullName] = rootDisplayedSizeNumeric; 633 }); 634 return sizes; 635 }); 636 637 return { 638 title: process.userFriendlyName, 639 contexts: timeToDump, 640 usedMemoryCells: usedMemoryCells, 641 allocatorCells: allocatorCells 642 }; 643 })); 644 }, 645 646 createFooterRows_: function(rows, columns) { 647 // Add a 'Total' row if there are at least two process memory dumps. 648 if (rows.length <= 1) 649 return []; 650 651 var totalRow = {title: 'Total'}; 652 tr.ui.analysis.aggregateTableRowCells(totalRow, rows, columns); 653 654 return [totalRow]; 655 }, 656 657 createColumns_: function(rows) { 658 var titleColumn = new ProcessNameColumn(); 659 titleColumn.width = '200px'; 660 661 var usedMemorySizeColumns = tr.ui.analysis.MemoryColumn.fromRows( 662 rows, 'usedMemoryCells', this.aggregationMode_, 663 UsedMemoryColumn.RULES); 664 665 var allocatorSizeColumns = tr.ui.analysis.MemoryColumn.fromRows( 666 rows, 'allocatorCells', this.aggregationMode_, 667 AllocatorColumn.RULES); 668 669 var sizeColumns = usedMemorySizeColumns.concat(allocatorSizeColumns); 670 tr.ui.analysis.MemoryColumn.spaceEqually(sizeColumns); 671 672 var columns = [titleColumn].concat(sizeColumns); 673 return columns; 674 }, 675 676 storeSelection_: function() { 677 var selectedRowTitle; 678 var selectedRow = this.$.table.selectedTableRow; 679 if (selectedRow !== undefined) 680 selectedRowTitle = selectedRow.title; 681 682 var selectedColumnName; 683 var selectedColumnIndex = this.$.table.selectedColumnIndex; 684 if (selectedColumnIndex !== undefined) { 685 var selectedColumn = this.$.table.tableColumns[selectedColumnIndex]; 686 selectedColumnName = selectedColumn.name; 687 } 688 689 this.$.state.set( 690 {rowTitle: selectedRowTitle, columnName: selectedColumnName}); 691 }, 692 693 restoreSelection_: function() { 694 var settings = this.$.state.get(); 695 if (settings === undefined || settings.rowTitle === undefined || 696 settings.columnName === undefined) 697 return; 698 699 var selectedColumnName = settings.columnName; 700 var selectedColumnIndex = tr.b.findFirstIndexInArray( 701 this.$.table.tableColumns, function(column) { 702 return column.name === selectedColumnName; 703 }); 704 if (selectedColumnIndex < 0) 705 return; 706 707 var selectedRowTitle = settings.rowTitle; 708 var selectedRow = tr.b.findFirstInArray(this.$.table.tableRows, 709 function(row) { 710 return row.title === selectedRowTitle; 711 }); 712 if (selectedRow === undefined) 713 return; 714 715 this.$.table.selectedTableRow = selectedRow; 716 this.$.table.selectedColumnIndex = selectedColumnIndex; 717 } 718 }); 719 720 return { 721 // All exports are for testing only. 722 ProcessNameColumn: ProcessNameColumn, 723 UsedMemoryColumn: UsedMemoryColumn, 724 PeakMemoryColumn: PeakMemoryColumn, 725 ByteStatColumn: ByteStatColumn, 726 AllocatorColumn: AllocatorColumn, 727 TracingColumn: TracingColumn 728 }; 729}); 730</script> 731