blob: 8275697edbc813497699828bfe6452c34bdb9d4e [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/iteration_helpers.html">
<link rel="import" href="/tracing/model/memory_allocator_dump.html">
<link rel="import"
href="/tracing/ui/analysis/memory_dump_heap_details_pane.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/table.html">
<polymer-element name="tr-ui-a-memory-dump-allocator-details-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 allocator dumps are set. */
flex: 1 0 auto;
align-self: stretch;
}
</style>
<div id="label">Component details</div>
<div id="contents">
<div id="info_text">No memory allocator 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 IMPORTANCE_RULES = [
{
condition: tr.model.MemoryAllocatorDump.EFFECTIVE_SIZE_ATTRIBUTE_NAME,
importance: 10
},
{
condition: tr.model.MemoryAllocatorDump.SIZE_ATTRIBUTE_NAME,
importance: 9
},
{
condition: 'page_size',
importance: 0
},
{
condition: /size/,
importance: 5
},
{
importance: 0
}
];
/** @{constructor} */
function AllocatorDumpNameColumn(title) {
tr.ui.analysis.TitleColumn.call(this, title);
}
AllocatorDumpNameColumn.prototype = {
__proto__: tr.ui.analysis.TitleColumn.prototype,
formatTitle: function(row) {
if (!row.suballocation)
return row.title;
return tr.ui.b.createSpan({
textContent: row.title,
italic: true,
tooltip: row.fullName
});
}
};
Polymer('tr-ui-a-memory-dump-allocator-details-pane', {
created: function() {
this.memoryAllocatorDumps_ = undefined;
this.heapDumps_ = undefined;
this.aggregationMode_ = undefined;
},
ready: function() {
this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW;
},
/**
* Sets the memory allocator dumps and schedules rebuilding the pane.
*
* The provided value should be a chronological list of memory allocator
* dumps. All dumps are assumed to belong to the same process and have
* the same full name. Example:
*
* [
* tr.model.MemoryAllocatorDump {}, // MAD at timestamp 1.
* undefined, // MAD not provided at timestamp 2.
* tr.model.MemoryAllocatorDump {}, // MAD at timestamp 3.
* ]
*/
set memoryAllocatorDumps(memoryAllocatorDumps) {
this.memoryAllocatorDumps_ = memoryAllocatorDumps;
this.scheduleRebuildPane_();
},
get memoryAllocatorDumps() {
return this.memoryAllocatorDumps_;
},
// TODO(petrcermak): Don't plumb the heap dumps through the allocator
// details pane. Maybe add support for multiple child panes to stacked pane
// (view) instead.
set heapDumps(heapDumps) {
this.heapDumps_ = heapDumps;
this.scheduleRebuildPane_();
},
set aggregationMode(aggregationMode) {
this.aggregationMode_ = aggregationMode;
this.scheduleRebuildPane_();
},
get aggregationMode() {
return this.aggregationMode_;
},
rebuildPane_: function() {
if (this.memoryAllocatorDumps_ === undefined ||
this.memoryAllocatorDumps_.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();
// Hide the heap details pane (if applicable).
this.childPaneBuilder = undefined;
return;
}
// Show the table (hide the info text).
this.$.info_text.style.display = 'none';
this.$.table.style.display = 'block';
var rows = this.createRows_();
var columns = this.createColumns_(rows);
this.$.table.tableRows = rows;
this.$.table.tableColumns = columns;
this.$.table.rebuild();
tr.ui.analysis.expandTableRowsRecursively(this.$.table);
// Show/hide the heap details pane.
if (this.heapDumps_ === undefined) {
this.childPaneBuilder = undefined;
} else {
this.childPaneBuilder = function() {
var pane =
document.createElement('tr-ui-a-memory-dump-heap-details-pane');
pane.heapDumps = this.heapDumps_;
pane.aggregationMode = this.aggregationMode_;
return pane;
}.bind(this);
}
},
createRows_: function() {
return [
this.createAllocatorRowRecursively_(this.memoryAllocatorDumps_)
];
},
createAllocatorRowRecursively_: function(dumps) {
// Get the name of the memory allocator dumps. We can use any defined
// dump in dumps since they all have the same name.
var definedDump = tr.b.findFirstInArray(dumps);
var title = definedDump.name;
var fullName = definedDump.fullName;
// Determine at which timestamps (indices of the current selection)
// the dump was provided.
var defined = dumps.map(function(dump) {
return dump !== undefined;
});
// Transform a chronological list of memory allocator dumps into a
// dictionary of cells (where each cell contains a chronological list
// of the values of its attribute).
var cells = tr.ui.analysis.createCells(dumps, function(dump) {
return dump.attributes;
});
// Determine whether the memory allocator dump is a suballocation. A
// dump is assumed to be a suballocation if (1) its name starts with
// two underscores, (2) it has an owner from within the same process at
// some timestamp, and (3) it is undefined, has no owners, or has the
// same owner (and no other owners) at all other timestamps.
var suballocatedBy = undefined;
if (title.startsWith('__')) {
for (var i = 0; i < dumps.length; i++) {
var dump = dumps[i];
if (dump === undefined || dump.ownedBy.length === 0) {
// Ignore timestamps where the dump is undefined or doesn't
// have any owner.
continue;
}
var ownerDump = dump.ownedBy[0].source;
if (dump.ownedBy.length > 1 ||
dump.children.length > 0 ||
ownerDump.containerMemoryDump !== dump.containerMemoryDump) {
// If the dump has (1) any children, (2) multiple owners, or
// (3) its owner is in a different process (otherwise, the
// modified title would be ambiguous), then it's not considered
// to be a suballocation.
suballocatedBy = undefined;
break;
}
if (suballocatedBy === undefined) {
suballocatedBy = ownerDump.fullName;
} else if (suballocatedBy !== ownerDump.fullName) {
// The full name of the owner dump changed over time, so this
// dump is not a suballocation.
suballocatedBy = undefined;
break;
}
}
}
var row = {
title: title,
fullName: fullName,
defined: defined,
cells: cells,
suballocatedBy: suballocatedBy
};
// Child memory dump name (dict key) -> Timestamp (list index) ->
// Child dump.
var childDumpNameToDumps = tr.b.invertArrayOfDicts(dumps,
function(dump) {
return tr.b.arrayToDict(dump.children, function(child) {
return child.name;
});
});
// Recursively create sub-rows for children (if applicable).
var subRows = [];
var suballocationClassificationRootNode = undefined;
tr.b.iterItems(childDumpNameToDumps, function(childName, childDumps) {
var childRow = this.createAllocatorRowRecursively_(childDumps);
if (childRow.suballocatedBy === undefined) {
// Not a suballocation row: just append it.
subRows.push(childRow);
} else {
// Suballocation row: classify it in a tree of suballocations.
suballocationClassificationRootNode =
this.classifySuballocationRow_(
childRow, suballocationClassificationRootNode);
}
}, this);
// Build the tree of suballocations (if applicable).
if (suballocationClassificationRootNode !== undefined) {
var suballocationRow = this.createSuballocationRowRecursively_(
'suballocations', suballocationClassificationRootNode);
tr.ui.analysis.aggregateTableRowCellsRecursively(
suballocationRow, 'cells');
subRows.push(suballocationRow);
}
if (subRows.length > 0)
row.subRows = subRows;
return row;
},
classifySuballocationRow_: function(suballocationRow, rootNode) {
if (rootNode === undefined) {
rootNode = {
children: {},
row: undefined
};
}
var suballocationLevels = suballocationRow.suballocatedBy.split('/');
var currentNode = rootNode;
for (var i = 0; i < suballocationLevels.length; i++) {
var suballocationLevel = suballocationLevels[i];
var nextNode = currentNode.children[suballocationLevel];
if (nextNode === undefined) {
currentNode.children[suballocationLevel] = nextNode = {
children: {},
row: undefined
};
}
var currentNode = nextNode;
}
if (currentNode.row !== undefined)
throw new Error('Multiple suballocations with the same owner name');
currentNode.row = suballocationRow;
return rootNode;
},
createSuballocationRowRecursively_: function(name, node) {
var childCount = Object.keys(node.children).length;
if (childCount === 0) {
if (node.row === undefined)
throw new Error('Suballocation node must have a row or children');
// Leaf row of the suballocation tree: Change the row's title from
// '__MEANINGLESSHASH' to the name of the suballocation owner.
var row = node.row;
row.title = name;
row.suballocation = true;
return row;
}
// Internal row of the suballocation tree: Recursively create its
// sub-rows.
var subRows = tr.b.dictionaryValues(tr.b.mapItems(
node.children, this.createSuballocationRowRecursively_, this));
if (node.row !== undefined) {
// Very unlikely case: Both an ancestor (e.g. 'skia') and one of its
// descendants (e.g. 'skia/sk_glyph_cache') both suballocate from the
// same MemoryAllocatorDump (e.g. 'malloc/allocated_objects'). In
// this case, the suballocation from the ancestor must be mapped to
// 'malloc/allocated_objects/suballocations/skia/<unspecified>' so
// that 'malloc/allocated_objects/suballocations/skia' could
// aggregate the attributes of the two suballocations properly.
var row = node.row;
row.title = '<unspecified>';
row.suballocation = true;
subRows.unshift(row);
}
// An internal row of the suballocation tree is assumed to be defined
// at a given timestamp if at least one of its sub-rows is defined at
// the timestamp.
var defined = new Array(subRows[0].defined.length);
for (var i = 0; i < subRows.length; i++) {
subRows[i].defined.forEach(function(definedValue, index) {
defined[index] = defined[index] || definedValue;
});
}
return {
title: name,
suballocation: true,
defined: defined,
cells: {},
subRows: subRows
};
},
createColumns_: function(rows) {
var titleColumn = new AllocatorDumpNameColumn('Component');
titleColumn.width = '200px';
var attributeColumns = tr.ui.analysis.MemoryColumn.fromRows(
rows, 'cells', this.aggregationMode_);
tr.ui.analysis.MemoryColumn.spaceEqually(attributeColumns);
tr.ui.analysis.MemoryColumn.sortByImportance(
attributeColumns, IMPORTANCE_RULES);
var columns = [titleColumn].concat(attributeColumns);
return columns;
}
});
return {};
});
</script>