| <!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/iteration_helpers.html"> |
| <link rel="import" href="/tracing/base/unit.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/table.html"> |
| <link rel="import" href="/tracing/value/numeric.html"> |
| |
| <dom-module id='tr-ui-a-memory-dump-vm-regions-details-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> |
| <div id="label">Memory maps</div> |
| <div id="contents"> |
| <div id="info_text">No memory maps selected</div> |
| <tr-ui-b-table id="table"></tr-ui-b-table> |
| </div> |
| </template> |
| </dom-module> |
| <script> |
| 'use strict'; |
| |
| tr.exportTo('tr.ui.analysis', function() { |
| |
| var ScalarNumeric = tr.v.ScalarNumeric; |
| var sizeInBytes_smallerIsBetter = |
| tr.b.Unit.byName.sizeInBytes_smallerIsBetter; |
| |
| var CONSTANT_COLUMN_RULES = [ |
| { |
| condition: 'Start address', |
| importance: 0, |
| columnConstructor: tr.ui.analysis.StringMemoryColumn |
| } |
| ]; |
| |
| var VARIABLE_COLUMN_RULES = [ |
| { |
| condition: 'Virtual size', |
| importance: 7, |
| columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn |
| }, |
| { |
| condition: 'Protection flags', |
| importance: 6, |
| columnConstructor: tr.ui.analysis.StringMemoryColumn |
| }, |
| { |
| condition: 'PSS', |
| importance: 5, |
| columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn |
| }, |
| { |
| condition: 'Private dirty', |
| importance: 4, |
| columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn |
| }, |
| { |
| condition: 'Private clean', |
| importance: 3, |
| columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn |
| }, |
| { |
| condition: 'Shared dirty', |
| importance: 2, |
| columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn |
| }, |
| { |
| condition: 'Shared clean', |
| importance: 1, |
| columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn |
| }, |
| { |
| condition: 'Swapped', |
| importance: 0, |
| columnConstructor: tr.ui.analysis.DetailsNumericMemoryColumn |
| } |
| ]; |
| |
| var BYTE_STAT_COLUMN_MAP = { |
| 'proportionalResident': 'PSS', |
| 'privateDirtyResident': 'Private dirty', |
| 'privateCleanResident': 'Private clean', |
| 'sharedDirtyResident': 'Shared dirty', |
| 'sharedCleanResident': 'Shared clean', |
| 'swapped': 'Swapped' |
| }; |
| |
| function hexString(address, is64BitAddress) { |
| if (address === undefined) |
| return undefined; |
| var hexPadding = is64BitAddress ? '0000000000000000' : '00000000'; |
| return (hexPadding + address.toString(16)).substr(-hexPadding.length); |
| } |
| |
| function pruneEmptyRuleRows(row) { |
| if (row.subRows === undefined || row.subRows.length === 0) |
| return; |
| |
| // Either all sub-rows are rule rows, or all sub-rows are VM region rows. |
| if (row.subRows[0].rule === undefined) { |
| // VM region rows: Early out to avoid filtering a large array for |
| // performance reasons (no sub-rows would be removed, but the whole array |
| // would be unnecessarily copied to a new array). |
| return; |
| } |
| |
| row.subRows.forEach(pruneEmptyRuleRows); |
| row.subRows = row.subRows.filter(function(subRow) { |
| return subRow.subRows.length > 0; |
| }); |
| } |
| |
| Polymer({ |
| is: 'tr-ui-a-memory-dump-vm-regions-details-pane', |
| behaviors: [tr.ui.analysis.StackedPane], |
| |
| created: function() { |
| this.vmRegions_ = undefined; |
| this.aggregationMode_ = undefined; |
| }, |
| |
| ready: function() { |
| this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW; |
| }, |
| |
| /** |
| * Sets the VM regions and schedules rebuilding the pane. |
| * |
| * The provided value should be a chronological list of lists of VM |
| * regions. All VM regions are assumed to belong to the same process. |
| * Example: |
| * |
| * [ |
| * [ |
| * // VM regions at timestamp 1. |
| * tr.model.VMRegion {}, |
| * tr.model.VMRegion {}, |
| * tr.model.VMRegion {} |
| * ], |
| * undefined, // No VM regions provided at timestamp 2. |
| * [ |
| * // VM regions at timestamp 3. |
| * tr.model.VMRegion {}, |
| * tr.model.VMRegion {} |
| * ] |
| * ] |
| */ |
| set vmRegions(vmRegions) { |
| this.vmRegions_ = vmRegions; |
| this.scheduleRebuild_(); |
| }, |
| |
| get vmRegions() { |
| return this.vmRegions_; |
| }, |
| |
| set aggregationMode(aggregationMode) { |
| this.aggregationMode_ = aggregationMode; |
| this.scheduleRebuild_(); |
| }, |
| |
| get aggregationMode() { |
| return this.aggregationMode_; |
| }, |
| |
| onRebuild_: function() { |
| if (this.vmRegions_ === undefined || this.vmRegions_.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_(this.vmRegions_); |
| var columns = this.createColumns_(rows); |
| |
| // Note: There is no need to aggregate fields of the VM regions because |
| // the classification tree already takes care of that. |
| |
| this.$.table.tableRows = rows; |
| this.$.table.tableColumns = columns; |
| |
| // TODO(petrcermak): This can be quite slow. Consider doing this somehow |
| // asynchronously. |
| this.$.table.rebuild(); |
| |
| tr.ui.analysis.expandTableRowsRecursively(this.$.table); |
| }, |
| |
| createRows_: function(timeToVmRegionTree) { |
| // Determine if any start address is outside the 32-bit range. |
| var is64BitAddress = timeToVmRegionTree.some(function(vmRegionTree) { |
| if (vmRegionTree === undefined) |
| return false; |
| return vmRegionTree.someRegion(function(region) { |
| if (region.startAddress === undefined) |
| return false; |
| return region.startAddress >= 4294967296 /* 2^32 */; |
| }); |
| }); |
| |
| return [ |
| this.createClassificationNodeRow(timeToVmRegionTree, is64BitAddress) |
| ]; |
| }, |
| |
| createClassificationNodeRow: function(timeToNode, is64BitAddress) { |
| // Get any defined classification node so that we can extract the |
| // properties which don't change over time. |
| var definedNode = tr.b.findFirstInArray(timeToNode); |
| |
| // Child node ID (list index) -> Timestamp (list index) -> |
| // VM region classification node. |
| var childNodeIdToTimeToNode = tr.b.dictionaryValues( |
| tr.b.invertArrayOfDicts(timeToNode, function(node) { |
| var children = node.children; |
| if (children === undefined) |
| return undefined; |
| var childMap = {}; |
| children.forEach(function(childNode) { |
| if (!childNode.hasRegions) |
| return; |
| childMap[childNode.title] = childNode; |
| }); |
| return childMap; |
| })); |
| var childNodeSubRows = childNodeIdToTimeToNode.map( |
| function(timeToChildNode) { |
| return this.createClassificationNodeRow( |
| timeToChildNode, is64BitAddress); |
| }, this); |
| |
| // Region ID (list index) -> Timestamp (list index) -> VM region. |
| var regionIdToTimeToRegion = tr.b.dictionaryValues( |
| tr.b.invertArrayOfDicts(timeToNode, function(node) { |
| var regions = node.regions; |
| if (regions === undefined) |
| return undefined; |
| return tr.b.arrayToDict(regions, function(region) { |
| return region.uniqueIdWithinProcess; |
| }); |
| })); |
| var regionSubRows = regionIdToTimeToRegion.map(function(timeToRegion) { |
| return this.createRegionRow_(timeToRegion, is64BitAddress); |
| }, this); |
| |
| var subRows = childNodeSubRows.concat(regionSubRows); |
| |
| return { |
| title: definedNode.title, |
| contexts: timeToNode, |
| variableCells: this.createVariableCells_(timeToNode), |
| subRows: subRows |
| }; |
| }, |
| |
| createRegionRow_: function(timeToRegion, is64BitAddress) { |
| // Get any defined VM region so that we can extract the properties which |
| // don't change over time. |
| var definedRegion = tr.b.findFirstInArray(timeToRegion); |
| |
| return { |
| title: definedRegion.mappedFile, |
| contexts: timeToRegion, |
| constantCells: this.createConstantCells_(definedRegion, is64BitAddress), |
| variableCells: this.createVariableCells_(timeToRegion) |
| }; |
| }, |
| |
| /** |
| * Create cells for VM region properties which DON'T change over time. |
| * |
| * Note that there are currently no such properties of classification nodes. |
| */ |
| createConstantCells_: function(definedRegion, is64BitAddress) { |
| return tr.ui.analysis.createCells([definedRegion], function(region) { |
| var startAddress = region.startAddress; |
| if (startAddress === undefined) |
| return undefined; |
| return { 'Start address': hexString(startAddress, is64BitAddress) }; |
| }); |
| }, |
| |
| /** |
| * Create cells for VM region (classification node) properties which DO |
| * change over time. |
| */ |
| createVariableCells_: function(timeToRegion) { |
| return tr.ui.analysis.createCells(timeToRegion, function(region) { |
| var fields = {}; |
| |
| var sizeInBytes = region.sizeInBytes; |
| if (sizeInBytes !== undefined) { |
| fields['Virtual size'] = new ScalarNumeric( |
| sizeInBytes_smallerIsBetter, sizeInBytes); |
| } |
| var protectionFlags = region.protectionFlagsToString; |
| if (protectionFlags !== undefined) |
| fields['Protection flags'] = protectionFlags; |
| |
| tr.b.iterItems(BYTE_STAT_COLUMN_MAP, |
| function(byteStatName, columnName) { |
| var byteStat = region.byteStats[byteStatName]; |
| if (byteStat === undefined) |
| return; |
| fields[columnName] = new ScalarNumeric( |
| sizeInBytes_smallerIsBetter, byteStat); |
| }); |
| |
| return fields; |
| }); |
| }, |
| |
| createColumns_: function(rows) { |
| var titleColumn = new tr.ui.analysis.TitleColumn('Mapped file'); |
| titleColumn.width = '200px'; |
| |
| var constantColumns = tr.ui.analysis.MemoryColumn.fromRows( |
| rows, 'constantCells', undefined, CONSTANT_COLUMN_RULES); |
| var variableColumns = tr.ui.analysis.MemoryColumn.fromRows( |
| rows, 'variableCells', this.aggregationMode_, VARIABLE_COLUMN_RULES); |
| var fieldColumns = constantColumns.concat(variableColumns); |
| tr.ui.analysis.MemoryColumn.spaceEqually(fieldColumns); |
| |
| var columns = [titleColumn].concat(fieldColumns); |
| return columns; |
| } |
| }); |
| |
| return {}; |
| }); |
| </script> |