Chromium Code Reviews| 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)); |