1<!DOCTYPE html> 2<!-- 3Copyright 2016 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found 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 20tr.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.<string, ScalarNumeric>} 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