1 <!DOCTYPE html>
2 <!--
3 Copyright 2016 The Chromium Authors. All rights reserved.
4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file.
6 -->
7
8 <link rel="import" href="/tracing/base/iteration_helpers.html">
9 <link rel="import" href="/tracing/base/range.html">
10 <link rel="import" href="/tracing/base/running_statistics.html">
11 <link rel="import" href="/tracing/base/sorted_array_utils.html">
12 <link rel="import" href="/tracing/base/statistics.html">
13 <link rel="import" href="/tracing/base/unit.html">
14 <link rel="import" href="/tracing/value/diagnostics/diagnostic_map.html">
15 <link rel="import" href="/tracing/value/numeric.html">
16
17 <script>
18 'use strict';
19
20 tr.exportTo('tr.v', function() {
21 var MAX_DIAGNOSTIC_MAPS = 16;
22
23 // p-values less than this indicate statistical significance.
24 var DEFAULT_ALPHA = 0.05;
25
26 /** @enum */
27 var Significance = {
28 DONT_CARE: -1,
29 INSIGNIFICANT: 0,
30 SIGNIFICANT: 1
31 };
32
33 var DEFAULT_BOUNDARIES_FOR_UNIT = new Map();
34
35 class HistogramBin {
36 /**
37 * @param {!tr.b.Range} range
38 */
39 constructor(range) {
40 this.range = range;
41 this.count = 0;
42 this.diagnosticMaps = [];
43 }
44
45 /**
46 * @param {*} value
47 */
48 addSample(value) {
49 this.count += 1;
50 }
51
52 /**
53 * @param {!tr.v.d.DiagnosticMap} diagnostics
54 */
55 addDiagnosticMap(diagnostics) {
56 tr.b.Statistics.uniformlySampleStream(
57 this.diagnosticMaps, this.count, diagnostics, MAX_DIAGNOSTIC_MAPS);
58 }
59
60 addBin(other) {
61 if (!this.range.equals(other.range))
62 throw new Error('Merging incompatible Histogram bins.');
63 tr.b.Statistics.mergeSampledStreams(this.diagnosticMaps, this.count,
64 other.diagnosticMaps, other.count, MAX_DIAGNOSTIC_MAPS);
65 this.count += other.count;
66 }
67
68 fromDict(d) {
69 this.count = d.count;
70 for (var map of d.diagnosticMaps)
71 this.diagnosticMaps.push(tr.v.d.DiagnosticMap.fromDict(map));
72 }
73
74 asDict() {
75 return {
76 count: this.count,
77 diagnosticMaps: this.diagnosticMaps.map(d => d.asDict())
78 };
79 }
80 }
81
82 /**
83 * This is basically a histogram, but so much more.
84 * Histogram is serializable using asDict/fromDict.
85 * Histogram computes several statistics of its contents.
86 * Histograms can be merged.
87 * getDifferenceSignificance() test whether one Histogram is statistically
88 * significantly different from another Histogram.
89 * Histogram stores a random sample of the exact number values added to it.
90 * Histogram stores a random sample of optional per-sample DiagnosticMaps.
91 * Histogram is visualized by <tr-v-ui-histogram-span>, which supports
92 * selecting bins, and visualizing the DiagnosticMaps of selected bins.
93 *
94 * @param {!tr.b.Unit} unit
95 * @param {!tr.v.HistogramBinBoundaries=} opt_binBoundaries
96 */
97 class Histogram {
98 constructor(name, unit, opt_binBoundaries) {
99
100 var binBoundaries = opt_binBoundaries;
101 if (!binBoundaries) {
102 var baseUnit = unit.baseUnit ? unit.baseUnit : unit;
103 binBoundaries = DEFAULT_BOUNDARIES_FOR_UNIT.get(baseUnit.unitName);
104 }
105
106 // If this Histogram is being deserialized, then its guid will be set by
107 // fromDict().
108 // If this Histogram is being computed by a metric, then its guid will be
109 // allocated the first time the guid is gotten by asDict().
110 this.guid_ = undefined;
111
112 this.allBins = [];
113 this.centralBins = [];
114 this.description = '';
115 this.diagnostics = new tr.v.d.DiagnosticMap();
116 this.maxCount_ = 0;
117 this.name_ = name;
118 this.nanDiagnosticMaps = [];
119 this.numNans = 0;
120 this.running = new tr.b.RunningStatistics();
121 this.sampleValues_ = [];
122 this.shortName = undefined;
123 this.summaryOptions = {
124 count: true,
125 sum: true,
126 avg: true,
127 geometricMean: false,
128 std: true,
129 min: true,
130 max: true,
131 nans: false,
132 percentile: []
133 };
134 this.unit = unit;
135
136 this.underflowBin = new HistogramBin(tr.b.Range.fromExplicitRange(
137 -Number.MAX_VALUE, binBoundaries.minBinBoundary));
138 this.overflowBin = new HistogramBin(tr.b.Range.fromExplicitRange(
139 binBoundaries.maxBinBoundary, Number.MAX_VALUE));
140
141 for (var range of binBoundaries)
142 this.centralBins.push(new HistogramBin(range));
143
144 this.allBins.push(this.underflowBin);
145 for (var bin of this.centralBins)
146 this.allBins.push(bin);
147 this.allBins.push(this.overflowBin);
148
149 this.maxNumSampleValues = this.allBins.length * 10;
150 }
151
152 get name() {
153 return this.name_;
154 }
155
156 get guid() {
157 if (this.guid_ === undefined)
158 this.guid_ = tr.b.GUID.allocateUUID4();
159
160 return this.guid_;
161 }
162
163 set guid(guid) {
164 if (this.guid_ !== undefined)
165 throw new Error('Cannot reset guid');
166
167 this.guid_ = guid;
168 }
169
170 static fromDict(d) {
171 var boundaries = HistogramBinBoundaries.createWithBoundaries(
172 d.binBoundaries);
173 var n = new Histogram(d.name, tr.b.Unit.fromJSON(d.unit), boundaries);
174 n.guid = d.guid;
175 n.shortName = d.shortName;
176 n.description = d.description;
177 n.diagnostics.addDicts(d.diagnostics);
178
179 n.underflowBin.fromDict(d.underflowBin);
180 for (var i = 0; i < d.centralBins.length; ++i)
181 n.centralBins[i].fromDict(d.centralBins[i]);
182 n.overflowBin.fromDict(d.overflowBin);
183
184 for (var bin of n.allBins)
185 n.maxCount_ = Math.max(n.maxCount_, bin.count);
186
187 if (d.running)
188 n.running = tr.b.RunningStatistics.fromDict(d.running);
189 if (d.summaryOptions)
190 n.customizeSummaryOptions(d.summaryOptions);
191
192 n.maxNumSampleValues = d.maxNumSampleValues;
193 n.sampleValues_ = d.sampleValues;
194
195 n.numNans = d.numNans;
196 for (var map of d.nanDiagnosticMaps)
197 n.nanDiagnosticMaps.push(tr.v.d.DiagnosticMap.fromDict(map));
198
199 return n;
200 }
201
202 /**
203 * Build a Histogram from a set of samples in order to effectively merge a
204 * set of ScalarNumerics.
205 * The range of the resulting histogram is determined by the smallest and
206 * largest sample value, which is unpredictable.
207 * https://github.com/catapult-project/catapult/issues/2685
208 *
209 * @param {!tr.b.Unit} unit
210 * @param {!Array.<number>} samples
211 * @return {!Histogram}
212 */
213 static buildFromSamples(unit, samples) {
214 var boundaries = HistogramBinBoundaries.createFromSamples(samples);
215 var result = new Histogram(unit, boundaries);
216 result.maxNumSampleValues = 1000;
217
218 // TODO(eakuefner): Propagate diagnosticMaps?
219 for (var sample of samples)
220 result.addSample(sample);
221
222 return result;
223 }
224
225 get numValues() {
226 return tr.b.Statistics.sum(this.allBins, function(e) {
227 return e.count;
228 });
229 }
230
231 get average() {
232 return this.running.mean;
233 }
234
235 get geometricMean() {
236 return this.running.geometricMean;
237 }
238
239 get sum() {
240 return this.running.sum;
241 }
242
243 get maxCount() {
244 return this.maxCount_;
245 }
246
247 /**
248 * Requires that units agree.
249 * Returns DONT_CARE if that is the units' improvementDirection.
250 * Returns SIGNIFICANT if the Mann-Whitney U test returns a
251 * p-value less than opt_alpha or DEFAULT_ALPHA. Returns INSIGNIFICANT if
252 * the p-value is greater than alpha.
253 *
254 * @param {!tr.v.Histogram} other
255 * @param {number=} opt_alpha
256 * @return {!tr.v.Significance}
257 */
258 getDifferenceSignificance(other, opt_alpha) {
259 if (this.unit !== other.unit)
260 throw new Error('Cannot compare Numerics with different units');
261
262 if (this.unit.improvementDirection ===
263 tr.b.ImprovementDirection.DONT_CARE) {
264 return tr.v.Significance.DONT_CARE;
265 }
266
267 if (!(other instanceof Histogram))
268 throw new Error('Unable to compute a p-value');
269
270 var mwu = tr.b.Statistics.mwu.test(this.sampleValues, other.sampleValues);
271 if (mwu.p < (opt_alpha || DEFAULT_ALPHA))
272 return tr.v.Significance.SIGNIFICANT;
273 return tr.v.Significance.INSIGNIFICANT;
274 }
275
276 /*
277 * Compute an approximation of percentile based on the counts in the bins.
278 * If the real percentile lies within |this.range| then the result of
279 * the function will deviate from the real percentile by at most
280 * the maximum width of the bin(s) within which the point(s)
281 * from which the real percentile would be calculated lie.
282 * If the real percentile is outside |this.range| then the function
283 * returns the closest range limit: |this.range.min| or |this.range.max|.
284 *
285 * @param {number} percent The percent must be between 0.0 and 1.0.
286 */
287 getApproximatePercentile(percent) {
288 if (!(percent >= 0 && percent <= 1))
289 throw new Error('percent must be [0,1]');
290 if (this.numValues == 0)
291 return 0;
292 var valuesToSkip = Math.floor((this.numValues - 1) * percent);
293 for (var i = 0; i < this.allBins.length; i++) {
294 var bin = this.allBins[i];
295 valuesToSkip -= bin.count;
296 if (valuesToSkip < 0) {
297 if (bin === this.underflowBin)
298 return bin.range.max;
299 else if (bin === this.overflowBin)
300 return bin.range.min;
301 else
302 return bin.range.center;
303 }
304 }
305 throw new Error('Unreachable');
306 }
307
308 getBinForValue(value) {
309 // Don't use subtraction to avoid arithmetic overflow.
310 var binIndex = tr.b.findHighIndexInSortedArray(
311 this.allBins, b => value < b.range.max ? -1 : 1);
312 return this.allBins[binIndex] || this.overflowBin;
313 }
314
315 /**
316 * @param {number|*} value
317 * @param {(!Object|!tr.v.d.DiagnosticMap)=} opt_diagnostics
318 */
319 addSample(value, opt_diagnostics) {
320 if (opt_diagnostics &&
321 !(opt_diagnostics instanceof tr.v.d.DiagnosticMap))
322 opt_diagnostics = tr.v.d.DiagnosticMap.fromObject(opt_diagnostics);
323
324 if (typeof(value) !== 'number' || isNaN(value)) {
325 this.numNans++;
326 if (opt_diagnostics) {
327 tr.b.Statistics.uniformlySampleStream(this.nanDiagnosticMaps,
328 this.numNans, opt_diagnostics, MAX_DIAGNOSTIC_MAPS);
329 }
330 } else {
331 this.running.add(value);
332
333 var bin = this.getBinForValue(value);
334 bin.addSample(value);
335 if (opt_diagnostics)
336 bin.addDiagnosticMap(opt_diagnostics);
337 if (bin.count > this.maxCount_)
338 this.maxCount_ = bin.count;
339 }
340
341 tr.b.Statistics.uniformlySampleStream(this.sampleValues_,
342 this.numValues + this.numNans, value, this.maxNumSampleValues);
343 }
344
345 sampleValuesInto(samples) {
346 for (var sampleValue of this.sampleValues)
347 samples.push(sampleValue);
348 }
349
350 /**
351 * Return true if this Histogram can be added to |other|.
352 *
353 * @param {!tr.v.Histogram} other
354 * @return {boolean}
355 */
356 canAddHistogram(other) {
357 if (this.unit !== other.unit)
358 return false;
359 if (this.allBins.length !== other.allBins.length)
360 return false;
361
362 for (var i = 0; i < this.allBins.length; ++i)
363 if (!this.allBins[i].range.equals(other.allBins[i].range))
364 return false;
365
366 return true;
367 }
368
369 /**
370 * Add |other| to this Histogram in-place if they can be added.
371 *
372 * @param {!tr.v.Histogram} other
373 */
374 addHistogram(other) {
375 if (!this.canAddHistogram(other))
376 throw new Error('Merging incompatible Numerics.');
377
378 tr.b.Statistics.mergeSampledStreams(this.nanDiagnosticMaps, this.numNans,
379 other.nanDiagnosticMaps, other.numNans, MAX_DIAGNOSTIC_MAPS);
380 tr.b.Statistics.mergeSampledStreams(
381 this.sampleValues, this.numValues,
382 other.sampleValues, other.numValues, tr.b.Statistics.mean(
383 [this.maxNumSampleValues, other.maxNumSampleValues]));
384 this.numNans += other.numNans;
385 this.running = this.running.merge(other.running);
386 for (var i = 0; i < this.allBins.length; ++i) {
387 this.allBins[i].addBin(other.allBins[i]);
388 }
389 }
390
391 /**
392 * Controls which statistics are exported to dashboard for this numeric.
393 * The |summaryOptions| parameter is a dictionary with optional boolean
394 * fields |count|, |sum|, |avg|, |std|, |min|, |max| and an optional
395 * array field |percentile|.
396 * Each percentile should be a number between 0.0 and 1.0.
397 * The options not included in the |summaryOptions| will not change.
398 */
399 customizeSummaryOptions(summaryOptions) {
400 tr.b.iterItems(summaryOptions, function(key, value) {
401 this.summaryOptions[key] = value;
402 }, this);
403 }
404
405 /**
406 * Returns a Map {statisticName: ScalarNumeric}.
407 *
408 * Each enabled summary option produces the corresponding value:
409 * min, max, count, sum, avg, or std.
410 * Each percentile 0.x produces pct_0x0.
411 * Each percentile 0.xx produces pct_0xx.
412 * Each percentile 0.xxy produces pct_0xx_y.
413 * Percentile 1.0 produces pct_100.
414 *
415 * @return {!Map.}
416 */
417 get statisticsScalars() {
418 function statNameToKey(stat) {
419 switch (stat) {
420 case 'std':
421 return 'stddev';
422 case 'avg':
423 return 'mean';
424 }
425 return stat;
426 }
427 /**
428 * Converts the given percent to a string in the format specified above.
429 * @param {number} percent The percent must be between 0.0 and 1.0.
430 */
431 function percentToString(percent) {
432 if (percent < 0 || percent > 1)
433 throw new Error('Percent must be between 0.0 and 1.0');
434 switch (percent) {
435 case 0:
436 return '000';
437 case 1:
438 return '100';
439 }
440 var str = percent.toString();
441 if (str[1] !== '.')
442 throw new Error('Unexpected percent');
443 // Pad short strings with zeros.
444 str = str + '0'.repeat(Math.max(4 - str.length, 0));
445 if (str.length > 4)
446 str = str.slice(0, 4) + '_' + str.slice(4);
447 return '0' + str.slice(2);
448 }
449
450 var results = new Map();
451 tr.b.iterItems(this.summaryOptions, function(stat, option) {
452 if (!option)
453 return;
454
455 if (stat === 'percentile') {
456 option.forEach(function(percent) {
457 var percentile = this.getApproximatePercentile(percent);
458 results.set('pct_' + percentToString(percent),
459 new tr.v.ScalarNumeric(this.unit, percentile));
460 }, this);
461 } else if (stat === 'nans') {
462 results.set('nans', new tr.v.ScalarNumeric(
463 tr.b.Unit.byName.count_smallerIsBetter, this.numNans));
464 } else {
465 var statUnit = stat === 'count' ?
466 tr.b.Unit.byName.count_smallerIsBetter : this.unit;
467 var key = statNameToKey(stat);
468 var statValue = this.running[key];
469
470 if (typeof(statValue) === 'number') {
471 results.set(stat, new tr.v.ScalarNumeric(statUnit, statValue));
472 }
473 }
474 }, this);
475 return results;
476 }
477
478 get sampleValues() {
479 return this.sampleValues_;
480 }
481
482 get binBoundaries() {
483 var boundaries = [];
484 for (var bin of this.centralBins)
485 boundaries.push(bin.range.min);
486 boundaries.push(this.overflowBin.range.min);
487 return boundaries;
488 }
489
490 clone() {
491 return Histogram.fromDict(this.asDict());
492 }
493
494 asDict() {
495 return {
496 name: this.name,
497 guid: this.guid,
498 shortName: this.shortName,
499 description: this.description,
500 diagnostics: this.diagnostics.asDict(),
501 unit: this.unit.asJSON(),
502 binBoundaries: this.binBoundaries,
503
504 underflowBin: this.underflowBin.asDict(),
505 centralBins: this.centralBins.map(bin => bin.asDict()),
506 overflowBin: this.overflowBin.asDict(),
507
508 running: this.running.asDict(),
509 summaryOptions: this.summaryOptions,
510
511 maxNumSampleValues: this.maxNumSampleValues,
512 sampleValues: this.sampleValues,
513
514 numNans: this.numNans,
515 nanDiagnosticMaps: this.nanDiagnosticMaps.map(dm => dm.asDict()),
516 };
517 }
518 }
519
520 /**
521 * Reusable builder for tr.v.Histogram objects.
522 *
523 * The bins of the numeric are specified by adding the desired boundaries
524 * between bins. Initially, the builder has only a single boundary:
525 *
526 * minBinBoundary=maxBinBoundary
527 * |
528 * |
529 * -MAX_INT <--------|------------------------------------------> +MAX_INT
530 * : resulting : resulting :
531 * : underflow : overflow :
532 * : bin : bin :
533 *
534 * More boundaries can be added (in increasing order) using addBinBoundary,
535 * addLinearBins and addExponentialBins:
536 *
537 * minBinBoundary maxBinBoundary
538 * | | | | |
539 * | | | | |
540 * -MAX_INT <--------|---------|---------|-----|---------|------> +MAX_INT
541 * : resulting : result. : result. : : result. : resulting :
542 * : underflow : central : central : ... : central : overflow :
543 * : bin : bin 0 : bin 1 : : bin N-1 : bin :
544 *
545 * An important feature of the builder is that it's reusable, i.e. it can be
546 * used to build multiple numerics with the same unit and bin structure.
547 *
548 * @constructor
549 * @param {!tr.b.Unit} unit Unit of the resulting Histogram(s).
550 * @param {number} minBinBoundary The minimum boundary between bins, namely
551 * the underflow bin and the first central bin (or the overflow bin if
552 * no other boundaries are added later).
553 */
554 class HistogramBinBoundaries {
555 /**
556 * Create a linearly scaled tr.v.HistogramBinBoundaries with |numBins| bins
557 * ranging from |min| to |max|.
558 *
559 * @param {number} min
560 * @param {number} max
561 * @param {number} numBins
562 * @return {tr.v.HistogramBinBoundaries}
563 */
564 static createLinear(min, max, numBins) {
565 return new HistogramBinBoundaries(min).addLinearBins(max, numBins);
566 }
567
568 /**
569 * Create an exponentially scaled tr.v.HistogramBinBoundaries with |numBins|
570 * bins ranging from |min| to |max|.
571 *
572 * @param {number} min
573 * @param {number} max
574 * @param {number} numBins
575 * @return {tr.v.HistogramBinBoundaries}
576 */
577 static createExponential(min, max, numBins) {
578 return new HistogramBinBoundaries(min).addExponentialBins(max, numBins);
579 }
580
581 /**
582 * @param {Array.<number>} binBoundaries
583 */
584 static createWithBoundaries(binBoundaries) {
585 var builder = new HistogramBinBoundaries(binBoundaries[0]);
586 for (var boundary of binBoundaries.slice(1))
587 builder.addBinBoundary(boundary);
588 return builder;
589 }
590
591 static createFromSamples(samples) {
592 var range = new tr.b.Range();
593 // Prevent non-numeric samples from introducing NaNs into the range.
594 for (var sample of samples)
595 if (!isNaN(Math.max(sample)))
596 range.addValue(sample);
597
598 // HistogramBinBoundaries.addLinearBins() requires this.
599 if (range.isEmpty)
600 range.addValue(1);
601 if (range.min === range.max)
602 range.addValue(range.min - 1);
603
604 // This optimizes the resolution when samples are uniformly distributed
605 // (which is almost never the case).
606 var numBins = Math.ceil(Math.sqrt(samples.length));
607 var builder = new HistogramBinBoundaries(range.min);
608 builder.addLinearBins(range.max, numBins);
609 return builder;
610 }
611
612 /**
613 * @param {number} minBinBoundary
614 */
615 constructor(minBinBoundary) {
616 this.boundaries_ = [minBinBoundary];
617 }
618
619 get minBinBoundary() {
620 return this.boundaries_[0];
621 }
622
623 get maxBinBoundary() {
624 return this.boundaries_[this.boundaries_.length - 1];
625 }
626
627 /**
628 * Yield Ranges of adjacent boundaries.
629 */
630 *[Symbol.iterator]() {
631 for (var i = 0; i < this.boundaries_.length - 1; ++i) {
632 yield tr.b.Range.fromExplicitRange(
633 this.boundaries_[i], this.boundaries_[i + 1]);
634 }
635 }
636
637 /**
638 * Add a bin boundary |nextMaxBinBoundary| to the builder.
639 *
640 * This operation effectively corresponds to appending a new central bin
641 * with the range [this.maxBinBoundary*, nextMaxBinBoundary].
642 *
643 * @param {number} nextMaxBinBoundary The added bin boundary (must be
644 * greater than |this.maxMinBoundary|).
645 */
646 addBinBoundary(nextMaxBinBoundary) {
647 if (nextMaxBinBoundary <= this.maxBinBoundary) {
648 throw new Error('The added max bin boundary must be larger than ' +
649 'the current max boundary');
650 }
651 this.boundaries_.push(nextMaxBinBoundary);
652
653 return this;
654 }
655
656 /**
657 * Add |binCount| linearly scaled bin boundaries up to |nextMaxBinBoundary|
658 * to the builder.
659 *
660 * This operation corresponds to appending |binCount| central bins of
661 * constant range width
662 * W = ((|nextMaxBinBoundary| - |this.maxBinBoundary|) / |binCount|)
663 * with the following ranges:
664 *
665 * [|this.maxMinBoundary|, |this.maxMinBoundary| + W]
666 * [|this.maxMinBoundary| + W, |this.maxMinBoundary| + 2W]
667 * [|this.maxMinBoundary| + 2W, |this.maxMinBoundary| + 3W]
668 * ...
669 * [|this.maxMinBoundary| + (|binCount| - 2) * W,
670 * |this.maxMinBoundary| + (|binCount| - 2) * W]
671 * [|this.maxMinBoundary| + (|binCount| - 1) * W,
672 * |nextMaxBinBoundary|]
673 *
674 * @param {number} nextBinBoundary The last added bin boundary (must be
675 * greater than |this.maxMinBoundary|).
676 * @param {number} binCount Number of bins to be added (must be positive).
677 */
678 addLinearBins(nextMaxBinBoundary, binCount) {
679 if (binCount <= 0)
680 throw new Error('Bin count must be positive');
681
682 var curMaxBinBoundary = this.maxBinBoundary;
683 if (curMaxBinBoundary >= nextMaxBinBoundary) {
684 throw new Error('The new max bin boundary must be greater than ' +
685 'the previous max bin boundary');
686 }
687
688 var binWidth = (nextMaxBinBoundary - curMaxBinBoundary) / binCount;
689 for (var i = 1; i < binCount; i++)
690 this.addBinBoundary(curMaxBinBoundary + i * binWidth);
691 this.addBinBoundary(nextMaxBinBoundary);
692
693 return this;
694 }
695
696 /**
697 * Add |binCount| exponentially scaled bin boundaries up to
698 * |nextMaxBinBoundary| to the builder.
699 *
700 * This operation corresponds to appending |binCount| central bins with
701 * a constant difference between the logarithms of their range min and max
702 * D = ((ln(|nextMaxBinBoundary|) - ln(|this.maxBinBoundary|)) / |binCount|)
703 * with the following ranges:
704 *
705 * [|this.maxMinBoundary|, |this.maxMinBoundary| * exp(D)]
706 * [|this.maxMinBoundary| * exp(D), |this.maxMinBoundary| * exp(2D)]
707 * [|this.maxMinBoundary| * exp(2D), |this.maxMinBoundary| * exp(3D)]
708 * ...
709 * [|this.maxMinBoundary| * exp((|binCount| - 2) * D),
710 * |this.maxMinBoundary| * exp((|binCount| - 2) * D)]
711 * [|this.maxMinBoundary| * exp((|binCount| - 1) * D),
712 * |nextMaxBinBoundary|]
713 *
714 * This method requires that the current max bin boundary is positive.
715 *
716 * @param {number} nextBinBoundary The last added bin boundary (must be
717 * greater than |this.maxMinBoundary|).
718 * @param {number} binCount Number of bins to be added (must be positive).
719 */
720 addExponentialBins(nextMaxBinBoundary, binCount) {
721 if (binCount <= 0)
722 throw new Error('Bin count must be positive');
723
724 var curMaxBinBoundary = this.maxBinBoundary;
725 if (curMaxBinBoundary <= 0)
726 throw new Error('Current max bin boundary must be positive');
727 if (curMaxBinBoundary >= nextMaxBinBoundary) {
728 throw new Error('The last added max boundary must be greater than ' +
729 'the current max boundary boundary');
730 }
731
732 var binExponentWidth =
733 Math.log(nextMaxBinBoundary / curMaxBinBoundary) / binCount;
734 for (var i = 1; i < binCount; i++) {
735 this.addBinBoundary(
736 curMaxBinBoundary * Math.exp(i * binExponentWidth));
737 }
738 this.addBinBoundary(nextMaxBinBoundary);
739
740 return this;
741 }
742 }
743
744 DEFAULT_BOUNDARIES_FOR_UNIT.set(
745 tr.b.Unit.byName.timeDurationInMs.unitName,
746 HistogramBinBoundaries.createExponential(1e-3, 1e6, 1e2));
747
748 DEFAULT_BOUNDARIES_FOR_UNIT.set(
749 tr.b.Unit.byName.timeStampInMs.unitName,
750 HistogramBinBoundaries.createLinear(0, 1e10, 1e3));
751
752 DEFAULT_BOUNDARIES_FOR_UNIT.set(
753 tr.b.Unit.byName.normalizedPercentage.unitName,
754 HistogramBinBoundaries.createLinear(0, 1.0, 20));
755
756 DEFAULT_BOUNDARIES_FOR_UNIT.set(
757 tr.b.Unit.byName.sizeInBytes.unitName,
758 HistogramBinBoundaries.createExponential(1, 1e12, 1e2));
759
760 DEFAULT_BOUNDARIES_FOR_UNIT.set(
761 tr.b.Unit.byName.energyInJoules.unitName,
762 HistogramBinBoundaries.createExponential(1e-3, 1e3, 50));
763
764 DEFAULT_BOUNDARIES_FOR_UNIT.set(
765 tr.b.Unit.byName.powerInWatts.unitName,
766 HistogramBinBoundaries.createExponential(1e-3, 1, 50));
767
768 DEFAULT_BOUNDARIES_FOR_UNIT.set(
769 tr.b.Unit.byName.unitlessNumber.unitName,
770 HistogramBinBoundaries.createExponential(1e-3, 1e3, 50));
771
772 DEFAULT_BOUNDARIES_FOR_UNIT.set(
773 tr.b.Unit.byName.count.unitName,
774 HistogramBinBoundaries.createExponential(1, 1e3, 20));
775
776 return {
777 Significance: Significance,
778 Histogram: Histogram,
779 HistogramBinBoundaries: HistogramBinBoundaries,
780 };
781 });
782 </script>
783