blob: 28aa3674a0ab5d425175a756bf5309116f134128 [file] [log] [blame]
<!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>