blob: 6afb45c85212d15bcc21121229eca211f56c5c98 [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/ui/base/dom_helpers.html">
<link rel="import" href="/tracing/ui/base/table.html">
<link rel="import" href="/tracing/value/numeric.html">
<link rel="import" href="/tracing/value/ui/scalar_span.html">
<script>
'use strict';
/**
* @fileoverview Helper code for memory dump sub-views.
*/
tr.exportTo('tr.ui.analysis', function() {
var NO_BREAK_SPACE = String.fromCharCode(160);
var RIGHTWARDS_ARROW = String.fromCharCode(8594);
var COLLATOR = new Intl.Collator(undefined, {numeric: true});
/**
* A table column for displaying memory dump row titles.
*
* @constructor
*/
function TitleColumn(title) {
this.title = title;
}
TitleColumn.prototype = {
supportsCellSelection: false,
/**
* Get the title associated with a given row.
*
* This method will decorate the title with color and '+++'/'---' prefix if
* appropriate (as determined by the optional row.contexts field).
* Examples:
*
* +----------------------+-----------------+--------+--------+
* | Contexts provided at | Interpretation | Prefix | Color |
* +----------------------+-----------------+--------+--------+
* | 1111111111 | always present | | |
* | 0000111111 | added | +++ | red |
* | 1111111000 | deleted | --- | green |
* | 1100111111* | flaky | | purple |
* | 0001001111 | added + flaky | +++ | purple |
* | 1111100010 | deleted + flaky | --- | purple |
* +----------------------+-----------------+--------+--------+
*
* *) This means that, given a selection of 10 memory dumps, a particular
* row (e.g. a process) was present in the first 2 and last 6 of them
* (but not in the third and fourth dump).
*
* This method should therefore NOT be overriden by subclasses. The
* formatTitle method should be overriden instead when necessary.
*/
value: function(row) {
var formattedTitle = this.formatTitle(row);
var contexts = row.contexts;
if (contexts === undefined || contexts.length === 0)
return formattedTitle;
// Determine if the row was provided in the first and last row and how
// many times it changed between being provided and not provided.
var firstContext = contexts[0];
var lastContext = contexts[contexts.length - 1];
var changeDefinedContextCount = 0;
for (var i = 1; i < contexts.length; i++) {
if ((contexts[i] === undefined) !== (contexts[i - 1] === undefined))
changeDefinedContextCount++;
}
// Determine the color and prefix of the title.
var color = undefined;
var prefix = undefined;
if (!firstContext && lastContext) {
// The row was added.
color = 'red';
prefix = '+++';
} else if (firstContext && !lastContext) {
// The row was removed.
color = 'green';
prefix = '---';
}
if (changeDefinedContextCount > 1) {
// The row was flaky (added/removed more than once).
color = 'purple';
}
if (color === undefined && prefix === undefined)
return formattedTitle;
var titleEl = document.createElement('span');
if (prefix !== undefined) {
var prefixEl = tr.ui.b.createSpan({textContent: prefix});
// Enforce same width of '+++' and '---'.
prefixEl.style.fontFamily = 'monospace';
Polymer.dom(titleEl).appendChild(prefixEl);
Polymer.dom(titleEl).appendChild(
tr.ui.b.asHTMLOrTextNode(NO_BREAK_SPACE));
}
if (color !== undefined)
titleEl.style.color = color;
Polymer.dom(titleEl).appendChild(
tr.ui.b.asHTMLOrTextNode(formattedTitle));
return titleEl;
},
/**
* Format the title associated with a given row. This method is intended to
* be overriden by subclasses.
*/
formatTitle: function(row) {
return row.title;
},
cmp: function(rowA, rowB) {
return COLLATOR.compare(rowA.title, rowB.title);
}
};
/**
* Abstract table column for displaying memory dump data.
*
* @constructor
*/
function MemoryColumn(name, cellPath, aggregationMode) {
this.name = name;
this.cellPath = cellPath;
// See MemoryColumn.AggregationMode enum in this file.
this.aggregationMode = aggregationMode;
}
/**
* Construct columns from cells in a hierarchy of rows and a list of rules.
*
* The list of rules contains objects with three fields:
*
* condition: Optional string or regular expression matched against the
* name of a cell. If omitted, the rule will match any cell.
* importance: Mandatory number which determines the final order of the
* columns. The column with the highest importance will be first in the
* returned array.
* columnConstructor: Mandatory memory column constructor.
*
* Example:
*
* var importanceRules = [
* {
* condition: 'page_size',
* columnConstructor: NumericMemoryColumn,
* importance: 8
* },
* {
* condition: /size/,
* columnConstructor: CustomNumericMemoryColumn,
* importance: 10
* },
* {
* // No condition: matches all columns.
* columnConstructor: NumericMemoryColumn,
* importance: 9
* }
* ];
*
* Given a name of a cell, the corresponding column constructor and
* importance are determined by the first rule whose condition matches the
* column's name. For example, given a cell with name 'inner_size', the
* corresponding column will be constructed using CustomNumericMemoryColumn
* and its importance (for sorting purposes) will be 10 (second rule).
*
* After columns are constructed for all cell names, they are sorted in
* descending order of importance and the resulting list is returned. In the
* example above, the constructed columns will be sorted into three groups as
* follows:
*
* [most important, left in the resulting table]
* 1. columns whose name contains 'size' excluding 'page_size' because it
* would have already matched the first rule (Note that string matches
* must be exact so a column named 'page_size2' would not match the
* first rule and would therefore belong to this group).
* 2. columns whose name does not contain 'size'.
* 3. columns whose name is 'page_size'.
* [least important, right in the resulting table]
*
* where columns will be sorted alphabetically within each group.
*/
MemoryColumn.fromRows = function(rows, cellKey, aggregationMode, rules) {
// Recursively find the names of all cells of the rows (and their sub-rows).
var cellNames = new Set();
function gatherCellNames(rows) {
rows.forEach(function(row) {
if (row === undefined)
return;
var fieldCells = row[cellKey];
if (fieldCells !== undefined) {
tr.b.iterItems(fieldCells, function(fieldName, fieldCell) {
if (fieldCell === undefined || fieldCell.fields === undefined)
return;
cellNames.add(fieldName);
});
}
var subRows = row.subRows;
if (subRows !== undefined)
gatherCellNames(subRows);
});
}
gatherCellNames(rows);
// Based on the provided list of rules, construct the columns and calculate
// their importance.
var positions = [];
cellNames.forEach(function(cellName) {
var cellPath = [cellKey, cellName];
var matchingRule = MemoryColumn.findMatchingRule(cellName, rules);
var constructor = matchingRule.columnConstructor;
var column = new constructor(cellName, cellPath, aggregationMode);
positions.push({
importance: matchingRule.importance,
column: column
});
});
positions.sort(function(a, b) {
// Sort columns with the same importance alphabetically.
if (a.importance === b.importance)
return COLLATOR.compare(a.column.name, b.column.name);
// Sort columns in descending order of importance.
return b.importance - a.importance;
});
return positions.map(function(position) { return position.column });
};
MemoryColumn.spaceEqually = function(columns) {
var columnWidth = (100 / columns.length).toFixed(3) + '%';
columns.forEach(function(column) {
column.width = columnWidth;
});
};
MemoryColumn.findMatchingRule = function(name, rules) {
for (var i = 0; i < rules.length; i++) {
var rule = rules[i];
if (MemoryColumn.nameMatchesCondition(name, rule.condition))
return rule;
}
return undefined;
};
MemoryColumn.nameMatchesCondition = function(name, condition) {
// Rules without conditions match all columns.
if (condition === undefined)
return true;
// String conditions must match the column name exactly.
if (typeof(condition) === 'string')
return name === condition;
// If the condition is not a string, assume it is a RegExp.
return condition.test(name);
};
/** @enum */
MemoryColumn.AggregationMode = {
DIFF: 0,
MAX: 1
};
MemoryColumn.SOME_TIMESTAMPS_INFO_QUANTIFIER = 'at some selected timestamps';
MemoryColumn.prototype = {
get title() {
return this.name;
},
cell: function(row) {
var cell = row;
var cellPath = this.cellPath;
for (var i = 0; i < cellPath.length; i++) {
if (cell === undefined)
return undefined;
cell = cell[cellPath[i]];
}
return cell;
},
aggregateCells: function(row, subRows) {
// No generic aggregation.
},
fields: function(row) {
var cell = this.cell(row);
if (cell === undefined)
return undefined;
return cell.fields;
},
/**
* Format a cell associated with this column from the given row. This
* method is not intended to be overriden.
*/
value: function(row) {
var fields = this.fields(row);
if (this.hasAllRelevantFieldsUndefined(fields))
return '';
// Determine the color and infos of the resulting element.
var contexts = row.contexts;
var color = this.color(fields, contexts);
var infos = [];
this.addInfos(fields, contexts, infos);
// Format the actual fields.
var formattedFields = this.formatFields(fields);
// If no color is specified and there are no infos, there is no need to
// wrap the value in a span element.#
if ((color === undefined || formattedFields === '') && infos.length === 0)
return formattedFields;
var fieldEl = document.createElement('span');
fieldEl.style.display = 'flex';
fieldEl.style.alignItems = 'center';
fieldEl.style.justifyContent = 'flex-end';
Polymer.dom(fieldEl).appendChild(
tr.ui.b.asHTMLOrTextNode(formattedFields));
// Add info icons with tooltips.
infos.forEach(function(info) {
var infoEl = document.createElement('span');
infoEl.style.paddingLeft = '4px';
infoEl.style.cursor = 'help';
infoEl.style.fontWeight = 'bold';
Polymer.dom(infoEl).textContent = info.icon;
if (info.color !== undefined)
infoEl.style.color = info.color;
infoEl.title = info.message;
Polymer.dom(fieldEl).appendChild(infoEl);
}, this);
// Set the color of the element.
if (color !== undefined)
fieldEl.style.color = color;
return fieldEl;
},
/**
* Returns true iff all fields of a row which are relevant for the current
* aggregation mode (e.g. first and last field for diff mode) are undefined.
*/
hasAllRelevantFieldsUndefined: function(fields) {
if (fields === undefined)
return true;
switch (this.aggregationMode) {
case MemoryColumn.AggregationMode.DIFF:
// Only the first and last field are relevant.
return fields[0] === undefined &&
fields[fields.length - 1] === undefined;
case MemoryColumn.AggregationMode.MAX:
default:
// All fields are relevant.
return fields.every(function(field) { return field === undefined; });
}
},
/**
* Get the color of the given fields formatted by this column. At least one
* field relevant for the current aggregation mode is guaranteed to be
* defined.
*/
color: function(fields, contexts) {
return undefined;
},
/**
* Format an arbitrary number of fields. At least one field relevant for
* the current aggregation mode is guaranteed to be defined.
*/
formatFields: function(fields) {
if (fields.length === 1)
return this.formatSingleField(fields[0]);
else
return this.formatMultipleFields(fields);
},
/**
* Format a single defined field.
*
* This method is intended to be overriden by field type specific columns
* (e.g. show '1.0 KiB' instead of '1024' for ScalarNumeric(s) representing
* bytes).
*/
formatSingleField: function(field) {
throw new Error('Not implemented');
},
/**
* Format multiple fields. At least one field relevant for the current
* aggregation mode is guaranteed to be defined.
*
* The aggregation mode specializations of this method (e.g.
* formatMultipleFieldsDiff) are intended to be overriden by field type
* specific columns.
*/
formatMultipleFields: function(fields) {
switch (this.aggregationMode) {
case MemoryColumn.AggregationMode.DIFF:
return this.formatMultipleFieldsDiff(
fields[0], fields[fields.length - 1]);
case MemoryColumn.AggregationMode.MAX:
return this.formatMultipleFieldsMax(fields);
default:
return tr.ui.b.createSpan({
textContent: '(unsupported aggregation mode)',
italic: true
});
}
},
formatMultipleFieldsDiff: function(firstField, lastField) {
throw new Error('Not implemented');
},
formatMultipleFieldsMax: function(fields) {
return this.formatSingleField(this.getMaxField(fields));
},
cmp: function(rowA, rowB) {
var fieldsA = this.fields(rowA);
var fieldsB = this.fields(rowB);
// Sanity check.
if (fieldsA !== undefined && fieldsB !== undefined &&
fieldsA.length !== fieldsB.length)
throw new Error('Different number of fields');
// Handle empty fields.
var undefinedA = this.hasAllRelevantFieldsUndefined(fieldsA);
var undefinedB = this.hasAllRelevantFieldsUndefined(fieldsB);
if (undefinedA && undefinedB)
return 0;
if (undefinedA)
return -1;
if (undefinedB)
return 1;
return this.compareFields(fieldsA, fieldsB);
},
/**
* Compare a pair of single or multiple fields. At least one field relevant
* for the current aggregation mode is guaranteed to be defined in each of
* the two lists.
*/
compareFields: function(fieldsA, fieldsB) {
if (fieldsA.length === 1)
return this.compareSingleFields(fieldsA[0], fieldsB[0]);
else
return this.compareMultipleFields(fieldsA, fieldsB);
},
/**
* Compare a pair of single defined fields.
*
* This method is intended to be overriden by field type specific columns.
*/
compareSingleFields: function(fieldA, fieldB) {
throw new Error('Not implemented');
},
/**
* Compare a pair of multiple fields. At least one field relevant for the
* current aggregation mode is guaranteed to be defined in each of the two
* lists.
*
* The aggregation mode specializations of this method (e.g.
* compareMultipleFieldsDiff) are intended to be overriden by field type
* specific columns.
*/
compareMultipleFields: function(fieldsA, fieldsB) {
switch (this.aggregationMode) {
case MemoryColumn.AggregationMode.DIFF:
return this.compareMultipleFieldsDiff(
fieldsA[0], fieldsA[fieldsA.length - 1],
fieldsB[0], fieldsB[fieldsB.length - 1]);
case MemoryColumn.AggregationMode.MAX:
return this.compareMultipleFieldsMax(fieldsA, fieldsB);
default:
return 0;
}
},
compareMultipleFieldsDiff: function(firstFieldA, lastFieldA, firstFieldB,
lastFieldB) {
throw new Error('Not implemented');
},
compareMultipleFieldsMax: function(fieldsA, fieldsB) {
return this.compareSingleFields(
this.getMaxField(fieldsA), this.getMaxField(fieldsB));
},
getMaxField: function(fields) {
return fields.reduce(function(accumulator, field) {
if (field === undefined)
return accumulator;
if (accumulator === undefined ||
this.compareSingleFields(field, accumulator) > 0) {
return field;
}
return accumulator;
}.bind(this), undefined);
},
addInfos: function(fields, contexts, infos) {
// No generic infos.
},
getImportance: function(importanceRules) {
if (importanceRules.length === 0)
return 0;
// Find the first matching rule.
var matchingRule =
MemoryColumn.findMatchingRule(this.name, importanceRules);
if (matchingRule !== undefined)
return matchingRule.importance;
// No matching rule. Return lower importance than all rules.
var minImportance = importanceRules[0].importance;
for (var i = 1; i < importanceRules.length; i++)
minImportance = Math.min(minImportance, importanceRules[i].importance);
return minImportance - 1;
}
};
/**
* @constructor
*/
function StringMemoryColumn(name, cellPath, aggregationMode) {
MemoryColumn.call(this, name, cellPath, aggregationMode);
}
StringMemoryColumn.prototype = {
__proto__: MemoryColumn.prototype,
formatSingleField: function(string) {
return string;
},
formatMultipleFieldsDiff: function(firstString, lastString) {
if (firstString === undefined) {
// String was added ("+NEW_VALUE" in red).
var spanEl = tr.ui.b.createSpan({color: 'red'});
Polymer.dom(spanEl).appendChild(tr.ui.b.asHTMLOrTextNode('+'));
Polymer.dom(spanEl).appendChild(tr.ui.b.asHTMLOrTextNode(
this.formatSingleField(lastString)));
return spanEl;
} else if (lastString === undefined) {
// String was removed ("-OLD_VALUE" in green).
var spanEl = tr.ui.b.createSpan({color: 'green'});
Polymer.dom(spanEl).appendChild(tr.ui.b.asHTMLOrTextNode('-'));
Polymer.dom(spanEl).appendChild(tr.ui.b.asHTMLOrTextNode(
this.formatSingleField(firstString)));
return spanEl;
} else if (firstString === lastString) {
// String didn't change ("VALUE" with unchanged color).
return this.formatSingleField(firstString);
} else {
// String changed ("OLD_VALUE -> NEW_VALUE" in orange).
var spanEl = tr.ui.b.createSpan({color: 'DarkOrange'});
Polymer.dom(spanEl).appendChild(tr.ui.b.asHTMLOrTextNode(
this.formatSingleField(firstString)));
Polymer.dom(spanEl).appendChild(tr.ui.b.asHTMLOrTextNode(
' ' + RIGHTWARDS_ARROW + ' '));
Polymer.dom(spanEl).appendChild(tr.ui.b.asHTMLOrTextNode(
this.formatSingleField(lastString)));
return spanEl;
}
},
compareSingleFields: function(stringA, stringB) {
return COLLATOR.compare(stringA, stringB);
},
compareMultipleFieldsDiff: function(firstStringA, lastStringA, firstStringB,
lastStringB) {
// If one of the strings was added (and the other one wasn't), mark the
// corresponding diff as greater.
if (firstStringA === undefined && firstStringB !== undefined)
return 1;
if (firstStringA !== undefined && firstStringB === undefined)
return -1;
// If both strings were added, compare the last values (greater last
// value implies greater diff).
if (firstStringA === undefined && firstStringB === undefined)
return this.compareSingleFields(lastStringA, lastStringB);
// If one of the strings was removed (and the other one wasn't), mark the
// corresponding diff as lower.
if (lastStringA === undefined && lastStringB !== undefined)
return -1;
if (lastStringA !== undefined && lastStringB === undefined)
return 1;
// If both strings were removed, compare the first values (greater first
// value implies smaller (!) diff).
if (lastStringA === undefined && lastStringB === undefined)
return this.compareSingleFields(firstStringB, firstStringA);
var areStringsAEqual = firstStringA === lastStringA;
var areStringsBEqual = firstStringB === lastStringB;
// Consider diffs of strings that did not change to be smaller than diffs
// of strings that did change.
if (areStringsAEqual && areStringsBEqual)
return 0;
if (areStringsAEqual)
return -1;
if (areStringsBEqual)
return 1;
// Both strings changed. We are unable to determine the ordering of the
// diffs.
return 0;
}
};
/**
* @constructor
*/
function NumericMemoryColumn(name, cellPath, aggregationMode) {
MemoryColumn.call(this, name, cellPath, aggregationMode);
}
// Avoid tiny positive/negative diffs (displayed in the UI as '+0.0 B' and
// '-0.0 B') due to imprecise floating-point arithmetic by treating all diffs
// within the (-DIFF_EPSILON, DIFF_EPSILON) range as zeros.
NumericMemoryColumn.DIFF_EPSILON = 0.0001;
NumericMemoryColumn.prototype = {
__proto__: MemoryColumn.prototype,
align: tr.ui.b.TableFormat.ColumnAlignment.RIGHT,
aggregateCells: function(row, subRows) {
var subRowCells = subRows.map(this.cell, this);
// Determine if there is at least one defined numeric in the sub-row
// cells and the timestamp count.
var hasDefinedSubRowNumeric = false;
var timestampCount = undefined;
subRowCells.forEach(function(subRowCell) {
if (subRowCell === undefined)
return;
var subRowNumerics = subRowCell.fields;
if (subRowNumerics === undefined)
return;
if (timestampCount === undefined)
timestampCount = subRowNumerics.length;
else if (timestampCount !== subRowNumerics.length)
throw new Error('Sub-rows have different numbers of timestamps');
if (hasDefinedSubRowNumeric)
return; // Avoid unnecessary traversals of the numerics.
hasDefinedSubRowNumeric = subRowNumerics.some(function(numeric) {
return numeric !== undefined;
});
});
if (!hasDefinedSubRowNumeric)
return; // No numeric to aggregate.
// Get or create the row cell.
var cellPath = this.cellPath;
var rowCell = row;
for (var i = 0; i < cellPath.length; i++) {
var nextStepName = cellPath[i];
var nextStep = rowCell[nextStepName];
if (nextStep === undefined) {
if (i < cellPath.length - 1)
nextStep = {};
else
nextStep = new MemoryCell(undefined);
rowCell[nextStepName] = nextStep;
}
rowCell = nextStep;
}
if (rowCell.fields === undefined) {
rowCell.fields = new Array(timestampCount);
} else if (rowCell.fields.length !== timestampCount) {
throw new Error(
'Row has a different number of timestamps than sub-rows');
}
for (var i = 0; i < timestampCount; i++) {
if (rowCell.fields[i] !== undefined)
continue;
rowCell.fields[i] = tr.model.MemoryAllocatorDump.aggregateNumerics(
subRowCells.map(function(subRowCell) {
if (subRowCell === undefined || subRowCell.fields === undefined)
return undefined;
return subRowCell.fields[i];
}));
}
},
formatSingleField: function(numeric) {
var formattingContext = this.getFormattingContext(numeric.unit);
var config = formattingContext !== undefined ?
{ context: formattingContext } : undefined;
return tr.v.ui.createScalarSpan(numeric, config);
},
getFormattingContext: function(unit) {
return undefined;
},
formatMultipleFieldsDiff: function(firstNumeric, lastNumeric) {
return this.formatSingleField(
this.getDiffField_(firstNumeric, lastNumeric));
},
compareSingleFields: function(numericA, numericB) {
return numericA.value - numericB.value;
},
compareMultipleFieldsDiff: function(firstNumericA, lastNumericA,
firstNumericB, lastNumericB) {
return this.getDiffFieldValue_(firstNumericA, lastNumericA) -
this.getDiffFieldValue_(firstNumericB, lastNumericB);
},
getDiffField_: function(firstNumeric, lastNumeric) {
var definedNumeric = firstNumeric || lastNumeric;
return new tr.v.ScalarNumeric(definedNumeric.unit.correspondingDeltaUnit,
this.getDiffFieldValue_(firstNumeric, lastNumeric));
},
getDiffFieldValue_: function(firstNumeric, lastNumeric) {
var firstValue = firstNumeric === undefined ? 0 : firstNumeric.value;
var lastValue = lastNumeric === undefined ? 0 : lastNumeric.value;
var diff = lastValue - firstValue;
return Math.abs(diff) < NumericMemoryColumn.DIFF_EPSILON ? 0 : diff;
}
};
/**
* @constructor
*/
function MemoryCell(fields) {
this.fields = fields;
}
MemoryCell.extractFields = function(cell) {
if (cell === undefined)
return undefined;
return cell.fields;
};
/** Limit for the number of sub-rows for recursive table row expansion. */
var RECURSIVE_EXPANSION_MAX_VISIBLE_ROW_COUNT = 10;
function expandTableRowsRecursively(table) {
var currentLevelRows = table.tableRows;
var totalVisibleRowCount = currentLevelRows.length;
while (currentLevelRows.length > 0) {
// Calculate the total number of sub-rows on the current level.
var nextLevelRowCount = 0;
currentLevelRows.forEach(function(currentLevelRow) {
var subRows = currentLevelRow.subRows;
if (subRows === undefined || subRows.length === 0)
return;
nextLevelRowCount += subRows.length;
});
// Determine whether expanding all rows on the current level would cause
// the total number of visible rows go over the limit.
if (totalVisibleRowCount + nextLevelRowCount >
RECURSIVE_EXPANSION_MAX_VISIBLE_ROW_COUNT) {
break;
}
// Expand all rows on the current level and gather their sub-rows.
var nextLevelRows = new Array(nextLevelRowCount);
var nextLevelRowIndex = 0;
currentLevelRows.forEach(function(currentLevelRow) {
var subRows = currentLevelRow.subRows;
if (subRows === undefined || subRows.length === 0)
return;
table.setExpandedForTableRow(currentLevelRow, true);
subRows.forEach(function(subRow) {
nextLevelRows[nextLevelRowIndex++] = subRow;
});
});
// Update the total number of visible rows and progress to the next level.
totalVisibleRowCount += nextLevelRowCount;
currentLevelRows = nextLevelRows;
}
}
function aggregateTableRowCellsRecursively(row, columns, opt_predicate) {
var subRows = row.subRows;
if (subRows === undefined || subRows.length === 0)
return;
subRows.forEach(function(subRow) {
aggregateTableRowCellsRecursively(subRow, columns, opt_predicate);
});
if (opt_predicate === undefined || opt_predicate(row.contexts))
aggregateTableRowCells(row, subRows, columns);
}
function aggregateTableRowCells(row, subRows, columns) {
columns.forEach(function(column) {
if (!(column instanceof MemoryColumn))
return;
column.aggregateCells(row, subRows);
});
}
function createCells(timeToValues, valueFieldsGetter, opt_this) {
opt_this = opt_this || this;
var fieldNameToFields = tr.b.invertArrayOfDicts(
timeToValues, valueFieldsGetter, opt_this);
return tr.b.mapItems(fieldNameToFields, function(fieldName, fields) {
return new tr.ui.analysis.MemoryCell(fields);
});
}
function createWarningInfo(message) {
return {
message: message,
icon: String.fromCharCode(9888),
color: 'red'
};
}
// TODO(petrcermak): Use a context manager instead
// (https://github.com/catapult-project/catapult/issues/2420).
function DetailsNumericMemoryColumn(name, cellPath, aggregationMode) {
NumericMemoryColumn.call(this, name, cellPath, aggregationMode);
}
DetailsNumericMemoryColumn.prototype = {
__proto__: NumericMemoryColumn.prototype,
getFormattingContext: function(unit) {
if (unit.baseUnit === tr.b.Unit.byName.sizeInBytes)
return { unitPrefix: tr.b.UnitScale.Binary.KIBI };
return undefined;
}
};
return {
TitleColumn: TitleColumn,
MemoryColumn: MemoryColumn,
StringMemoryColumn: StringMemoryColumn,
NumericMemoryColumn: NumericMemoryColumn,
MemoryCell: MemoryCell,
expandTableRowsRecursively: expandTableRowsRecursively,
aggregateTableRowCellsRecursively: aggregateTableRowCellsRecursively,
aggregateTableRowCells: aggregateTableRowCells,
createCells: createCells,
createWarningInfo: createWarningInfo,
DetailsNumericMemoryColumn: DetailsNumericMemoryColumn
};
});
</script>