1<!DOCTYPE html>
2<!--
3Copyright 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/base/multi_dimensional_view.html">
11<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html">
12<link rel="import" href="/tracing/ui/analysis/stacked_pane.html">
13<link rel="import" href="/tracing/ui/base/dom_helpers.html">
14<link rel='import' href='/tracing/ui/base/info_bar.html'>
15<link rel="import" href="/tracing/ui/base/table.html">
16<link rel="import" href="/tracing/value/numeric.html">
17<link rel="import" href="/tracing/value/unit.html">
18
19<polymer-element name="tr-ui-a-memory-dump-heap-details-pane"
20    extends="tr-ui-a-stacked-pane">
21  <template>
22    <style>
23      :host {
24        display: flex;
25        flex-direction: column;
26      }
27
28      #header {
29        flex: 0 0 auto;
30        display: flex;
31        flex-direction: row;
32        align-items: center;
33
34        background-color: #eee;
35        border-bottom: 1px solid #8e8e8e;
36        border-top: 1px solid white;
37      }
38
39      #label {
40        flex: 1 1 auto;
41        padding: 8px;
42        font-size:  15px;
43        font-weight: bold;
44      }
45
46      #view_mode_container {
47        display: none;
48        flex: 0 0 auto;
49        padding: 5px;
50        font-size: 15px;
51      }
52
53      #contents {
54        flex: 1 0 auto;
55        align-self: stretch;
56        font-size: 12px;
57      }
58
59      #info_text {
60        padding: 8px;
61        color: #666;
62        font-style: italic;
63        text-align: center;
64      }
65
66      #table {
67        display: none;  /* Hide until memory allocator dumps are set. */
68        flex: 1 0 auto;
69        align-self: stretch;
70      }
71    </style>
72    <div id="header">
73      <div id="label">Heap details</div>
74      <div id="view_mode_container">
75        <span>View mode:</span>
76        <!-- View mode selector (added in Polymer.ready()) -->
77      </div>
78    </div>
79    <div id="contents">
80      <tr-ui-b-info-bar id="info_bar" class="info-bar-hidden">
81      </tr-ui-b-info-bar>
82      <div id="info_text">No heap dump selected</div>
83      <tr-ui-b-table id="table"></tr-ui-b-table>
84    </div>
85  </template>
86</polymer-element>
87<script>
88'use strict';
89
90tr.exportTo('tr.ui.analysis', function() {
91
92  var ScalarNumeric = tr.v.ScalarNumeric;
93  var sizeInBytes_smallerIsBetter =
94      tr.v.Unit.byName.sizeInBytes_smallerIsBetter;
95
96  /** @{enum} */
97  var RowDimension = {
98    ROOT: -1,
99    STACK_FRAME: 0,
100    OBJECT_TYPE: 1
101  };
102
103  var LATIN_SMALL_LETTER_F_WITH_HOOK = String.fromCharCode(0x0192);
104  var CIRCLED_LATIN_CAPITAL_LETTER_T = String.fromCharCode(0x24C9);
105
106  /** @{constructor} */
107  function HeapDumpNodeTitleColumn(title) {
108    tr.ui.analysis.TitleColumn.call(this, title);
109  }
110
111  HeapDumpNodeTitleColumn.prototype = {
112    __proto__: tr.ui.analysis.TitleColumn.prototype,
113
114    formatTitle: function(row) {
115      var title = row.title;
116      var dimension = row.dimension;
117      switch (dimension) {
118        case RowDimension.ROOT:
119          return title;
120
121        case RowDimension.STACK_FRAME:
122        case RowDimension.OBJECT_TYPE:
123          return this.formatSubRow_(title, dimension);
124
125        default:
126          throw new Error('Invalid row dimension: ' + row.dimension);
127      }
128    },
129
130    cmp: function(rowA, rowB) {
131      if (rowA.dimension !== rowB.dimension)
132        return rowA.dimension - rowB.dimension;
133      return tr.ui.analysis.TitleColumn.prototype.cmp.call(this, rowA, rowB);
134    },
135
136    formatSubRow_: function(title, dimension) {
137      var titleEl = document.createElement('span');
138
139      var symbolEl = document.createElement('span');
140      var symbolColorName;
141      if (dimension === RowDimension.STACK_FRAME) {
142        symbolEl.textContent = LATIN_SMALL_LETTER_F_WITH_HOOK;
143        symbolEl.title = 'Stack frame';
144        symbolColorName = 'heap_dump_stack_frame';
145      } else {
146        symbolEl.textContent = CIRCLED_LATIN_CAPITAL_LETTER_T;
147        symbolEl.title = 'Object type';
148        symbolColorName = 'heap_dump_object_type';
149      }
150      symbolEl.style.color =
151          tr.b.ColorScheme.getColorForReservedNameAsString(symbolColorName);
152      symbolEl.style.paddingRight = '4px';
153      symbolEl.style.cursor = 'help';
154      symbolEl.style.weight = 'bold';
155      titleEl.appendChild(symbolEl);
156
157      titleEl.appendChild(document.createTextNode(title));
158
159      return titleEl;
160    }
161  };
162
163  var COLUMN_RULES = [
164    {
165      importance: 0,
166      columnConstructor: tr.ui.analysis.NumericMemoryColumn
167    }
168  ];
169
170  Polymer('tr-ui-a-memory-dump-heap-details-pane', {
171    created: function() {
172      this.heapDumps_ = undefined;
173      this.aggregationMode_ = undefined;
174      this.viewMode_ = undefined;
175    },
176
177    ready: function() {
178      this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW;
179      this.$.info_bar.message = 'Note: Values displayed in the heavy view ' +
180          'are lower bounds (except for the root).';
181
182      this.$.view_mode_container.appendChild(tr.ui.b.createSelector(
183          this, 'viewMode', 'memoryDumpHeapDetailsPane.viewMode',
184          tr.b.MultiDimensionalViewType.TOP_DOWN_TREE_VIEW,
185          [
186            {
187              label: 'Top-down (Tree)',
188              value: tr.b.MultiDimensionalViewType.TOP_DOWN_TREE_VIEW
189            },
190            {
191              label: 'Top-down (Heavy)',
192              value: tr.b.MultiDimensionalViewType.TOP_DOWN_HEAVY_VIEW
193            },
194            {
195              label: 'Bottom-up (Heavy)',
196              value: tr.b.MultiDimensionalViewType.BOTTOM_UP_HEAVY_VIEW
197            }
198          ]));
199    },
200
201    /**
202     * Sets the heap dumps and schedules rebuilding the pane.
203     *
204     * The provided value should be a chronological list of heap dumps. All
205     * dumps are assumed to belong to the same process and belong to the same
206     * allocator. Example:
207     *
208     *   [
209     *     tr.model.HeapDump {},  // Heap dump at timestamp 1.
210     *     undefined,  // Heap dump not provided at timestamp 2.
211     *     tr.model.HeapDump {},  // Heap dump at timestamp 3.
212     *   ]
213     */
214    set heapDumps(heapDumps) {
215      this.heapDumps_ = heapDumps;
216      this.scheduleRebuildPane_();
217    },
218
219    get heapDumps() {
220      return this.heapDumps_;
221    },
222
223    set aggregationMode(aggregationMode) {
224      this.aggregationMode_ = aggregationMode;
225      this.scheduleRebuildPane_();
226    },
227
228    get aggregationMode() {
229      return this.aggregationMode_;
230    },
231
232    set viewMode(viewMode) {
233      this.viewMode_ = viewMode;
234      this.scheduleRebuildPane_();
235    },
236
237    get viewMode() {
238      return this.viewMode_;
239    },
240
241    get heavyView() {
242      switch (this.viewMode) {
243        case tr.b.MultiDimensionalViewType.TOP_DOWN_HEAVY_VIEW:
244        case tr.b.MultiDimensionalViewType.BOTTOM_UP_HEAVY_VIEW:
245          return true;
246        default:
247          return false;
248      }
249    },
250
251    rebuildPane_: function() {
252      if (this.heapDumps_ === undefined ||
253          this.heapDumps_.length === 0) {
254        // Show the info text (hide the table and the view mode selector).
255        this.$.info_text.style.display = 'block';
256        this.$.table.style.display = 'none';
257        this.$.view_mode_container.style.display = 'none';
258        this.$.info_bar.visible = false;
259
260        this.$.table.clear();
261        this.$.table.rebuild();
262        return;
263      }
264
265      // Show the table and the view mode selector (hide the info text).
266      this.$.info_text.style.display = 'none';
267      this.$.table.style.display = 'block';
268      this.$.view_mode_container.style.display = 'block';
269
270      // Show the info bar if in heavy view mode.
271      this.$.info_bar.visible = this.heavyView;
272
273      var stackFrameTrees = this.createStackFrameTrees_(this.heapDumps_);
274      var rows = this.createRows_(stackFrameTrees);
275      var columns = this.createColumns_(rows);
276
277      this.$.table.tableRows = rows;
278      this.$.table.tableColumns = columns;
279      this.$.table.rebuild();
280      tr.ui.analysis.expandTableRowsRecursively(this.$.table);
281    },
282
283    createStackFrameTrees_: function(heapDumps) {
284      return heapDumps.map(function(heapDump) {
285        if (heapDump === undefined)
286          return undefined;
287
288        var builder = new tr.b.MultiDimensionalViewBuilder(2 /* dimensions */);
289
290        // Build the heap tree.
291        heapDump.entries.forEach(function(entry) {
292          var leafStackFrame = entry.leafStackFrame;
293          var stackTracePath = leafStackFrame === undefined ?
294              [] : leafStackFrame.getUserFriendlyStackTrace().reverse();
295
296          var objectTypeName = entry.objectTypeName;
297          var objectTypeNamePath = objectTypeName === undefined ?
298              [] : [objectTypeName];
299
300          builder.addPath([stackTracePath, objectTypeNamePath], entry.size,
301              tr.b.MultiDimensionalViewBuilder.ValueKind.TOTAL);
302        }, this);
303
304        return builder.buildView(this.viewMode);
305      }, this);
306    },
307
308    createRows_: function(stackFrameTrees) {
309      var definedHeapDump = tr.b.findFirstInArray(this.heapDumps);
310      if (definedHeapDump === undefined)
311        return [];
312
313      // The title of the root row is the name of the allocator.
314      var rootRowTitle = definedHeapDump.allocatorName;
315      return [this.createHeapRowRecursively_(
316          stackFrameTrees, RowDimension.ROOT, rootRowTitle)];
317    },
318
319    createHeapRowRecursively_: function(nodes, dimension, title) {
320      // Transform a chronological list of stack frame tree nodes into a
321      // dictionary of cells (where each cell contains a chronological list
322      // of the values of its numeric).
323      var cells = tr.ui.analysis.createCells(nodes, function(node) {
324        return {
325          'Size': new ScalarNumeric(sizeInBytes_smallerIsBetter, node.total)
326        };
327      });
328
329      var row = {
330        dimension: dimension,
331        title: title,
332        contexts: nodes,
333        cells: cells
334      };
335
336      // Recursively create sub-rows for children (if applicable).
337      var stackFrameSubRows = this.createHeapDimensionSubRowsRecursively_(
338          nodes, RowDimension.STACK_FRAME);
339      var objectTypeSubRows = this.createHeapDimensionSubRowsRecursively_(
340          nodes, RowDimension.OBJECT_TYPE);
341      var subRows = stackFrameSubRows.concat(objectTypeSubRows);
342      if (subRows.length > 0)
343        row.subRows = subRows;
344
345      return row;
346    },
347
348    createHeapDimensionSubRowsRecursively_: function(nodes, dimension) {
349      // Sub-row name (list index) -> Timestamp (list index) -> Child
350      // MultiDimensionalViewNode.
351      var dimensionGroupedChildNodes = tr.b.dictionaryValues(
352          tr.b.invertArrayOfDicts(nodes, function(node) {
353            var childDict = {};
354            var displayedChildrenTotal = 0;
355            var hasDisplayedChildren = false;
356            for (var child of node.children[dimension].values()) {
357              // Don't show lower-bound sub-rows in tree-view.
358              if (!this.heavyView && child.isLowerBound)
359                continue;
360              childDict[child.title[dimension]] = child;
361              displayedChildrenTotal += child.total;
362              hasDisplayedChildren = true;
363            }
364
365            // Add '<other>' node if necessary in tree-view.
366            if (!this.heavyView && displayedChildrenTotal < node.total &&
367                hasDisplayedChildren) {
368              var otherTitle = node.title.slice();
369              otherTitle[dimension] = '<other>';
370              childDict['<other>'] = {
371                title: otherTitle,
372                total: node.total - displayedChildrenTotal,
373                children: [new Map(), new Map()]
374              };
375            }
376
377            return childDict;
378          }, this));
379
380      // Sub-row name (list index) -> Sub-row.
381      return dimensionGroupedChildNodes.map(function(subRowNodes) {
382        var subRowTitle = tr.b.findFirstInArray(subRowNodes).title[dimension];
383        return this.createHeapRowRecursively_(
384            subRowNodes, dimension, subRowTitle);
385      }, this);
386    },
387
388    createColumns_: function(rows) {
389      var titleColumn = new HeapDumpNodeTitleColumn('Stack frame');
390      titleColumn.width = '500px';
391
392      var numericColumns = tr.ui.analysis.MemoryColumn.fromRows(
393          rows, 'cells', this.aggregationMode_, COLUMN_RULES);
394      tr.ui.analysis.MemoryColumn.spaceEqually(numericColumns);
395
396      var columns = [titleColumn].concat(numericColumns);
397      return columns;
398    }
399  });
400
401  return {
402    RowDimension: RowDimension  // Exported for testing.
403  };
404});
405</script>
406