| <!DOCTYPE html> |
| <!-- |
| Copyright 2015 The Chromium Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. |
| --> |
| |
| <link rel="import" href="/tracing/base/color_scheme.html"> |
| <link rel="import" href="/tracing/base/iteration_helpers.html"> |
| <link rel="import" href="/tracing/base/multi_dimensional_view.html"> |
| <link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> |
| <link rel="import" href="/tracing/ui/analysis/stacked_pane.html"> |
| <link rel="import" href="/tracing/ui/base/dom_helpers.html"> |
| <link rel='import' href='/tracing/ui/base/info_bar.html'> |
| <link rel="import" href="/tracing/ui/base/table.html"> |
| <link rel="import" href="/tracing/value/numeric.html"> |
| <link rel="import" href="/tracing/value/unit.html"> |
| |
| <polymer-element name="tr-ui-a-memory-dump-heap-details-pane" |
| extends="tr-ui-a-stacked-pane"> |
| <template> |
| <style> |
| :host { |
| display: flex; |
| flex-direction: column; |
| } |
| |
| #header { |
| flex: 0 0 auto; |
| display: flex; |
| flex-direction: row; |
| align-items: center; |
| |
| background-color: #eee; |
| border-bottom: 1px solid #8e8e8e; |
| border-top: 1px solid white; |
| } |
| |
| #label { |
| flex: 1 1 auto; |
| padding: 8px; |
| font-size: 15px; |
| font-weight: bold; |
| } |
| |
| #view_mode_container { |
| display: none; |
| flex: 0 0 auto; |
| padding: 5px; |
| font-size: 15px; |
| } |
| |
| #contents { |
| flex: 1 0 auto; |
| align-self: stretch; |
| font-size: 12px; |
| } |
| |
| #info_text { |
| padding: 8px; |
| color: #666; |
| font-style: italic; |
| text-align: center; |
| } |
| |
| #table { |
| display: none; /* Hide until memory allocator dumps are set. */ |
| flex: 1 0 auto; |
| align-self: stretch; |
| } |
| </style> |
| <div id="header"> |
| <div id="label">Heap details</div> |
| <div id="view_mode_container"> |
| <span>View mode:</span> |
| <!-- View mode selector (added in Polymer.ready()) --> |
| </div> |
| </div> |
| <div id="contents"> |
| <tr-ui-b-info-bar id="info_bar" class="info-bar-hidden"> |
| </tr-ui-b-info-bar> |
| <div id="info_text">No heap dump selected</div> |
| <tr-ui-b-table id="table"></tr-ui-b-table> |
| </div> |
| </template> |
| </polymer-element> |
| <script> |
| 'use strict'; |
| |
| tr.exportTo('tr.ui.analysis', function() { |
| |
| var ScalarNumeric = tr.v.ScalarNumeric; |
| var sizeInBytes_smallerIsBetter = |
| tr.v.Unit.byName.sizeInBytes_smallerIsBetter; |
| |
| /** @{enum} */ |
| var RowDimension = { |
| ROOT: -1, |
| STACK_FRAME: 0, |
| OBJECT_TYPE: 1 |
| }; |
| |
| var LATIN_SMALL_LETTER_F_WITH_HOOK = String.fromCharCode(0x0192); |
| var CIRCLED_LATIN_CAPITAL_LETTER_T = String.fromCharCode(0x24C9); |
| |
| /** @{constructor} */ |
| function HeapDumpNodeTitleColumn(title) { |
| tr.ui.analysis.TitleColumn.call(this, title); |
| } |
| |
| HeapDumpNodeTitleColumn.prototype = { |
| __proto__: tr.ui.analysis.TitleColumn.prototype, |
| |
| formatTitle: function(row) { |
| var title = row.title; |
| var dimension = row.dimension; |
| switch (dimension) { |
| case RowDimension.ROOT: |
| return title; |
| |
| case RowDimension.STACK_FRAME: |
| case RowDimension.OBJECT_TYPE: |
| return this.formatSubRow_(title, dimension); |
| |
| default: |
| throw new Error('Invalid row dimension: ' + row.dimension); |
| } |
| }, |
| |
| cmp: function(rowA, rowB) { |
| if (rowA.dimension !== rowB.dimension) |
| return rowA.dimension - rowB.dimension; |
| return tr.ui.analysis.TitleColumn.prototype.cmp.call(this, rowA, rowB); |
| }, |
| |
| formatSubRow_: function(title, dimension) { |
| var titleEl = document.createElement('span'); |
| |
| var symbolEl = document.createElement('span'); |
| var symbolColorName; |
| if (dimension === RowDimension.STACK_FRAME) { |
| symbolEl.textContent = LATIN_SMALL_LETTER_F_WITH_HOOK; |
| symbolEl.title = 'Stack frame'; |
| symbolColorName = 'heap_dump_stack_frame'; |
| } else { |
| symbolEl.textContent = CIRCLED_LATIN_CAPITAL_LETTER_T; |
| symbolEl.title = 'Object type'; |
| symbolColorName = 'heap_dump_object_type'; |
| } |
| symbolEl.style.color = |
| tr.b.ColorScheme.getColorForReservedNameAsString(symbolColorName); |
| symbolEl.style.paddingRight = '4px'; |
| symbolEl.style.cursor = 'help'; |
| symbolEl.style.weight = 'bold'; |
| titleEl.appendChild(symbolEl); |
| |
| titleEl.appendChild(document.createTextNode(title)); |
| |
| return titleEl; |
| } |
| }; |
| |
| var COLUMN_RULES = [ |
| { |
| importance: 0, |
| columnConstructor: tr.ui.analysis.NumericMemoryColumn |
| } |
| ]; |
| |
| Polymer('tr-ui-a-memory-dump-heap-details-pane', { |
| created: function() { |
| this.heapDumps_ = undefined; |
| this.aggregationMode_ = undefined; |
| this.viewMode_ = undefined; |
| }, |
| |
| ready: function() { |
| this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW; |
| this.$.info_bar.message = 'Note: Values displayed in the heavy view ' + |
| 'are lower bounds (except for the root).'; |
| |
| this.$.view_mode_container.appendChild(tr.ui.b.createSelector( |
| this, 'viewMode', 'memoryDumpHeapDetailsPane.viewMode', |
| tr.b.MultiDimensionalViewType.TOP_DOWN_TREE_VIEW, |
| [ |
| { |
| label: 'Top-down (Tree)', |
| value: tr.b.MultiDimensionalViewType.TOP_DOWN_TREE_VIEW |
| }, |
| { |
| label: 'Top-down (Heavy)', |
| value: tr.b.MultiDimensionalViewType.TOP_DOWN_HEAVY_VIEW |
| }, |
| { |
| label: 'Bottom-up (Heavy)', |
| value: tr.b.MultiDimensionalViewType.BOTTOM_UP_HEAVY_VIEW |
| } |
| ])); |
| }, |
| |
| /** |
| * Sets the heap dumps and schedules rebuilding the pane. |
| * |
| * The provided value should be a chronological list of heap dumps. All |
| * dumps are assumed to belong to the same process and belong to the same |
| * allocator. Example: |
| * |
| * [ |
| * tr.model.HeapDump {}, // Heap dump at timestamp 1. |
| * undefined, // Heap dump not provided at timestamp 2. |
| * tr.model.HeapDump {}, // Heap dump at timestamp 3. |
| * ] |
| */ |
| set heapDumps(heapDumps) { |
| this.heapDumps_ = heapDumps; |
| this.scheduleRebuildPane_(); |
| }, |
| |
| get heapDumps() { |
| return this.heapDumps_; |
| }, |
| |
| set aggregationMode(aggregationMode) { |
| this.aggregationMode_ = aggregationMode; |
| this.scheduleRebuildPane_(); |
| }, |
| |
| get aggregationMode() { |
| return this.aggregationMode_; |
| }, |
| |
| set viewMode(viewMode) { |
| this.viewMode_ = viewMode; |
| this.scheduleRebuildPane_(); |
| }, |
| |
| get viewMode() { |
| return this.viewMode_; |
| }, |
| |
| get heavyView() { |
| switch (this.viewMode) { |
| case tr.b.MultiDimensionalViewType.TOP_DOWN_HEAVY_VIEW: |
| case tr.b.MultiDimensionalViewType.BOTTOM_UP_HEAVY_VIEW: |
| return true; |
| default: |
| return false; |
| } |
| }, |
| |
| rebuildPane_: function() { |
| if (this.heapDumps_ === undefined || |
| this.heapDumps_.length === 0) { |
| // Show the info text (hide the table and the view mode selector). |
| this.$.info_text.style.display = 'block'; |
| this.$.table.style.display = 'none'; |
| this.$.view_mode_container.style.display = 'none'; |
| this.$.info_bar.visible = false; |
| |
| this.$.table.clear(); |
| this.$.table.rebuild(); |
| return; |
| } |
| |
| // Show the table and the view mode selector (hide the info text). |
| this.$.info_text.style.display = 'none'; |
| this.$.table.style.display = 'block'; |
| this.$.view_mode_container.style.display = 'block'; |
| |
| // Show the info bar if in heavy view mode. |
| this.$.info_bar.visible = this.heavyView; |
| |
| var stackFrameTrees = this.createStackFrameTrees_(this.heapDumps_); |
| var rows = this.createRows_(stackFrameTrees); |
| var columns = this.createColumns_(rows); |
| |
| this.$.table.tableRows = rows; |
| this.$.table.tableColumns = columns; |
| this.$.table.rebuild(); |
| tr.ui.analysis.expandTableRowsRecursively(this.$.table); |
| }, |
| |
| createStackFrameTrees_: function(heapDumps) { |
| return heapDumps.map(function(heapDump) { |
| if (heapDump === undefined) |
| return undefined; |
| |
| var builder = new tr.b.MultiDimensionalViewBuilder(2 /* dimensions */); |
| |
| // Build the heap tree. |
| heapDump.entries.forEach(function(entry) { |
| var leafStackFrame = entry.leafStackFrame; |
| var stackTracePath = leafStackFrame === undefined ? |
| [] : leafStackFrame.getUserFriendlyStackTrace().reverse(); |
| |
| var objectTypeName = entry.objectTypeName; |
| var objectTypeNamePath = objectTypeName === undefined ? |
| [] : [objectTypeName]; |
| |
| builder.addPath([stackTracePath, objectTypeNamePath], entry.size, |
| tr.b.MultiDimensionalViewBuilder.ValueKind.TOTAL); |
| }, this); |
| |
| return builder.buildView(this.viewMode); |
| }, this); |
| }, |
| |
| createRows_: function(stackFrameTrees) { |
| var definedHeapDump = tr.b.findFirstInArray(this.heapDumps); |
| if (definedHeapDump === undefined) |
| return []; |
| |
| // The title of the root row is the name of the allocator. |
| var rootRowTitle = definedHeapDump.allocatorName; |
| return [this.createHeapRowRecursively_( |
| stackFrameTrees, RowDimension.ROOT, rootRowTitle)]; |
| }, |
| |
| createHeapRowRecursively_: function(nodes, dimension, title) { |
| // Transform a chronological list of stack frame tree nodes into a |
| // dictionary of cells (where each cell contains a chronological list |
| // of the values of its numeric). |
| var cells = tr.ui.analysis.createCells(nodes, function(node) { |
| return { |
| 'Size': new ScalarNumeric(sizeInBytes_smallerIsBetter, node.total) |
| }; |
| }); |
| |
| var row = { |
| dimension: dimension, |
| title: title, |
| contexts: nodes, |
| cells: cells |
| }; |
| |
| // Recursively create sub-rows for children (if applicable). |
| var stackFrameSubRows = this.createHeapDimensionSubRowsRecursively_( |
| nodes, RowDimension.STACK_FRAME); |
| var objectTypeSubRows = this.createHeapDimensionSubRowsRecursively_( |
| nodes, RowDimension.OBJECT_TYPE); |
| var subRows = stackFrameSubRows.concat(objectTypeSubRows); |
| if (subRows.length > 0) |
| row.subRows = subRows; |
| |
| return row; |
| }, |
| |
| createHeapDimensionSubRowsRecursively_: function(nodes, dimension) { |
| // Sub-row name (list index) -> Timestamp (list index) -> Child |
| // MultiDimensionalViewNode. |
| var dimensionGroupedChildNodes = tr.b.dictionaryValues( |
| tr.b.invertArrayOfDicts(nodes, function(node) { |
| var childDict = {}; |
| var displayedChildrenTotal = 0; |
| var hasDisplayedChildren = false; |
| for (var child of node.children[dimension].values()) { |
| // Don't show lower-bound sub-rows in tree-view. |
| if (!this.heavyView && child.isLowerBound) |
| continue; |
| childDict[child.title[dimension]] = child; |
| displayedChildrenTotal += child.total; |
| hasDisplayedChildren = true; |
| } |
| |
| // Add '<other>' node if necessary in tree-view. |
| if (!this.heavyView && displayedChildrenTotal < node.total && |
| hasDisplayedChildren) { |
| var otherTitle = node.title.slice(); |
| otherTitle[dimension] = '<other>'; |
| childDict['<other>'] = { |
| title: otherTitle, |
| total: node.total - displayedChildrenTotal, |
| children: [new Map(), new Map()] |
| }; |
| } |
| |
| return childDict; |
| }, this)); |
| |
| // Sub-row name (list index) -> Sub-row. |
| return dimensionGroupedChildNodes.map(function(subRowNodes) { |
| var subRowTitle = tr.b.findFirstInArray(subRowNodes).title[dimension]; |
| return this.createHeapRowRecursively_( |
| subRowNodes, dimension, subRowTitle); |
| }, this); |
| }, |
| |
| createColumns_: function(rows) { |
| var titleColumn = new HeapDumpNodeTitleColumn('Stack frame'); |
| titleColumn.width = '500px'; |
| |
| var numericColumns = tr.ui.analysis.MemoryColumn.fromRows( |
| rows, 'cells', this.aggregationMode_, COLUMN_RULES); |
| tr.ui.analysis.MemoryColumn.spaceEqually(numericColumns); |
| |
| var columns = [titleColumn].concat(numericColumns); |
| return columns; |
| } |
| }); |
| |
| return { |
| RowDimension: RowDimension // Exported for testing. |
| }; |
| }); |
| </script> |