blob: 67b224e541e0dd84bfc8a738420fbd3c0bd997a8 [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright 2016 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/value/ui/histogram_set_table_cell.html">
<link rel="import" href="/tracing/value/ui/histogram_set_table_name_cell.html">
<script>
'use strict';
tr.exportTo('tr.v.ui', function() {
class HistogramSetTableRow {
constructor(name) {
this.name = name;
this.description = '';
this.depth = 0;
this.subRows = [];
this.columns = new Map();
this.nameCell_ = undefined;
this.cells = new Map();
this.constrainNameColumnWidth_ = false;
this.overviewDataRange_ = undefined;
this.displayStatistic_ = 'avg';
this.doMergeRelationshipsForColumn_ = new Map();
}
/**
* Clones and filters |rows| to contain only |histograms|.
*
* @param {!Array.<HistogramSetTableRow>} rows
* @param {!tr.v.HistogramSet} histograms
* @returns {!Array.<HistogramSetTableRow>}
*/
static filter(rows, histograms) {
let results = [];
for (let row of rows) {
let filteredSubRows = [];
if (row.subRows.length > 0) {
// This is a branch row. Drop it if all of its subrows were dropped.
filteredSubRows = HistogramSetTableRow.filter(
row.subRows, histograms);
if (filteredSubRows.length === 0) continue;
} else {
// This is a leaf row. Drop it if none of the Histograms in
// |row.columns| were merged from any in |histograms|.
let found = false;
for (let testHist of row.columns.values()) {
if (!(testHist instanceof tr.v.Histogram)) continue;
if (histograms.lookupHistogram(testHist.guid) !== undefined) {
found = true;
break;
}
let mergedFrom = testHist.diagnostics.get(
tr.v.d.MERGED_FROM_DIAGNOSTIC_KEY);
if (mergedFrom !== undefined) {
for (let origHist of mergedFrom) {
if (histograms.lookupHistogram(origHist.guid) !== undefined) {
found = true;
break;
}
}
}
if (found) break;
}
// If none of the Histograms in |row| were merged from any of
// |histograms|, then drop this row.
if (!found) continue;
}
let clone = new HistogramSetTableRow(row.name);
clone.description = row.description;
clone.depth = row.depth;
clone.subRows = filteredSubRows;
// Don't need to clone Histograms.
clone.columns = row.columns;
// Don't need to rebuild nameCell or cells.
clone.nameCell_ = row.nameCell_;
clone.cells = row.cells;
clone.constrainNameColumnWidth_ = row.constrainNameColumnWidth_;
clone.overviewDataRange_ = row.overviewDataRange_;
clone.displayStatistic_ = row.displayStatistic_;
results.push(clone);
}
return results;
}
/**
* Build table rows recursively from grouped Histograms.
*
* @param {!(HistogramArray|HistogramArrayMap)}
* @returns {!Array.<!HistogramSetTableRow>}
*/
static build(histogramArrayMap) {
const rootRows = [];
HistogramSetTableRow.buildInternal_(histogramArrayMap, [], rootRows);
const histograms = new tr.v.HistogramSet();
for (const row of HistogramSetTableRow.walkAll(rootRows)) {
for (const hist of row.columns.values()) {
if (!(hist instanceof tr.v.Histogram)) continue;
histograms.addHistogram(hist);
}
}
histograms.deduplicateDiagnostics();
for (const row of HistogramSetTableRow.walkAll(rootRows)) {
for (const [name, hist] of row.columns) {
if (!(hist instanceof tr.v.Histogram)) continue;
if (!row.doMergeRelationshipsForColumn_.get(name)) continue;
hist.diagnostics.mergeRelationships(hist);
}
}
// Delete "merged to" diagnostics from the original Histograms, or else
// they'll accumulate as the user re-groups them, and slow down future
// mergeRelationships operations.
for (const row of HistogramSetTableRow.walkAll(rootRows)) {
// Walk directly down to the leaves in order to avoid touching
// original Histograms more than once.
if (row.subRows.length) continue;
for (const hist of row.columns.values()) {
if (!(hist instanceof tr.v.Histogram)) continue;
const mergedFrom = hist.diagnostics.get(
tr.v.MERGED_FROM_DIAGNOSTIC_KEY);
if (mergedFrom !== undefined) {
for (const other of mergedFrom) {
other.diagnostics.delete(tr.v.MERGED_TO_DIAGNOSTIC_KEY);
}
}
}
}
for (const row of HistogramSetTableRow.walkAll(rootRows)) {
row.maybeRebin_();
}
return rootRows;
}
* walk() {
yield this;
for (const row of this.subRows) yield* row.walk();
}
static* walkAll(rootRows) {
for (const rootRow of rootRows) yield* rootRow.walk();
}
maybeRebin_() {
// if all of |this| row's columns are single-bin, then re-bin all of them.
const dataRange = new tr.b.math.Range();
for (const hist of this.columns.values()) {
if (!(hist instanceof tr.v.Histogram)) continue;
if (hist.allBins.length > 1) return; // don't re-bin
if (hist.numValues === 0) continue; // ignore hist
dataRange.addValue(hist.min);
dataRange.addValue(hist.max);
}
dataRange.addValue(tr.b.math.lesserWholeNumber(dataRange.min));
dataRange.addValue(tr.b.math.greaterWholeNumber(dataRange.max));
if (dataRange.min === dataRange.max) return; // don't rebin
const boundaries = tr.v.HistogramBinBoundaries.createLinear(
dataRange.min, dataRange.max, tr.v.DEFAULT_REBINNED_COUNT);
for (const [name, hist] of this.columns) {
if (!(hist instanceof tr.v.Histogram)) continue;
this.columns.set(name, hist.rebin(boundaries));
}
}
static mergeHistogramDownHierarchy_(histogram, hierarchy, columnName) {
// Track the path down the grouping tree to each Histogram,
// but only start tracking the path at the grouping level that
// corresponds to the Histogram NAME Grouping. This groupingPath will be
// attached to Histograms in order to help mergeRelationships() figure out
// which merged Histograms should be related to which other merged
// Histograms.
let groupingPath = undefined;
for (let row of hierarchy) {
if (groupingPath !== undefined) {
groupingPath.push(row.name);
} else if (row.name === histogram.name) {
// Start tracking the path, but don't add histogram.name to the path,
// since related histograms won't have the same name.
groupingPath = [];
}
if (!row.description) {
row.description = histogram.description;
}
if (row.columns.get(columnName) === undefined) {
let clone = histogram.clone();
if (groupingPath !== undefined) {
new tr.v.d.GroupingPath(groupingPath).addToHistogram(clone);
}
row.columns.set(columnName, clone);
row.doMergeRelationshipsForColumn_.set(columnName, true);
continue;
}
if (!(row.columns.get(columnName) instanceof tr.v.Histogram)) continue;
if (!row.columns.get(columnName).canAddHistogram(histogram)) {
row.columns.set(columnName, tr.v.ui.UNMERGEABLE);
continue;
}
let merged = row.columns.get(columnName);
// If row.columns.get(columnName).name != histogram.name, then it won't
// make sense to merge relationships for this histogram.
if (merged.name !== histogram.name) {
row.doMergeRelationshipsForColumn_.set(name, false);
}
merged.addHistogram(histogram);
}
}
static buildInternal_(histogramArrayMap, hierarchy, rootRows) {
for (let [name, histograms] of histogramArrayMap) {
if (histograms instanceof Array) {
// This recursion base case corresponds to the recursion base case of
// groupHistogramsRecursively(). The last groupingCallback is always
// getDisplayLabel, which defines the columns of the table and is
// unskippable.
for (let histogram of histograms) {
HistogramSetTableRow.mergeHistogramDownHierarchy_(
histogram, hierarchy, name);
}
} else if (histograms instanceof Map) {
// |histograms| is actually a nested histogramArrayMap.
let row = new HistogramSetTableRow(name);
row.depth = hierarchy.length;
hierarchy.push(row);
HistogramSetTableRow.buildInternal_(histograms, hierarchy, rootRows);
hierarchy.pop();
if (hierarchy.length === 0) {
rootRows.push(row);
} else {
hierarchy[hierarchy.length - 1].subRows.push(row);
}
}
}
}
get nameCell() {
if (this.nameCell_ === undefined) {
this.nameCell_ = document.createElement(
'tr-v-ui-histogram-set-table-name-cell');
this.nameCell_.row = this;
this.nameCell_.constrainWidth = this.constrainNameColumnWidth_;
}
return this.nameCell_;
}
set constrainNameColumnWidth(constrain) {
for (const row of this.walk()) {
row.constrainNameColumnWidth_ = constrain;
if (row.nameCell_ !== undefined) {
row.nameCell_.constrainWidth = constrain;
}
}
}
get isNameCellOverflowing() {
for (const row of this.walk()) {
if (row.nameCell.isOverflowing) return true;
}
return false;
}
get displayStatistic() {
return this.displayStatistic_;
}
set displayStatistic(statName) {
for (const row of this.walk()) {
row.displayStatistic_ = statName;
for (let [displayLabel, cell] of row.cells) {
cell.displayStatistic = statName;
}
}
}
buildCell(displayLabel, referenceDisplayLabel) {
let cell = document.createElement('tr-v-ui-histogram-set-table-cell');
cell.row = this;
cell.histogram = this.columns.get(displayLabel);
cell.displayStatistic = this.displayStatistic;
if (referenceDisplayLabel &&
referenceDisplayLabel !== displayLabel) {
cell.referenceHistogram = this.columns.get(
referenceDisplayLabel);
}
this.cells.set(displayLabel, cell);
return cell;
}
get overviewDataRange() {
if (this.overviewDataRange_ === undefined) {
this.overviewDataRange_ = new tr.b.math.Range();
for (let [displayLabel, hist] of this.columns) {
if (hist.average !== undefined) {
this.overviewDataRange_.addValue(hist.average);
}
for (let subRow of this.subRows) {
let subHist = subRow.columns.get(displayLabel);
if (!(subHist instanceof tr.v.Histogram)) continue;
if (subHist.average === undefined) continue;
this.overviewDataRange_.addValue(subHist.average);
}
}
}
return this.overviewDataRange_;
}
getLeafHistograms(histograms) {
for (const row of this.walk()) {
if (row.subRows.length) return;
for (const hist of this.columns.values()) {
histograms.addHistogram(hist);
}
}
}
compareNames(other) {
return this.name.localeCompare(other.name);
}
compareCells(other, displayLabel, referenceDisplayLabel) {
let cellA = this.columns.get(displayLabel);
let cellB = other.columns.get(displayLabel);
if (!(cellA instanceof tr.v.Histogram) ||
!(cellB instanceof tr.v.Histogram)) {
return undefined;
}
let referenceCellA;
let referenceCellB;
// If a reference column is selected, compare the absolute deltas
// between the two cells and their references.
if (referenceDisplayLabel &&
referenceDisplayLabel !== displayLabel) {
referenceCellA = this.columns.get(referenceDisplayLabel);
referenceCellB = other.columns.get(referenceDisplayLabel);
}
const statisticA = cellA.getAvailableStatisticName(
this.displayStatistic, referenceCellA);
const statisticB = cellB.getAvailableStatisticName(
this.displayStatistic, referenceCellB);
const valueA = cellA.getStatisticScalar(statisticA, referenceCellA).value;
const valueB = cellB.getStatisticScalar(statisticB, referenceCellB).value;
return valueA - valueB;
}
getExpansionStates(table) {
let states = {
expanded: table.getExpandedForTableRow(this),
cells: new Map(),
subRows: new Map(),
};
for (let [displayLabel, cell] of this.cells) {
if (cell.isHistogramOpen) {
states.cells.set(displayLabel, true);
}
}
if (states.expanded) {
for (let i = 0; i < this.subRows.length; ++i) {
states.subRows.set(i, this.subRows[i].getExpansionStates(table));
}
}
return states;
}
setExpansionStates(states, table) {
if (states.expanded) {
if (this.subRows.length) {
table.setExpandedForTableRow(this, true);
for (let [index, subStates] of states.subRows) {
this.subRows[index].setExpansionStates(subStates, table);
}
}
}
for (let [displayLabel, isHistogramOpen] of states.cells) {
let cell = this.cells.get(displayLabel);
if (cell) {
cell.isHistogramOpen = isHistogramOpen;
}
}
}
}
return {
HistogramSetTableRow,
};
});
</script>