blob: 2669ff344784f497e820c5c9c209591939812a13 [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/base/range.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">
<link rel="import" href="/tracing/value/unit.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() {
// Constant representing the context in suballocation rows.
var SUBALLOCATION_CONTEXT = true;
// Size numeric info types.
var MemoryAllocatorDumpInfoType = tr.model.MemoryAllocatorDumpInfoType;
var PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN =
MemoryAllocatorDumpInfoType.PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN;
var PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER =
MemoryAllocatorDumpInfoType.PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER;
// Unicode symbols used for memory cell info icons and messages.
var LEFTWARDS_OPEN_HEADED_ARROW = String.fromCharCode(0x21FD);
var RIGHTWARDS_OPEN_HEADED_ARROW = String.fromCharCode(0x21FE);
var EN_DASH = String.fromCharCode(0x2013);
var CIRCLED_LATIN_SMALL_LETTER_I = String.fromCharCode(0x24D8);
/** @constructor */
function AllocatorDumpNameColumn() {
tr.ui.analysis.TitleColumn.call(this, 'Component');
}
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.fullNames === undefined ?
undefined : row.fullNames.join(', ')
});
}
};
/**
* Retrieve the entry associated with a given name from a map and increment
* its count.
*
* If there is no entry associated with the name, a new entry is created, the
* creation callback is called, the entry's count is incremented (from 0 to
* 1) and the newly created entry is returned.
*/
function getAndUpdateEntry(map, name, createdCallback) {
var entry = map.get(name);
if (entry === undefined) {
entry = {count: 0};
createdCallback(entry);
map.set(name, entry);
}
entry.count++;
return entry;
}
/**
* Helper class for building size and effective size column info messages.
*
* @constructor
*/
function SizeInfoMessageBuilder() {
this.parts_ = [];
this.indent_ = 0;
}
SizeInfoMessageBuilder.prototype = {
append: function(/* arguments */) {
this.parts_.push.apply(
this.parts_, Array.prototype.slice.apply(arguments));
},
/**
* Append the entries of a map to the message according to the following
* rules:
*
* 1. If the map is empty, append emptyText to the message (if provided).
* Examples:
*
* emptyText=undefined
* Hello, World! ====================> Hello, World!
*
* emptyText='empty'
* The bottle is ====================> The bottle is empty
*
* 2. If the map contains a single entry, append a space and call
* itemCallback on the entry (which is in turn expected to append a
* message for the entry). Example:
*
* Please do not ====================> Please do not [item-message]
*
* 3. If the map contains multiple entries, append them as a list
* with itemCallback called on each entry. If hasPluralSuffix is true,
* 's' will be appended to the message before the list. Examples:
*
* hasPluralSuffix=false
* I need to buy ====================> I need to buy:
* - [item1-message]
* - [item2-message]
* [...]
* - [itemN-message]
*
* hasPluralSuffix=true
* Suspected CL ====================> Suspected CLs:
* - [item1-message]
* - [item2-message]
* [...]
* - [itemN-message]
*/
appendMap: function(
map, hasPluralSuffix, emptyText, itemCallback, opt_this) {
opt_this = opt_this || this;
if (map.size === 0) {
if (emptyText)
this.append(emptyText);
} else if (map.size === 1) {
this.parts_.push(' ');
var key = map.keys().next().value;
itemCallback.call(opt_this, key, map.get(key));
} else {
if (hasPluralSuffix)
this.parts_.push('s');
this.parts_.push(':');
this.indent_++;
for (var key of map.keys()) {
this.parts_.push('\n', ' '.repeat(3 * (this.indent_ - 1)), ' - ');
itemCallback.call(opt_this, key, map.get(key));
}
this.indent_--;
}
},
appendImportanceRange: function(range) {
this.append(' (importance: ');
if (range.min === range.max)
this.append(range.min);
else
this.append(range.min, EN_DASH, range.max);
this.append(')');
},
appendSizeIfDefined: function(size) {
if (size !== undefined)
this.append(' (', tr.v.Unit.byName.sizeInBytes.format(size), ')');
},
appendSomeTimestampsQuantifier: function() {
this.append(
' ', tr.ui.analysis.MemoryColumn.SOME_TIMESTAMPS_INFO_QUANTIFIER);
},
build: function() {
return this.parts_.join('');
}
};
/** @constructor */
function EffectiveSizeColumn(name, cellPath, aggregationMode) {
tr.ui.analysis.NumericMemoryColumn.call(
this, name, cellPath, aggregationMode);
}
EffectiveSizeColumn.prototype = {
__proto__: tr.ui.analysis.NumericMemoryColumn.prototype,
addInfos: function(numerics, memoryAllocatorDumps, infos) {
if (memoryAllocatorDumps === undefined)
return;
// Quantified name of an owner dump (of the given dump) -> {count,
// importanceRange}.
var ownerNameToEntry = new Map();
// Quantified name of an owned dump (by the given dump) -> {count,
// importanceRange, sharerNameToEntry}, where sharerNameToEntry is a map
// from quantified names of other owners of the owned dump to {count,
// importanceRange}.
var ownedNameToEntry = new Map();
for (var i = 0; i < numerics.length; i++) {
if (numerics[i] === undefined)
continue;
var dump = memoryAllocatorDumps[i];
if (dump === SUBALLOCATION_CONTEXT)
return; // No ownership of suballocation internal rows.
// Gather owners of this dump.
dump.ownedBy.forEach(function(ownerLink) {
var ownerDump = ownerLink.source;
this.getAndUpdateOwnershipEntry_(
ownerNameToEntry, ownerDump, ownerLink);
}, this);
// Gather dumps owned by this dump and other owner dumps sharing them
// (with this dump).
var ownedLink = dump.owns;
if (ownedLink !== undefined) {
var ownedDump = ownedLink.target;
var ownedEntry = this.getAndUpdateOwnershipEntry_(ownedNameToEntry,
ownedDump, ownedLink, true /* opt_withSharerNameToEntry */);
var sharerNameToEntry = ownedEntry.sharerNameToEntry;
ownedDump.ownedBy.forEach(function(sharerLink) {
var sharerDump = sharerLink.source;
if (sharerDump === dump)
return;
this.getAndUpdateOwnershipEntry_(
sharerNameToEntry, sharerDump, sharerLink);
}, this);
}
}
// Emit a single info listing all owners of this dump.
if (ownerNameToEntry.size > 0) {
var messageBuilder = new SizeInfoMessageBuilder();
messageBuilder.append('shared by');
messageBuilder.appendMap(
ownerNameToEntry,
false /* hasPluralSuffix */,
undefined /* emptyText */,
function(ownerName, ownerEntry) {
messageBuilder.append(ownerName);
if (ownerEntry.count < numerics.length)
messageBuilder.appendSomeTimestampsQuantifier();
messageBuilder.appendImportanceRange(ownerEntry.importanceRange);
}, this);
infos.push({
message: messageBuilder.build(),
icon: LEFTWARDS_OPEN_HEADED_ARROW,
color: 'green'
});
}
// Emit a single info listing all dumps owned by this dump together
// with list(s) of other owner dumps sharing them with this dump.
if (ownedNameToEntry.size > 0) {
var messageBuilder = new SizeInfoMessageBuilder();
messageBuilder.append('shares');
messageBuilder.appendMap(
ownedNameToEntry,
false /* hasPluralSuffix */,
undefined /* emptyText */,
function(ownedName, ownedEntry) {
messageBuilder.append(ownedName);
var ownedCount = ownedEntry.count;
if (ownedCount < numerics.length)
messageBuilder.appendSomeTimestampsQuantifier();
messageBuilder.appendImportanceRange(ownedEntry.importanceRange);
messageBuilder.append(' with');
messageBuilder.appendMap(
ownedEntry.sharerNameToEntry,
false /* hasPluralSuffix */,
' no other dumps',
function(sharerName, sharerEntry) {
messageBuilder.append(sharerName);
if (sharerEntry.count < ownedCount)
messageBuilder.appendSomeTimestampsQuantifier();
messageBuilder.appendImportanceRange(
sharerEntry.importanceRange);
}, this);
}, this);
infos.push({
message: messageBuilder.build(),
icon: RIGHTWARDS_OPEN_HEADED_ARROW,
color: 'green'
});
}
},
getAndUpdateOwnershipEntry_: function(
map, dump, link, opt_withSharerNameToEntry) {
var entry = getAndUpdateEntry(map, dump.quantifiedName,
function(newEntry) {
newEntry.importanceRange = new tr.b.Range();
if (opt_withSharerNameToEntry)
newEntry.sharerNameToEntry = new Map();
});
entry.importanceRange.addValue(link.importance || 0);
return entry;
}
};
/** @constructor */
function SizeColumn(name, cellPath, aggregationMode) {
tr.ui.analysis.NumericMemoryColumn.call(
this, name, cellPath, aggregationMode);
}
SizeColumn.prototype = {
__proto__: tr.ui.analysis.NumericMemoryColumn.prototype,
addInfos: function(numerics, memoryAllocatorDumps, infos) {
if (memoryAllocatorDumps === undefined)
return;
this.addOverlapInfo_(numerics, memoryAllocatorDumps, infos);
this.addProvidedSizeWarningInfos_(numerics, memoryAllocatorDumps, infos);
},
addOverlapInfo_: function(numerics, memoryAllocatorDumps, infos) {
// Sibling allocator dump name -> {count, size}. The latter field (size)
// is omitted in multi-selection mode.
var siblingNameToEntry = new Map();
for (var i = 0; i < numerics.length; i++) {
if (numerics[i] === undefined)
continue;
var dump = memoryAllocatorDumps[i];
if (dump === SUBALLOCATION_CONTEXT)
return; // No ownership of suballocation internal rows.
var ownedBySiblingSizes = dump.ownedBySiblingSizes;
for (var siblingDump of ownedBySiblingSizes.keys()) {
var siblingName = siblingDump.name;
getAndUpdateEntry(siblingNameToEntry, siblingName,
function(newEntry) {
if (numerics.length === 1 /* single-selection mode */)
newEntry.size = ownedBySiblingSizes.get(siblingDump);
});
}
}
// Emit a single info describing all overlaps with siblings (if
// applicable).
if (siblingNameToEntry.size > 0) {
var messageBuilder = new SizeInfoMessageBuilder();
messageBuilder.append('overlaps with its sibling');
messageBuilder.appendMap(
siblingNameToEntry,
true /* hasPluralSuffix */,
undefined /* emptyText */,
function(siblingName, siblingEntry) {
messageBuilder.append('\'', siblingName, '\'');
messageBuilder.appendSizeIfDefined(siblingEntry.size);
if (siblingEntry.count < numerics.length)
messageBuilder.appendSomeTimestampsQuantifier();
}, this);
infos.push({
message: messageBuilder.build(),
icon: CIRCLED_LATIN_SMALL_LETTER_I,
color: 'blue'
});
}
},
addProvidedSizeWarningInfos_: function(numerics, memoryAllocatorDumps,
infos) {
// Info type (see MemoryAllocatorDumpInfoType) -> {count, providedSize,
// dependencySize}. The latter two fields (providedSize and
// dependencySize) are omitted in multi-selection mode.
var infoTypeToEntry = new Map();
for (var i = 0; i < numerics.length; i++) {
if (numerics[i] === undefined)
continue;
var dump = memoryAllocatorDumps[i];
if (dump === SUBALLOCATION_CONTEXT)
return; // Suballocation internal rows have no provided size.
dump.infos.forEach(function(dumpInfo) {
getAndUpdateEntry(infoTypeToEntry, dumpInfo.type, function(newEntry) {
if (numerics.length === 1 /* single-selection mode */) {
newEntry.providedSize = dumpInfo.providedSize;
newEntry.dependencySize = dumpInfo.dependencySize;
}
});
});
}
// Emit a warning info for every info type.
for (var infoType of infoTypeToEntry.keys()) {
var entry = infoTypeToEntry.get(infoType);
var messageBuilder = new SizeInfoMessageBuilder();
messageBuilder.append('provided size');
messageBuilder.appendSizeIfDefined(entry.providedSize);
var dependencyName;
switch (infoType) {
case PROVIDED_SIZE_LESS_THAN_AGGREGATED_CHILDREN:
dependencyName = 'the aggregated size of the children';
break;
case PROVIDED_SIZE_LESS_THAN_LARGEST_OWNER:
dependencyName = 'the size of the largest owner';
break;
default:
dependencyName = 'an unknown dependency';
break;
}
messageBuilder.append(' was less than ', dependencyName);
messageBuilder.appendSizeIfDefined(entry.dependencySize);
if (entry.count < numerics.length)
messageBuilder.appendSomeTimestampsQuantifier();
infos.push(tr.ui.analysis.createWarningInfo(messageBuilder.build()));
}
}
};
var NUMERIC_COLUMN_RULES = [
{
condition: tr.model.MemoryAllocatorDump.EFFECTIVE_SIZE_NUMERIC_NAME,
importance: 10,
columnConstructor: EffectiveSizeColumn
},
{
condition: tr.model.MemoryAllocatorDump.SIZE_NUMERIC_NAME,
importance: 9,
columnConstructor: SizeColumn
},
{
condition: 'page_size',
importance: 0,
columnConstructor: tr.ui.analysis.NumericMemoryColumn
},
{
condition: /size/,
importance: 5,
columnConstructor: tr.ui.analysis.NumericMemoryColumn
},
{
// All other columns.
importance: 0,
columnConstructor: tr.ui.analysis.NumericMemoryColumn
}
];
var DIAGNOSTIC_COLUMN_RULES = [
{
importance: 0,
columnConstructor: tr.ui.analysis.StringMemoryColumn
}
];
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);
rows.forEach(function(rootRow) {
tr.ui.analysis.aggregateTableRowCellsRecursively(rootRow, columns,
function(contexts) {
// Only aggregate suballocation rows (numerics of regular rows
// corresponding to MADs have already been aggregated by the
// model in MemoryAllocatorDump.aggregateNumericsRecursively).
return contexts !== undefined && contexts.some(function(context) {
return context === SUBALLOCATION_CONTEXT;
});
});
});
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;
// Transform a chronological list of memory allocator dumps into two
// dictionaries of cells (where each cell contains a chronological list
// of the values of one of its numerics or diagnostics).
var numericCells = tr.ui.analysis.createCells(dumps, function(dump) {
return dump.numerics;
});
var diagnosticCells = tr.ui.analysis.createCells(dumps, function(dump) {
return dump.diagnostics;
});
// 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,
fullNames: [fullName],
contexts: dumps,
numericCells: numericCells,
diagnosticCells: diagnosticCells,
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);
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;
}
var existingRow = currentNode.row;
if (existingRow !== undefined) {
// On rare occasions it can happen that one dump (e.g. sqlite) owns
// different suballocations at different timestamps (e.g.
// malloc/allocated_objects/_7d35 and malloc/allocated_objects/_511e).
// When this happens, we merge the two suballocations into a single row
// (malloc/allocated_objects/suballocations/sqlite).
for (var i = 0; i < suballocationRow.contexts.length; i++) {
var newContext = suballocationRow.contexts[i];
if (newContext === undefined)
continue;
if (existingRow.contexts[i] !== undefined)
throw new Error('Multiple suballocations with the same owner name');
existingRow.contexts[i] = newContext;
['numericCells', 'diagnosticCells'].forEach(function(cellKey) {
var suballocationCells = suballocationRow[cellKey];
if (suballocationCells === undefined)
return;
tr.b.iterItems(suballocationCells, function(cellName, cell) {
if (cell === undefined)
return;
var fields = cell.fields;
if (fields === undefined)
return;
var field = fields[i];
if (field === undefined)
return;
var existingCells = existingRow[cellKey];
if (existingCells === undefined) {
existingCells = {};
existingRow[cellKey] = existingCells;
}
var existingCell = existingCells[cellName];
if (existingCell === undefined) {
existingCell = new tr.ui.analysis.MemoryCell(
new Array(fields.length));
existingCells[cellName] = existingCell;
}
existingCell.fields[i] = field;
});
});
}
existingRow.fullNames.push.apply(
existingRow.fullNames, suballocationRow.fullNames);
} else {
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 numerics 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 contexts = new Array(subRows[0].contexts.length);
for (var i = 0; i < subRows.length; i++) {
subRows[i].contexts.forEach(function(subContext, index) {
if (subContext !== undefined)
contexts[index] = SUBALLOCATION_CONTEXT;
});
}
return {
title: name,
suballocation: true,
contexts: contexts,
subRows: subRows
};
},
createColumns_: function(rows) {
var titleColumn = new AllocatorDumpNameColumn();
titleColumn.width = '200px';
var numericColumns = tr.ui.analysis.MemoryColumn.fromRows(
rows, 'numericCells', this.aggregationMode_, NUMERIC_COLUMN_RULES);
var diagnosticColumns = tr.ui.analysis.MemoryColumn.fromRows(
rows, 'diagnosticCells', this.aggregationMode_,
DIAGNOSTIC_COLUMN_RULES);
var fieldColumns = numericColumns.concat(diagnosticColumns);
tr.ui.analysis.MemoryColumn.spaceEqually(fieldColumns);
var columns = [titleColumn].concat(fieldColumns);
return columns;
}
});
return {
// All exports are for testing only.
SUBALLOCATION_CONTEXT: SUBALLOCATION_CONTEXT,
AllocatorDumpNameColumn: AllocatorDumpNameColumn,
EffectiveSizeColumn: EffectiveSizeColumn,
SizeColumn: SizeColumn
};
});
</script>