| <!DOCTYPE html> |
| <!-- |
| Copyright (c) 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/model/attribute.html"> |
| <link rel="import" href="/tracing/model/memory_allocator_dump.html"> |
| <link rel="import" |
| href="/tracing/ui/analysis/memory_dump_allocator_details_pane.html"> |
| <link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> |
| <link rel="import" |
| href="/tracing/ui/analysis/memory_dump_vm_regions_details_pane.html"> |
| <link rel="import" href="/tracing/ui/analysis/stacked_pane.html"> |
| <link rel="import" href="/tracing/ui/base/color_legend.html"> |
| <link rel="import" href="/tracing/ui/base/dom_helpers.html"> |
| <link rel="import" href="/tracing/ui/base/table.html"> |
| <link rel="import" href="/tracing/ui/view_specific_brushing_state.html"> |
| |
| <polymer-element name="tr-ui-a-memory-dump-overview-pane" |
| extends="tr-ui-a-stacked-pane"> |
| <template> |
| <style> |
| :host { |
| display: flex; |
| flex-direction: column; |
| } |
| |
| #label { |
| flex: 0 0 auto; |
| padding: 8px; |
| |
| background-color: #eee; |
| border-bottom: 1px solid #8e8e8e; |
| border-top: 1px solid white; |
| |
| font-size: 15px; |
| font-weight: bold; |
| } |
| |
| #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 dumps are set. */ |
| flex: 1 0 auto; |
| align-self: stretch; |
| } |
| </style> |
| <tr-ui-b-view-specific-brushing-state id="state" |
| view-id="analysis.memory_dump_overview_pane"> |
| </tr-ui-b-view-specific-brushing-state> |
| <div id="label">Overview</div> |
| <div id="contents"> |
| <div id="info_text">No memory memory dumps 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 ColorScheme = tr.b.ColorScheme; |
| |
| var PLATFORM_SPECIFIC_TOTAL_NAME_SUFFIX = '_bytes'; |
| |
| var DISPLAYED_SIZE_ATTRIBUTE_NAME = |
| tr.model.MemoryAllocatorDump.DISPLAYED_SIZE_ATTRIBUTE_NAME; |
| |
| var GREATER_THAN_OR_EQUAL_TO_SYMBOL = String.fromCharCode(8805); |
| |
| /** @constructor */ |
| function ProcessNameColumn(title) { |
| tr.ui.analysis.TitleColumn.call(this, title); |
| } |
| |
| ProcessNameColumn.prototype = { |
| __proto__: tr.ui.analysis.TitleColumn.prototype, |
| |
| formatTitle: function(row) { |
| if (row.noLegend) |
| return row.title; |
| var titleEl = document.createElement('tr-ui-b-color-legend'); |
| titleEl.label = row.title; |
| return titleEl; |
| } |
| }; |
| |
| /** @constructor */ |
| function UsedMemoryColumn(name, units, cellGetter, aggregationMode) { |
| tr.ui.analysis.ScalarMemoryColumn.call( |
| this, name, units, cellGetter, aggregationMode); |
| } |
| |
| var USED_MEMORY_COLUMN_COLOR = |
| ColorScheme.getColorForReservedNameAsString('used_memory_column'); |
| var OLDER_USED_MEMORY_COLUMN_COLOR = |
| ColorScheme.getColorForReservedNameAsString('older_used_memory_column'); |
| |
| UsedMemoryColumn.prototype = { |
| __proto__: tr.ui.analysis.ScalarMemoryColumn.prototype, |
| |
| get title() { |
| return tr.ui.b.createSpan( |
| {textContent: this.name, color: USED_MEMORY_COLUMN_COLOR}); |
| }, |
| |
| color: function(attrs) { |
| // TODO(petrcermak): Figure out how to make this work for multi- |
| // selection as well. |
| if (attrs.length === 1 && attrs[0].isOlderValue) |
| return OLDER_USED_MEMORY_COLUMN_COLOR; |
| else |
| return USED_MEMORY_COLUMN_COLOR; |
| } |
| }; |
| |
| /** @constructor */ |
| function AllocatorColumn(name, units, cellGetter, aggregationMode) { |
| tr.ui.analysis.ScalarMemoryColumn.call( |
| this, name, units, cellGetter, aggregationMode); |
| } |
| |
| AllocatorColumn.prototype = { |
| __proto__: tr.ui.analysis.ScalarMemoryColumn.prototype, |
| |
| get title() { |
| var titleEl = document.createElement('tr-ui-b-color-legend'); |
| titleEl.label = this.name; |
| return titleEl; |
| }, |
| |
| getInfos: function(attrs) { |
| // Show the heap dump icon if at least one of the attributes has an |
| // associated heap dump. |
| var hasDumpInfo = undefined; |
| attrs.some(function(attr) { |
| if (attr === undefined) |
| return false; |
| return attr.infos.some(function(info) { |
| if (info.type !== tr.model.AttributeInfoType.HAS_HEAP_DUMP) |
| return false; |
| hasDumpInfo = info; |
| return true; |
| }); |
| }); |
| |
| if (hasDumpInfo !== undefined) |
| return [hasDumpInfo]; |
| else |
| return []; |
| } |
| }; |
| |
| /** @constructor */ |
| function TracingColumn(name, units, cellGetter, aggregationMode) { |
| tr.ui.analysis.ScalarMemoryColumn.call( |
| this, name, units, cellGetter, aggregationMode); |
| } |
| |
| var TRACING_COLUMN_COLOR = |
| ColorScheme.getColorForReservedNameAsString('tracing_memory_column'); |
| |
| TracingColumn.prototype = { |
| __proto__: tr.ui.analysis.ScalarMemoryColumn.prototype, |
| |
| get title() { |
| return tr.ui.b.createSpan( |
| {textContent: this.name, color: TRACING_COLUMN_COLOR}); |
| }, |
| |
| color: TRACING_COLUMN_COLOR |
| }; |
| |
| // Rules for constructing and sorting used memory columns. |
| var USED_MEMORY_SIZE_COLUMNS_CONSTRUCTOR_RULES = [ |
| { |
| columnConstructor: UsedMemoryColumn |
| } |
| ]; |
| var USED_MEMORY_SIZE_COLUMNS_IMPORTANCE_RULES = |
| tr.ui.analysis.MemoryColumn.columnNamesToImportanceRules([ |
| 'Total resident', |
| 'Peak total resident', |
| 'PSS', |
| 'Private dirty', |
| 'Swapped']); |
| |
| // Rules for constructing and sorting allocator columns. |
| var ALLOCATOR_SIZE_COLUMNS_CONSTRUCTOR_RULES = [ |
| { |
| condition: 'tracing', |
| columnConstructor: TracingColumn |
| }, |
| { |
| columnConstructor: AllocatorColumn |
| } |
| ]; |
| var ALLOCATOR_SIZE_COLUMNS_IMPORTANCE_RULES = [ |
| { |
| condition: 'tracing', |
| importance: 0 |
| }, |
| { |
| importance: 1 |
| } |
| ]; |
| |
| Polymer('tr-ui-a-memory-dump-overview-pane', { |
| created: function() { |
| this.processMemoryDumps_ = undefined; |
| this.aggregationMode_ = undefined; |
| }, |
| |
| ready: function() { |
| this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.CELL; |
| this.$.table.addEventListener('selection-changed', |
| function(tableEvent) { |
| tableEvent.stopPropagation(); |
| this.changeChildPane_(); |
| }.bind(this)); |
| }, |
| |
| /** |
| * Sets the process memory dumps and schedules rebuilding the pane. |
| * |
| * The provided value should be a chronological list of dictionaries |
| * mapping process IDs to process memory dumps. Example: |
| * |
| * [ |
| * { |
| * // PMDs at timestamp 1. |
| * 42: tr.model.ProcessMemoryDump {} |
| * }, |
| * { |
| * // PMDs at timestamp 2. |
| * 42: tr.model.ProcessMemoryDump {}, |
| * 89: tr.model.ProcessMemoryDump {} |
| * } |
| * ] |
| */ |
| set processMemoryDumps(processMemoryDumps) { |
| this.processMemoryDumps_ = processMemoryDumps; |
| this.scheduleRebuildPane_(); |
| }, |
| |
| get processMemoryDumps() { |
| return this.processMemoryDumps_; |
| }, |
| |
| set aggregationMode(aggregationMode) { |
| this.aggregationMode_ = aggregationMode; |
| this.scheduleRebuildPane_(); |
| }, |
| |
| get aggregationMode() { |
| return this.aggregationMode_; |
| }, |
| |
| get selectedMemoryCell() { |
| if (this.processMemoryDumps_ === undefined || |
| this.processMemoryDumps_.length === 0) { |
| return undefined; |
| } |
| |
| var selectedTableRow = this.$.table.selectedTableRow; |
| if (!selectedTableRow) |
| return undefined; |
| |
| var selectedColumnIndex = this.$.table.selectedColumnIndex; |
| if (selectedColumnIndex === undefined) |
| return undefined; |
| |
| var selectedColumn = this.$.table.tableColumns[selectedColumnIndex]; |
| var selectedMemoryCell = selectedColumn.cell(selectedTableRow); |
| return selectedMemoryCell; |
| }, |
| |
| changeChildPane_: function() { |
| this.storeSelection_(); |
| var builder = undefined; |
| if (this.selectedMemoryCell !== undefined) |
| builder = this.selectedMemoryCell.buildDetailsPane; |
| this.childPaneBuilder = builder; |
| }, |
| |
| rebuildPane_: function() { |
| if (this.processMemoryDumps_ === undefined || |
| this.processMemoryDumps_.length === 0) { |
| // Show the info text (hide the table). |
| this.$.info_text.style.display = 'block'; |
| this.$.table.style.display = 'none'; |
| |
| this.$.table.clear(); |
| this.$.table.rebuild(); |
| return; |
| } |
| |
| // Show the table (hide the info text). |
| this.$.info_text.style.display = 'none'; |
| this.$.table.style.display = 'block'; |
| |
| var rows = this.createRows_(); |
| var footerRows = this.createFooterRows_(rows); |
| var columns = this.createColumns_(rows); |
| |
| this.$.table.tableRows = rows; |
| this.$.table.footerRows = footerRows; |
| this.$.table.tableColumns = columns; |
| this.$.table.rebuild(); |
| |
| this.restoreSelection_(); |
| }, |
| |
| createRows_: function() { |
| // Timestamp (list index) -> Process ID (dict key) -> PMD. |
| var timeToPidToProcessMemoryDump = this.processMemoryDumps_; |
| |
| // Process ID (dict key) -> Timestamp (list index) -> PMD or undefined. |
| var pidToTimeToProcessMemoryDump = tr.b.invertArrayOfDicts( |
| timeToPidToProcessMemoryDump); |
| |
| // Process (list index) -> Component (dict key) -> Cell. |
| var rows = []; |
| |
| var aggregationMode = this.aggregationMode_; |
| return tr.b.dictionaryValues(tr.b.mapItems( |
| pidToTimeToProcessMemoryDump, function(pid, timeToDump) { |
| // Get the process associated with the dumps. We can use any defined |
| // process memory dump in timeToDump since they all have the same pid. |
| var process = tr.b.findFirstInArray(timeToDump).process; |
| |
| // Determine at which timestamps (indices of the current selection) |
| // the dump was provided. |
| var defined = timeToDump.map(function(dump) { |
| return dump !== undefined; |
| }); |
| |
| // Used memory (total resident, PSS, ...). |
| var timeToVmRegions = timeToDump.map(function(dump) { |
| if (dump === undefined) |
| return undefined; |
| return dump.mostRecentVmRegions; |
| }); |
| function buildVmRegionsPane() { |
| var pane = document.createElement( |
| 'tr-ui-a-memory-dump-vm-regions-details-pane'); |
| pane.vmRegions = timeToVmRegions; |
| pane.aggregationMode = aggregationMode; |
| return pane; |
| } |
| var usedMemoryCells = tr.ui.analysis.createCells(timeToDump, |
| function(dump) { |
| var sizes = {}; |
| |
| // Totals. |
| var totals = dump.totals; |
| if (totals !== undefined) { |
| tr.ui.analysis.addAttributeIfDefined( |
| sizes, 'Total resident', tr.model.ScalarAttribute, 'bytes', |
| totals.residentBytes); |
| tr.ui.analysis.addAttributeIfDefined( |
| sizes, 'Peak total resident', tr.model.ScalarAttribute, |
| 'bytes', totals.peakResidentBytes, function(attr) { |
| if (dump.totals.arePeakResidentBytesResettable) { |
| attr.infos.push(new tr.model.AttributeInfo( |
| tr.model.AttributeInfoType.RECENT_VALUE, |
| 'Peak RSS since previous memory dump.')); |
| } else { |
| attr.infos.push(new tr.model.AttributeInfo( |
| tr.model.AttributeInfoType.OVERALL_VALUE, |
| 'Peak RSS since process startup. Finer grained ' + |
| 'peaks require a Linux kernel version ' + |
| GREATER_THAN_OR_EQUAL_TO_SYMBOL + ' 4.0.')); |
| } |
| }); |
| |
| // Platform-specific totals (e.g. private resident on Mac). |
| var platformSpecific = totals.platformSpecific; |
| if (platformSpecific !== undefined) { |
| tr.b.iterItems(platformSpecific, function(name, size) { |
| // Change raw OS-specific total name to a user-friendly |
| // column title (e.g. 'private_bytes' -> 'Private'). |
| if (name.endsWith(PLATFORM_SPECIFIC_TOTAL_NAME_SUFFIX)) { |
| name = name.substring(0, name.length - |
| PLATFORM_SPECIFIC_TOTAL_NAME_SUFFIX.length); |
| } |
| name = name.replace('_', ' ').trim(); |
| name = name.charAt(0).toUpperCase() + name.slice(1); |
| sizes[name] = new tr.model.ScalarAttribute('bytes', size); |
| }); |
| } |
| } |
| |
| // VM regions byte stats. |
| var vmRegionAttributeAddedCallback = undefined; |
| if (!dump.hasOwnVmRegions) { |
| vmRegionAttributeAddedCallback = function(attr) { |
| attr.infos.push(new tr.model.AttributeInfo( |
| tr.model.AttributeInfoType.LINK, |
| 'Older value (process did not dump memory maps).')); |
| attr.isOlderValue = true; |
| }; |
| } |
| tr.ui.analysis.addAttributeIfDefined( |
| sizes, 'PSS', tr.model.ScalarAttribute, 'bytes', |
| dump.getMostRecentTotalVmRegionStat( |
| 'proportionalResident'), |
| vmRegionAttributeAddedCallback); |
| tr.ui.analysis.addAttributeIfDefined( |
| sizes, 'Private dirty', tr.model.ScalarAttribute, 'bytes', |
| dump.getMostRecentTotalVmRegionStat( |
| 'privateDirtyResident'), |
| vmRegionAttributeAddedCallback); |
| tr.ui.analysis.addAttributeIfDefined( |
| sizes, 'Swapped', tr.model.ScalarAttribute, 'bytes', |
| dump.getMostRecentTotalVmRegionStat('swapped'), |
| vmRegionAttributeAddedCallback); |
| |
| return sizes; |
| }, |
| function(attrName, cell) { |
| cell.buildDetailsPane = buildVmRegionsPane; |
| }); |
| |
| // Allocator memory (v8, oilpan, ...). |
| var allocatorCells = tr.ui.analysis.createCells(timeToDump, |
| function(dump) { |
| if (dump.memoryAllocatorDumps === undefined) |
| return undefined; |
| var sizes = {}; |
| dump.memoryAllocatorDumps.forEach(function(allocatorDump) { |
| var rootAttribute = allocatorDump.attributes[ |
| DISPLAYED_SIZE_ATTRIBUTE_NAME]; |
| if (rootAttribute === undefined) |
| return; |
| var allocatorName = allocatorDump.fullName; |
| // Clone the attribute so that we could provide custom infos |
| // (instead of the original ones). |
| var overviewAttribute = new rootAttribute.constructor( |
| rootAttribute.units, rootAttribute.value); |
| if (dump.heapDumps !== undefined && |
| dump.heapDumps[allocatorName] !== undefined) { |
| overviewAttribute.infos.push(new tr.model.AttributeInfo( |
| tr.model.AttributeInfoType.HAS_HEAP_DUMP, |
| 'Heap dump provided')); |
| } |
| sizes[allocatorName] = overviewAttribute; |
| }); |
| return sizes; |
| }, |
| function(allocatorName, cell) { |
| var memoryAllocatorDumps = timeToDump.map(function(dump) { |
| if (dump === undefined) |
| return undefined; |
| return dump.getMemoryAllocatorDumpByFullName(allocatorName); |
| }); |
| // Lazily construct the list of heap dumps if a heap dump is |
| // encountered. |
| var heapDumps = undefined; |
| timeToDump.forEach(function(dump, index) { |
| if (dump === undefined || dump.heapDumps === undefined) |
| return; |
| if (heapDumps === undefined) |
| heapDumps = new Array(timeToDump.length); |
| heapDumps[index] = dump.heapDumps[allocatorName]; |
| }); |
| cell.buildDetailsPane = function() { |
| var pane = document.createElement( |
| 'tr-ui-a-memory-dump-allocator-details-pane'); |
| pane.memoryAllocatorDumps = memoryAllocatorDumps; |
| pane.heapDumps = heapDumps; |
| pane.aggregationMode = aggregationMode; |
| return pane; |
| }; |
| }); |
| |
| return { |
| title: process.userFriendlyName, |
| defined: defined, |
| usedMemoryCells: usedMemoryCells, |
| allocatorCells: allocatorCells |
| }; |
| })); |
| }, |
| |
| createFooterRows_: function(rows) { |
| // Add a 'Total' row if there are at least two process memory dumps. |
| if (rows.length <= 1) |
| return []; |
| |
| var totalRow = { |
| title: 'Total', |
| noLegend: true |
| }; |
| |
| tr.ui.analysis.aggregateTableRowCells( |
| totalRow, rows, 'usedMemoryCells'); |
| tr.ui.analysis.aggregateTableRowCells(totalRow, rows, 'allocatorCells'); |
| |
| return [totalRow]; |
| }, |
| |
| createColumns_: function(rows) { |
| var titleColumn = new ProcessNameColumn('Process'); |
| titleColumn.width = '200px'; |
| |
| var usedMemorySizeColumns = tr.ui.analysis.MemoryColumn.fromRows( |
| rows, 'usedMemoryCells', this.aggregationMode_, |
| USED_MEMORY_SIZE_COLUMNS_CONSTRUCTOR_RULES); |
| tr.ui.analysis.MemoryColumn.sortByImportance( |
| usedMemorySizeColumns, USED_MEMORY_SIZE_COLUMNS_IMPORTANCE_RULES); |
| |
| var allocatorSizeColumns = tr.ui.analysis.MemoryColumn.fromRows( |
| rows, 'allocatorCells', this.aggregationMode_, |
| ALLOCATOR_SIZE_COLUMNS_CONSTRUCTOR_RULES); |
| tr.ui.analysis.MemoryColumn.sortByImportance( |
| allocatorSizeColumns, ALLOCATOR_SIZE_COLUMNS_IMPORTANCE_RULES); |
| |
| var sizeColumns = usedMemorySizeColumns.concat(allocatorSizeColumns); |
| tr.ui.analysis.MemoryColumn.spaceEqually(sizeColumns); |
| |
| var columns = [titleColumn].concat(sizeColumns); |
| return columns; |
| }, |
| |
| storeSelection_: function() { |
| var selectedRowTitle; |
| var selectedRow = this.$.table.selectedTableRow; |
| if (selectedRow !== undefined) |
| selectedRowTitle = selectedRow.title; |
| |
| var selectedColumnName; |
| var selectedColumnIndex = this.$.table.selectedColumnIndex; |
| if (selectedColumnIndex !== undefined) { |
| var selectedColumn = this.$.table.tableColumns[selectedColumnIndex]; |
| selectedColumnName = selectedColumn.name; |
| } |
| |
| this.$.state.set( |
| {rowTitle: selectedRowTitle, columnName: selectedColumnName}); |
| }, |
| |
| restoreSelection_: function() { |
| var settings = this.$.state.get(); |
| if (settings === undefined || settings.rowTitle === undefined || |
| settings.columnName === undefined) |
| return; |
| |
| var selectedColumnName = settings.columnName; |
| var selectedColumnIndex = tr.b.findFirstIndexInArray( |
| this.$.table.tableColumns, function(column) { |
| return column.name === selectedColumnName; |
| }); |
| if (selectedColumnIndex < 0) |
| return; |
| |
| var selectedRowTitle = settings.rowTitle; |
| var selectedRow = tr.b.findFirstInArray(this.$.table.tableRows, |
| function(row) { |
| return row.title === selectedRowTitle; |
| }); |
| if (selectedRow === undefined) |
| return; |
| |
| this.$.table.selectedTableRow = selectedRow; |
| this.$.table.selectedColumnIndex = selectedColumnIndex; |
| } |
| }); |
| |
| return { |
| AllocatorColumn: AllocatorColumn // Exported for testing. |
| }; |
| }); |
| </script> |