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