Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(356)

Unified Diff: tracing/tracing/value/histogram.html

Issue 2364243002: Serialize Histograms more efficiently. (Closed)
Patch Set: RunningStatistics.asDict undefined Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tracing/tracing/value/histogram.html
diff --git a/tracing/tracing/value/histogram.html b/tracing/tracing/value/histogram.html
index 32c1a467228788b3085c5cc8f51a88ae38da1708..34bbb4a2ec9e469c386676f65910544cf428f59c 100644
--- a/tracing/tracing/value/histogram.html
+++ b/tracing/tracing/value/histogram.html
@@ -56,19 +56,42 @@ tr.exportTo('tr.v', function() {
}
fromDict(d) {
- this.count = d.count;
- for (var map of d.diagnosticMaps)
- this.diagnosticMaps.push(tr.v.d.DiagnosticMap.fromDict(map));
+ if (d instanceof Array) {
+ this.count = d[0];
+ for (var map of d[1]) {
+ this.diagnosticMaps.push(tr.v.d.DiagnosticMap.fromDict(map));
+ }
+ } else {
+ this.count = d;
+ }
}
asDict() {
- return {
- count: this.count,
- diagnosticMaps: this.diagnosticMaps.map(d => d.asDict())
- };
+ if (!this.diagnosticMaps.length) {
+ // This may be 0, in which case Histogram.asDict() can elide it.
+ return this.count;
nednguyen 2016/09/27 18:48:54 ping on not special casing on this?
benjhayden 2016/09/27 21:05:17 Done.
+ }
+ // It's more efficient to serialize these 2 fields in an array. If you
+ // add any other fields, you should re-evaluate whether it would be more
+ // efficient to serialize as a dict.
+ return Object.freeze([
+ this.count,
+ this.diagnosticMaps.map(d => d.asDict())
+ ]);
}
}
+ var DEFAULT_SUMMARY_OPTIONS = new Map([
nednguyen 2016/09/27 18:48:54 nits: Object.freeze(..) this?
benjhayden 2016/09/27 21:05:16 Hm, it looks like Object.freeze doesn't work on Ma
+ ['avg', true],
+ ['geometricMean', false],
+ ['std', true],
+ ['count', true],
+ ['sum', true],
+ ['min', true],
+ ['max', true],
+ ['nans', false],
+ ]);
+
/**
* This is basically a histogram, but so much more.
* Histogram is serializable using asDict/fromDict.
@@ -99,6 +122,7 @@ tr.exportTo('tr.v', function() {
this.guid_ = undefined;
this.allBins = [];
+ this.binBoundariesBuilderDict_ = binBoundaries.asDict();
nednguyen 2016/09/27 18:48:54 Oh, now I see that the API is binBoundaries.asDict
benjhayden 2016/09/27 21:05:17 I added a comment. It's possible that binBoundarie
this.centralBins = [];
this.description = '';
this.diagnostics = new tr.v.d.DiagnosticMap();
@@ -109,33 +133,37 @@ tr.exportTo('tr.v', function() {
this.running = new tr.b.RunningStatistics();
this.sampleValues_ = [];
this.shortName = undefined;
- this.summaryOptions = {
- avg: true,
- geometricMean: false,
- std: true,
- count: true,
- sum: true,
- min: true,
- max: true,
- nans: false,
- percentile: []
- };
+ this.summaryOptions = new Map(DEFAULT_SUMMARY_OPTIONS);
+ this.summaryOptions.set('percentile', []);
nednguyen 2016/09/27 18:48:54 Why not put 'percentile' in DEFAULT_SUMMARY_OPTION
benjhayden 2016/09/27 21:05:17 I added a comment to DEFAULT_SUMMARY_OPTIONS. Its
this.unit = unit;
this.underflowBin = new HistogramBin(tr.b.Range.fromExplicitRange(
- -Number.MAX_VALUE, binBoundaries.minBinBoundary));
+ -Number.MAX_VALUE, binBoundaries.range.min));
this.overflowBin = new HistogramBin(tr.b.Range.fromExplicitRange(
- binBoundaries.maxBinBoundary, Number.MAX_VALUE));
+ binBoundaries.range.max, Number.MAX_VALUE));
- for (var range of binBoundaries)
+ for (var range of binBoundaries.binRanges()) {
this.centralBins.push(new HistogramBin(range));
+ }
this.allBins.push(this.underflowBin);
for (var bin of this.centralBins)
this.allBins.push(bin);
this.allBins.push(this.overflowBin);
- this.maxNumSampleValues = this.allBins.length * 10;
+ this.maxNumSampleValues_ = this.defaultMaxNumSampleValues_;
+ }
+
+ get maxNumSampleValues() {
+ return this.maxNumSampleValues_;
+ }
+
+ set maxNumSampleValues(n) {
+ this.maxNumSampleValues_ = n;
+ while (this.maxNumSampleValues < this.sampleValues_.length) {
+ var i = parseInt(Math.random() * this.sampleValues_.length);
nednguyen 2016/09/27 18:48:54 Ping on refactor this to a helper method
benjhayden 2016/09/27 21:05:17 Done.
+ this.sampleValues_.splice(i, 1);
+ }
}
get name() {
@@ -156,36 +184,62 @@ tr.exportTo('tr.v', function() {
this.guid_ = guid;
}
- static fromDict(d) {
- var boundaries = HistogramBinBoundaries.createWithBoundaries(
- d.binBoundaries);
- var n = new Histogram(d.name, tr.b.Unit.fromJSON(d.unit), boundaries);
- n.guid = d.guid;
- n.shortName = d.shortName;
- n.description = d.description;
- n.diagnostics.addDicts(d.diagnostics);
-
- n.underflowBin.fromDict(d.underflowBin);
- for (var i = 0; i < d.centralBins.length; ++i)
- n.centralBins[i].fromDict(d.centralBins[i]);
- n.overflowBin.fromDict(d.overflowBin);
-
- for (var bin of n.allBins)
- n.maxCount_ = Math.max(n.maxCount_, bin.count);
-
- if (d.running)
- n.running = tr.b.RunningStatistics.fromDict(d.running);
- if (d.summaryOptions)
- n.customizeSummaryOptions(d.summaryOptions);
-
- n.maxNumSampleValues = d.maxNumSampleValues;
- n.sampleValues_ = d.sampleValues;
-
- n.numNans = d.numNans;
- for (var map of d.nanDiagnosticMaps)
- n.nanDiagnosticMaps.push(tr.v.d.DiagnosticMap.fromDict(map));
-
- return n;
+ static fromDict(dict) {
+ var hist = new Histogram(dict.name,
+ tr.b.Unit.fromJSON(dict.unit),
+ HistogramBinBoundaries.fromDict(
+ dict.binBoundaries));
+ hist.guid = dict.guid;
+ if (dict.shortName) {
+ hist.shortName = dict.shortName;
+ }
+ if (dict.description) {
+ hist.description = dict.description;
+ }
+ if (dict.diagnostics) {
+ hist.diagnostics.addDicts(dict.diagnostics);
+ }
+ if (dict.underflowBin) {
+ hist.underflowBin.fromDict(dict.underflowBin);
+ }
+ if (dict.overflowBin) {
+ hist.overflowBin.fromDict(dict.overflowBin);
+ }
+ if (dict.centralBins) {
+ if (dict.centralBins.length !== undefined) {
+ for (var i = 0; i < dict.centralBins.length; ++i) {
+ hist.centralBins[i].fromDict(dict.centralBins[i]);
+ }
+ } else {
+ tr.b.iterItems(dict.centralBins, (i, binDict) => {
+ hist.centralBins[i].fromDict(binDict);
+ });
+ }
+ }
+ for (var bin of hist.allBins) {
+ hist.maxCount_ = Math.max(hist.maxCount_, bin.count);
+ }
+ if (dict.running) {
+ hist.running = tr.b.RunningStatistics.fromDict(dict.running);
+ }
+ if (dict.summaryOptions) {
+ hist.customizeSummaryOptions(dict.summaryOptions);
+ }
+ if (dict.maxNumSampleValues !== undefined) {
+ hist.maxNumSampleValues = dict.maxNumSampleValues;
+ }
+ if (dict.sampleValues) {
+ hist.sampleValues_ = dict.sampleValues;
+ }
+ if (dict.numNans) {
+ hist.numNans = dict.numNans;
+ }
+ if (dict.nanDiagnostics) {
+ for (var map of dict.nanDiagnostics) {
+ hist.nanDiagnosticMaps.push(tr.v.d.DiagnosticMap.fromDict(map));
+ }
+ }
+ return hist;
}
/**
@@ -390,9 +444,8 @@ tr.exportTo('tr.v', function() {
* The options not included in the |summaryOptions| will not change.
*/
customizeSummaryOptions(summaryOptions) {
- tr.b.iterItems(summaryOptions, function(key, value) {
- this.summaryOptions[key] = value;
- }, this);
+ tr.b.iterItems(summaryOptions, (key, value) =>
+ this.summaryOptions.set(key, value));
}
/**
@@ -441,16 +494,17 @@ tr.exportTo('tr.v', function() {
}
var results = new Map();
- tr.b.iterItems(this.summaryOptions, function(stat, option) {
- if (!option)
- return;
+ for (var [stat, option] of this.summaryOptions) {
+ if (!option) {
+ continue;
+ }
if (stat === 'percentile') {
- option.forEach(function(percent) {
+ for (var percent of option) {
var percentile = this.getApproximatePercentile(percent);
results.set('pct_' + percentToString(percent),
new tr.v.ScalarNumeric(this.unit, percentile));
- }, this);
+ }
} else if (stat === 'nans') {
results.set('nans', new tr.v.ScalarNumeric(
tr.b.Unit.byName.count_smallerIsBetter, this.numNans));
@@ -464,7 +518,7 @@ tr.exportTo('tr.v', function() {
results.set(stat, new tr.v.ScalarNumeric(statUnit, statValue));
}
}
- }, this);
+ }
return results;
}
@@ -472,51 +526,143 @@ tr.exportTo('tr.v', function() {
return this.sampleValues_;
}
- get binBoundaries() {
- var boundaries = [];
- for (var bin of this.centralBins)
- boundaries.push(bin.range.min);
- boundaries.push(this.overflowBin.range.min);
- return boundaries;
- }
-
+ /**
+ * Create a new Histogram object that is exactly the same as this one, with
+ * this Histogram's name, unit, and binBoundaries, guid, bin counts, and
+ * diagnostics.
+ * @return {!tr.v.Histogram}
+ */
clone() {
return Histogram.fromDict(this.asDict());
}
+ /**
+ * Create a new Histogram with this Histogram's name, unit, and
+ * binBoundaries, but not its guid, bin counts, or diagnostics.
+ * @return {!tr.v.Histogram}
+ */
+ cloneEmpty() {
+ var binBoundaries = HistogramBinBoundaries.fromDict(
+ this.binBoundariesBuilderDict_);
+ return new Histogram(this.name, this.unit, binBoundaries);
+ }
+
asDict() {
- return {
- name: this.name,
- guid: this.guid,
- shortName: this.shortName,
- description: this.description,
- diagnostics: this.diagnostics.asDict(),
- unit: this.unit.asJSON(),
- binBoundaries: this.binBoundaries,
+ var dict = {};
+ dict.binBoundaries = this.binBoundariesBuilderDict_;
+ dict.name = this.name;
+ dict.unit = this.unit.asJSON();
+ dict.guid = this.guid;
+ if (this.shortName) {
+ dict.shortName = this.shortName;
+ }
+ if (this.description) {
+ dict.description = this.description;
+ }
+ if (this.diagnostics.size) {
+ dict.diagnostics = this.diagnostics.asDict();
+ }
+ if (this.maxNumSampleValues !== this.defaultMaxNumSampleValues_) {
+ dict.maxNumSampleValues = this.maxNumSampleValues;
+ }
+ if (this.numNans) {
+ dict.numNans = this.numNans;
+ }
+ if (this.nanDiagnosticMaps.length) {
+ dict.nanDiagnostics = this.nanDiagnosticMaps.map(
+ dm => dm.asDict());
+ }
+ var underflowBin = this.underflowBin.asDict();
+ if (tr.b.dictionaryLength(underflowBin)) {
+ dict.underflowBin = underflowBin;
+ }
+ var overflowBin = this.overflowBin.asDict();
+ if (tr.b.dictionaryLength(overflowBin)) {
+ dict.overflowBin = overflowBin;
+ }
- underflowBin: this.underflowBin.asDict(),
- centralBins: this.centralBins.map(bin => bin.asDict()),
- overflowBin: this.overflowBin.asDict(),
+ if (this.numValues) {
+ dict.sampleValues = this.sampleValues.slice();
+ dict.running = this.running.asDict();
+
+ // If all centralBins are empty, then don't serialize anything for them.
+ var anyCentralBins = false;
+
+ // CENTRAL_BINS may be either an array or a dict, whichever is more
+ // efficient.
nednguyen 2016/09/27 18:48:54 Can you factor the logic of serializing CENTRAL_BI
benjhayden 2016/09/27 21:05:16 Done.
+ var centralBinsArray = [];
+ var centralBinsDict = {};
+
+ // Compute which of centralBinsArray or centralBinsDict is more
+ // efficient. Both contain begin/end brackets and non-empty bins, so
+ // ignore those.
+ var centralBinsArrayOverhead = 0;
nednguyen 2016/09/27 18:48:54 I think this is a bit too complicated. Since the o
benjhayden 2016/09/27 21:05:16 If a histogram has 1000 bins and they are all non-
nednguyen 2016/09/27 21:46:29 You convinced me with your number that this is nee
benjhayden 2016/09/27 22:47:50 I didn't expect the tipping point to actually be a
+ var centralBinsDictOverhead = 0;
+
+ for (var bin of this.centralBins) {
+ var binDict = bin.asDict();
+ if (binDict) {
+ anyCentralBins = true;
+ centralBinsDict[centralBinsArray.length] = binDict;
+ // centralBinsDict only contains overhead for non-empty bins.
+ // The overhead is the length of the index plus the colon and comma.
+ centralBinsDictOverhead +=
+ 2 + (centralBinsArray.length + '').length;
+ } else {
+ // centralBinsArray contains overhead for empty bins.
+ // The length of the value (that HistogramBin.asDict() returns when
+ // count === 0) is 1.
+ centralBinsArrayOverhead += 1;
+ }
+ // centralBinsArray contains a comma for every bin.
+ centralBinsArrayOverhead += 1;
+ centralBinsArray.push(binDict);
+ }
+
+ if (anyCentralBins) {
+ if (centralBinsDictOverhead >= centralBinsArrayOverhead) {
+ dict.centralBins = Object.freeze(centralBinsArray);
+ } else {
+ dict.centralBins = Object.freeze(centralBinsDict);
+ }
+ }
+ }
- running: this.running.asDict(),
- summaryOptions: this.summaryOptions,
+ var summaryOptions = {};
+ var anyOverriddenSummaryOptions = false;
+ for (var [name, option] of this.summaryOptions) {
+ if (name === 'percentile') {
+ if (option.length === 0) {
+ continue;
+ }
+ option = option.slice();
+ } else if (option === DEFAULT_SUMMARY_OPTIONS.get(name)) {
+ continue;
+ }
+ summaryOptions[name] = option;
+ anyOverriddenSummaryOptions = true;
+ }
+ if (anyOverriddenSummaryOptions) {
+ dict.summaryOptions = Object.freeze(summaryOptions);
+ }
- maxNumSampleValues: this.maxNumSampleValues,
- sampleValues: this.sampleValues,
+ return Object.freeze(dict);
+ }
- numNans: this.numNans,
- nanDiagnosticMaps: this.nanDiagnosticMaps.map(dm => dm.asDict()),
- };
+ get defaultMaxNumSampleValues_() {
+ return this.allBins.length * 10;
}
}
+ var HISTOGRAM_BIN_BOUNDARIES_CACHE = new Map();
+
/**
* Reusable builder for tr.v.Histogram objects.
*
* The bins of the numeric are specified by adding the desired boundaries
* between bins. Initially, the builder has only a single boundary:
*
- * minBinBoundary=maxBinBoundary
+ * range.min=range.max
* |
* |
* -MAX_INT <--------|------------------------------------------> +MAX_INT
@@ -527,7 +673,7 @@ tr.exportTo('tr.v', function() {
* More boundaries can be added (in increasing order) using addBinBoundary,
* addLinearBins and addExponentialBins:
*
- * minBinBoundary maxBinBoundary
+ * range.min range.max
* | | | | |
* | | | | |
* -MAX_INT <--------|---------|---------|-----|---------|------> +MAX_INT
@@ -538,11 +684,6 @@ tr.exportTo('tr.v', function() {
* An important feature of the builder is that it's reusable, i.e. it can be
* used to build multiple numerics with the same unit and bin structure.
*
- * @constructor
- * @param {!tr.b.Unit} unit Unit of the resulting Histogram(s).
- * @param {number} minBinBoundary The minimum boundary between bins, namely
- * the underflow bin and the first central bin (or the overflow bin if
- * no other boundaries are added later).
*/
class HistogramBinBoundaries {
/**
@@ -603,46 +744,141 @@ tr.exportTo('tr.v', function() {
}
/**
- * @param {number} minBinBoundary
+ * @param {number} minBinBoundary The minimum boundary between bins, namely
+ * the underflow bin and the first central bin (or the overflow bin if
+ * no other boundaries are added later).
*/
constructor(minBinBoundary) {
- this.boundaries_ = [minBinBoundary];
+ this.boundaries_ = undefined;
+ this.builder_ = [minBinBoundary];
+ this.range_ = new tr.b.Range();
+ this.range_.addValue(minBinBoundary);
}
- get minBinBoundary() {
- return this.boundaries_[0];
+ get range() {
+ return this.range_;
}
- get maxBinBoundary() {
- return this.boundaries_[this.boundaries_.length - 1];
+ asDict() {
+ // Deep-copy builder_ in case ours is modified later.
+ var dict = [];
+ for (var slice of this.builder_) {
+ if (slice instanceof Array) {
+ dict.push(Object.freeze(slice.slice()));
+ } else {
+ dict.push(slice);
+ }
+ }
+ return Object.freeze(dict);
+ }
+
+ static fromDict(dict) {
+ // When loading a results2.html with many Histograms with the same bin
+ // boundaries, caching the HistogramBinBoundaries not only speeds up
+ // loading, but also prevents a bug where build_ is occasionally
+ // non-deterministic, which causes similar Histograms to be unmergeable.
+ var cacheKey = JSON.stringify(dict);
+ if (HISTOGRAM_BIN_BOUNDARIES_CACHE.has(cacheKey)) {
+ return HISTOGRAM_BIN_BOUNDARIES_CACHE.get(cacheKey);
+ }
+
+ var binBoundaries = new HistogramBinBoundaries(dict[0]);
+ for (var slice of dict.slice(1)) {
+ if (!(slice instanceof Array)) {
+ binBoundaries.addBinBoundary(slice);
+ continue;
+ }
+ switch (slice[0]) {
+ case HistogramBinBoundaries.SLICE_TYPE.LINEAR:
+ binBoundaries.addLinearBins(slice[1], slice[2]);
+ break;
+
+ case HistogramBinBoundaries.SLICE_TYPE.EXPONENTIAL:
+ binBoundaries.addExponentialBins(slice[1], slice[2]);
+ break;
+
+ default:
+ throw new Error('Unrecognized HistogramBinBoundaries slice type');
+ }
+ }
+ HISTOGRAM_BIN_BOUNDARIES_CACHE.set(cacheKey, binBoundaries);
+ return binBoundaries;
}
/**
* Yield Ranges of adjacent boundaries.
*/
- *[Symbol.iterator]() {
+ *binRanges() {
+ if (this.boundaries_ === undefined) {
+ this.build_();
+ }
for (var i = 0; i < this.boundaries_.length - 1; ++i) {
yield tr.b.Range.fromExplicitRange(
this.boundaries_[i], this.boundaries_[i + 1]);
}
}
+ build_() {
+ if (typeof this.builder_[0] !== 'number') {
+ throw new Error('Invalid start of builder_');
+ }
+ this.boundaries_ = [this.builder_[0]];
+
+ for (var slice of this.builder_.slice(1)) {
+ if (!(slice instanceof Array)) {
+ this.boundaries_.push(slice);
+ continue;
+ }
+ var nextMaxBinBoundary = slice[1];
+ var binCount = slice[2];
+ var curMaxBinBoundary = this.boundaries_[
+ this.boundaries_.length - 1];
+
+ switch (slice[0]) {
+ case HistogramBinBoundaries.SLICE_TYPE.LINEAR:
+ var binWidth = (nextMaxBinBoundary - curMaxBinBoundary) / binCount;
+ for (var i = 1; i < binCount; i++) {
+ var boundary = curMaxBinBoundary + i * binWidth;
+ this.boundaries_.push(boundary);
+ }
+ break;
+
+ case HistogramBinBoundaries.SLICE_TYPE.EXPONENTIAL:
+ var binExponentWidth =
+ Math.log(nextMaxBinBoundary / curMaxBinBoundary) / binCount;
+ for (var i = 1; i < binCount; i++) {
+ var boundary = curMaxBinBoundary * Math.exp(i * binExponentWidth)
+ this.boundaries_.push(boundary);
+ }
+ break;
+
+ default:
+ throw new Error('Unrecognized HistogramBinBoundaries slice type');
+ }
+ this.boundaries_.push(nextMaxBinBoundary);
+ }
+ }
+
/**
* Add a bin boundary |nextMaxBinBoundary| to the builder.
*
* This operation effectively corresponds to appending a new central bin
- * with the range [this.maxBinBoundary*, nextMaxBinBoundary].
+ * with the range [this.range.max, nextMaxBinBoundary].
*
* @param {number} nextMaxBinBoundary The added bin boundary (must be
* greater than |this.maxMinBoundary|).
*/
addBinBoundary(nextMaxBinBoundary) {
- if (nextMaxBinBoundary <= this.maxBinBoundary) {
+ if (nextMaxBinBoundary <= this.range.max) {
throw new Error('The added max bin boundary must be larger than ' +
'the current max boundary');
}
- this.boundaries_.push(nextMaxBinBoundary);
+ // If boundaries_ had been built, then clear them.
+ this.boundaries_ = undefined;
+
+ this.builder_.push(nextMaxBinBoundary);
+ this.range.addValue(nextMaxBinBoundary);
return this;
}
@@ -652,7 +888,7 @@ tr.exportTo('tr.v', function() {
*
* This operation corresponds to appending |binCount| central bins of
* constant range width
- * W = ((|nextMaxBinBoundary| - |this.maxBinBoundary|) / |binCount|)
+ * W = ((|nextMaxBinBoundary| - |this.range.max|) / |binCount|)
* with the following ranges:
*
* [|this.maxMinBoundary|, |this.maxMinBoundary| + W]
@@ -672,17 +908,17 @@ tr.exportTo('tr.v', function() {
if (binCount <= 0)
throw new Error('Bin count must be positive');
- var curMaxBinBoundary = this.maxBinBoundary;
- if (curMaxBinBoundary >= nextMaxBinBoundary) {
+ if (nextMaxBinBoundary <= this.range.max) {
throw new Error('The new max bin boundary must be greater than ' +
'the previous max bin boundary');
}
- var binWidth = (nextMaxBinBoundary - curMaxBinBoundary) / binCount;
- for (var i = 1; i < binCount; i++)
- this.addBinBoundary(curMaxBinBoundary + i * binWidth);
- this.addBinBoundary(nextMaxBinBoundary);
+ // If boundaries_ had been built, then clear them.
+ this.boundaries_ = undefined;
+ this.builder_.push([HistogramBinBoundaries.SLICE_TYPE.LINEAR,
+ nextMaxBinBoundary, binCount]);
+ this.range.addValue(nextMaxBinBoundary);
return this;
}
@@ -692,7 +928,7 @@ tr.exportTo('tr.v', function() {
*
* This operation corresponds to appending |binCount| central bins with
* a constant difference between the logarithms of their range min and max
- * D = ((ln(|nextMaxBinBoundary|) - ln(|this.maxBinBoundary|)) / |binCount|)
+ * D = ((ln(|nextMaxBinBoundary|) - ln(|this.range.max|)) / |binCount|)
* with the following ranges:
*
* [|this.maxMinBoundary|, |this.maxMinBoundary| * exp(D)]
@@ -711,29 +947,32 @@ tr.exportTo('tr.v', function() {
* @param {number} binCount Number of bins to be added (must be positive).
*/
addExponentialBins(nextMaxBinBoundary, binCount) {
- if (binCount <= 0)
+ if (binCount <= 0) {
throw new Error('Bin count must be positive');
-
- var curMaxBinBoundary = this.maxBinBoundary;
- if (curMaxBinBoundary <= 0)
+ }
+ if (this.range.max <= 0) {
throw new Error('Current max bin boundary must be positive');
- if (curMaxBinBoundary >= nextMaxBinBoundary) {
+ }
+ if (this.range.max >= nextMaxBinBoundary) {
throw new Error('The last added max boundary must be greater than ' +
'the current max boundary boundary');
}
- var binExponentWidth =
- Math.log(nextMaxBinBoundary / curMaxBinBoundary) / binCount;
- for (var i = 1; i < binCount; i++) {
- this.addBinBoundary(
- curMaxBinBoundary * Math.exp(i * binExponentWidth));
- }
- this.addBinBoundary(nextMaxBinBoundary);
+ // If boundaries_ had been built, then clear them.
nednguyen 2016/09/27 18:48:54 This seems like a big change to existing API of hi
benjhayden 2016/09/27 21:05:17 The existing boundaries are preserved in builder_.
+ this.boundaries_ = undefined;
+ this.builder_.push([HistogramBinBoundaries.SLICE_TYPE.EXPONENTIAL,
+ nextMaxBinBoundary, binCount]);
+ this.range.addValue(nextMaxBinBoundary);
nednguyen 2016/09/27 18:48:54 why not this.addBinBoundary(nextMaxBinBoundary) he
benjhayden 2016/09/27 21:05:17 That's not how it works. Callers call addBinBounda
return this;
}
}
+ HistogramBinBoundaries.SLICE_TYPE = {
+ LINEAR: 0,
+ EXPONENTIAL: 1,
+ };
+
DEFAULT_BOUNDARIES_FOR_UNIT.set(
tr.b.Unit.byName.timeDurationInMs.unitName,
HistogramBinBoundaries.createExponential(1e-3, 1e6, 1e2));

Powered by Google App Engine
This is Rietveld 408576698