[6a3a178] | 1 | "use strict";
|
---|
| 2 | Object.defineProperty(exports, "__esModule", { value: true });
|
---|
| 3 | exports.default = exports.JsHistogram = void 0;
|
---|
| 4 | /*
|
---|
| 5 | * This is a TypeScript port of the original Java version, which was written by
|
---|
| 6 | * Gil Tene as described in
|
---|
| 7 | * https://github.com/HdrHistogram/HdrHistogram
|
---|
| 8 | * and released to the public domain, as explained at
|
---|
| 9 | * http://creativecommons.org/publicdomain/zero/1.0/
|
---|
| 10 | */
|
---|
| 11 | const RecordedValuesIterator_1 = require("./RecordedValuesIterator");
|
---|
| 12 | const PercentileIterator_1 = require("./PercentileIterator");
|
---|
| 13 | const formatters_1 = require("./formatters");
|
---|
| 14 | const ulp_1 = require("./ulp");
|
---|
| 15 | const Histogram_1 = require("./Histogram");
|
---|
| 16 | const { pow, floor, ceil, log2, max, min } = Math;
|
---|
| 17 | class JsHistogram {
|
---|
| 18 | constructor(lowestDiscernibleValue, highestTrackableValue, numberOfSignificantValueDigits) {
|
---|
| 19 | this.autoResize = false;
|
---|
| 20 | this.startTimeStampMsec = Number.MAX_SAFE_INTEGER;
|
---|
| 21 | this.endTimeStampMsec = 0;
|
---|
| 22 | this.tag = Histogram_1.NO_TAG;
|
---|
| 23 | this.maxValue = 0;
|
---|
| 24 | this.minNonZeroValue = Number.MAX_SAFE_INTEGER;
|
---|
| 25 | this.identity = 0;
|
---|
| 26 | this.highestTrackableValue = 0;
|
---|
| 27 | this.lowestDiscernibleValue = 0;
|
---|
| 28 | this.numberOfSignificantValueDigits = 0;
|
---|
| 29 | this.bucketCount = 0;
|
---|
| 30 | this.subBucketCount = 0;
|
---|
| 31 | this.countsArrayLength = 0;
|
---|
| 32 | this.wordSizeInBytes = 0;
|
---|
| 33 | // Verify argument validity
|
---|
| 34 | if (lowestDiscernibleValue < 1) {
|
---|
| 35 | throw new Error("lowestDiscernibleValue must be >= 1");
|
---|
| 36 | }
|
---|
| 37 | if (highestTrackableValue < 2 * lowestDiscernibleValue) {
|
---|
| 38 | throw new Error(`highestTrackableValue must be >= 2 * lowestDiscernibleValue ( 2 * ${lowestDiscernibleValue} )`);
|
---|
| 39 | }
|
---|
| 40 | if (numberOfSignificantValueDigits < 0 ||
|
---|
| 41 | numberOfSignificantValueDigits > 5) {
|
---|
| 42 | throw new Error("numberOfSignificantValueDigits must be between 0 and 5");
|
---|
| 43 | }
|
---|
| 44 | this.identity = JsHistogram.identityBuilder++;
|
---|
| 45 | this.init(lowestDiscernibleValue, highestTrackableValue, numberOfSignificantValueDigits);
|
---|
| 46 | }
|
---|
| 47 | incrementTotalCount() {
|
---|
| 48 | this._totalCount++;
|
---|
| 49 | }
|
---|
| 50 | addToTotalCount(value) {
|
---|
| 51 | this._totalCount += value;
|
---|
| 52 | }
|
---|
| 53 | setTotalCount(value) {
|
---|
| 54 | this._totalCount = value;
|
---|
| 55 | }
|
---|
| 56 | /**
|
---|
| 57 | * Get the total count of all recorded values in the histogram
|
---|
| 58 | * @return the total count of all recorded values in the histogram
|
---|
| 59 | */
|
---|
| 60 | get totalCount() {
|
---|
| 61 | return this._totalCount;
|
---|
| 62 | }
|
---|
| 63 | updatedMaxValue(value) {
|
---|
| 64 | const internalValue = value + this.unitMagnitudeMask;
|
---|
| 65 | this.maxValue = internalValue;
|
---|
| 66 | }
|
---|
| 67 | updateMinNonZeroValue(value) {
|
---|
| 68 | if (value <= this.unitMagnitudeMask) {
|
---|
| 69 | return;
|
---|
| 70 | }
|
---|
| 71 | const internalValue = floor(value / this.lowestDiscernibleValueRounded) *
|
---|
| 72 | this.lowestDiscernibleValueRounded;
|
---|
| 73 | this.minNonZeroValue = internalValue;
|
---|
| 74 | }
|
---|
| 75 | init(lowestDiscernibleValue, highestTrackableValue, numberOfSignificantValueDigits) {
|
---|
| 76 | this.lowestDiscernibleValue = lowestDiscernibleValue;
|
---|
| 77 | this.highestTrackableValue = highestTrackableValue;
|
---|
| 78 | this.numberOfSignificantValueDigits = numberOfSignificantValueDigits;
|
---|
| 79 | /*
|
---|
| 80 | * Given a 3 decimal point accuracy, the expectation is obviously for "+/- 1 unit at 1000". It also means that
|
---|
| 81 | * it's "ok to be +/- 2 units at 2000". The "tricky" thing is that it is NOT ok to be +/- 2 units at 1999. Only
|
---|
| 82 | * starting at 2000. So internally, we need to maintain single unit resolution to 2x 10^decimalPoints.
|
---|
| 83 | */
|
---|
| 84 | const largestValueWithSingleUnitResolution = 2 * floor(pow(10, numberOfSignificantValueDigits));
|
---|
| 85 | this.unitMagnitude = floor(log2(lowestDiscernibleValue));
|
---|
| 86 | this.lowestDiscernibleValueRounded = pow(2, this.unitMagnitude);
|
---|
| 87 | this.unitMagnitudeMask = this.lowestDiscernibleValueRounded - 1;
|
---|
| 88 | // We need to maintain power-of-two subBucketCount (for clean direct indexing) that is large enough to
|
---|
| 89 | // provide unit resolution to at least largestValueWithSingleUnitResolution. So figure out
|
---|
| 90 | // largestValueWithSingleUnitResolution's nearest power-of-two (rounded up), and use that:
|
---|
| 91 | const subBucketCountMagnitude = ceil(log2(largestValueWithSingleUnitResolution));
|
---|
| 92 | this.subBucketHalfCountMagnitude =
|
---|
| 93 | (subBucketCountMagnitude > 1 ? subBucketCountMagnitude : 1) - 1;
|
---|
| 94 | this.subBucketCount = pow(2, this.subBucketHalfCountMagnitude + 1);
|
---|
| 95 | this.subBucketHalfCount = this.subBucketCount / 2;
|
---|
| 96 | this.subBucketMask =
|
---|
| 97 | (floor(this.subBucketCount) - 1) * pow(2, this.unitMagnitude);
|
---|
| 98 | this.establishSize(highestTrackableValue);
|
---|
| 99 | this.leadingZeroCountBase =
|
---|
| 100 | 53 - this.unitMagnitude - this.subBucketHalfCountMagnitude - 1;
|
---|
| 101 | this.percentileIterator = new PercentileIterator_1.default(this, 1);
|
---|
| 102 | this.recordedValuesIterator = new RecordedValuesIterator_1.default(this);
|
---|
| 103 | }
|
---|
| 104 | /**
|
---|
| 105 | * The buckets (each of which has subBucketCount sub-buckets, here assumed to be 2048 as an example) overlap:
|
---|
| 106 | *
|
---|
| 107 | * <pre>
|
---|
| 108 | * The 0'th bucket covers from 0...2047 in multiples of 1, using all 2048 sub-buckets
|
---|
| 109 | * The 1'th bucket covers from 2048..4097 in multiples of 2, using only the top 1024 sub-buckets
|
---|
| 110 | * The 2'th bucket covers from 4096..8191 in multiple of 4, using only the top 1024 sub-buckets
|
---|
| 111 | * ...
|
---|
| 112 | * </pre>
|
---|
| 113 | *
|
---|
| 114 | * Bucket 0 is "special" here. It is the only one that has 2048 entries. All the rest have 1024 entries (because
|
---|
| 115 | * their bottom half overlaps with and is already covered by the all of the previous buckets put together). In other
|
---|
| 116 | * words, the k'th bucket could represent 0 * 2^k to 2048 * 2^k in 2048 buckets with 2^k precision, but the midpoint
|
---|
| 117 | * of 1024 * 2^k = 2048 * 2^(k-1) = the k-1'th bucket's end, so we would use the previous bucket for those lower
|
---|
| 118 | * values as it has better precision.
|
---|
| 119 | */
|
---|
| 120 | establishSize(newHighestTrackableValue) {
|
---|
| 121 | // establish counts array length:
|
---|
| 122 | this.countsArrayLength = this.determineArrayLengthNeeded(newHighestTrackableValue);
|
---|
| 123 | // establish exponent range needed to support the trackable value with no overflow:
|
---|
| 124 | this.bucketCount = this.getBucketsNeededToCoverValue(newHighestTrackableValue);
|
---|
| 125 | // establish the new highest trackable value:
|
---|
| 126 | this.highestTrackableValue = newHighestTrackableValue;
|
---|
| 127 | }
|
---|
| 128 | determineArrayLengthNeeded(highestTrackableValue) {
|
---|
| 129 | if (highestTrackableValue < 2 * this.lowestDiscernibleValue) {
|
---|
| 130 | throw new Error("highestTrackableValue (" +
|
---|
| 131 | highestTrackableValue +
|
---|
| 132 | ") cannot be < (2 * lowestDiscernibleValue)");
|
---|
| 133 | }
|
---|
| 134 | //determine counts array length needed:
|
---|
| 135 | const countsArrayLength = this.getLengthForNumberOfBuckets(this.getBucketsNeededToCoverValue(highestTrackableValue));
|
---|
| 136 | return countsArrayLength;
|
---|
| 137 | }
|
---|
| 138 | /**
|
---|
| 139 | * If we have N such that subBucketCount * 2^N > max value, we need storage for N+1 buckets, each with enough
|
---|
| 140 | * slots to hold the top half of the subBucketCount (the lower half is covered by previous buckets), and the +1
|
---|
| 141 | * being used for the lower half of the 0'th bucket. Or, equivalently, we need 1 more bucket to capture the max
|
---|
| 142 | * value if we consider the sub-bucket length to be halved.
|
---|
| 143 | */
|
---|
| 144 | getLengthForNumberOfBuckets(numberOfBuckets) {
|
---|
| 145 | const lengthNeeded = (numberOfBuckets + 1) * (this.subBucketCount / 2);
|
---|
| 146 | return lengthNeeded;
|
---|
| 147 | }
|
---|
| 148 | getBucketsNeededToCoverValue(value) {
|
---|
| 149 | // the k'th bucket can express from 0 * 2^k to subBucketCount * 2^k in units of 2^k
|
---|
| 150 | let smallestUntrackableValue = this.subBucketCount * pow(2, this.unitMagnitude);
|
---|
| 151 | // always have at least 1 bucket
|
---|
| 152 | let bucketsNeeded = 1;
|
---|
| 153 | while (smallestUntrackableValue <= value) {
|
---|
| 154 | if (smallestUntrackableValue > Number.MAX_SAFE_INTEGER / 2) {
|
---|
| 155 | // TODO check array max size in JavaScript
|
---|
| 156 | // next shift will overflow, meaning that bucket could represent values up to ones greater than
|
---|
| 157 | // Number.MAX_SAFE_INTEGER, so it's the last bucket
|
---|
| 158 | return bucketsNeeded + 1;
|
---|
| 159 | }
|
---|
| 160 | smallestUntrackableValue = smallestUntrackableValue * 2;
|
---|
| 161 | bucketsNeeded++;
|
---|
| 162 | }
|
---|
| 163 | return bucketsNeeded;
|
---|
| 164 | }
|
---|
| 165 | /**
|
---|
| 166 | * Record a value in the histogram
|
---|
| 167 | *
|
---|
| 168 | * @param value The value to be recorded
|
---|
| 169 | * @throws may throw Error if value is exceeds highestTrackableValue
|
---|
| 170 | */
|
---|
| 171 | recordValue(value) {
|
---|
| 172 | this.recordSingleValue(value);
|
---|
| 173 | }
|
---|
| 174 | recordSingleValue(value) {
|
---|
| 175 | const countsIndex = this.countsArrayIndex(value);
|
---|
| 176 | if (countsIndex >= this.countsArrayLength) {
|
---|
| 177 | this.handleRecordException(1, value);
|
---|
| 178 | }
|
---|
| 179 | else {
|
---|
| 180 | this.incrementCountAtIndex(countsIndex);
|
---|
| 181 | }
|
---|
| 182 | this.updateMinAndMax(value);
|
---|
| 183 | this.incrementTotalCount();
|
---|
| 184 | }
|
---|
| 185 | handleRecordException(count, value) {
|
---|
| 186 | if (!this.autoResize) {
|
---|
| 187 | throw new Error("Value " + value + " is outside of histogram covered range");
|
---|
| 188 | }
|
---|
| 189 | this.resize(value);
|
---|
| 190 | var countsIndex = this.countsArrayIndex(value);
|
---|
| 191 | this.addToCountAtIndex(countsIndex, count);
|
---|
| 192 | this.highestTrackableValue = this.highestEquivalentValue(this.valueFromIndex(this.countsArrayLength - 1));
|
---|
| 193 | }
|
---|
| 194 | countsArrayIndex(value) {
|
---|
| 195 | if (value < 0) {
|
---|
| 196 | throw new Error("Histogram recorded value cannot be negative.");
|
---|
| 197 | }
|
---|
| 198 | const bucketIndex = this.getBucketIndex(value);
|
---|
| 199 | const subBucketIndex = this.getSubBucketIndex(value, bucketIndex);
|
---|
| 200 | return this.computeCountsArrayIndex(bucketIndex, subBucketIndex);
|
---|
| 201 | }
|
---|
| 202 | computeCountsArrayIndex(bucketIndex, subBucketIndex) {
|
---|
| 203 | // TODO
|
---|
| 204 | //assert(subBucketIndex < subBucketCount);
|
---|
| 205 | //assert(bucketIndex == 0 || (subBucketIndex >= subBucketHalfCount));
|
---|
| 206 | // Calculate the index for the first entry that will be used in the bucket (halfway through subBucketCount).
|
---|
| 207 | // For bucketIndex 0, all subBucketCount entries may be used, but bucketBaseIndex is still set in the middle.
|
---|
| 208 | const bucketBaseIndex = (bucketIndex + 1) * pow(2, this.subBucketHalfCountMagnitude);
|
---|
| 209 | // Calculate the offset in the bucket. This subtraction will result in a positive value in all buckets except
|
---|
| 210 | // the 0th bucket (since a value in that bucket may be less than half the bucket's 0 to subBucketCount range).
|
---|
| 211 | // However, this works out since we give bucket 0 twice as much space.
|
---|
| 212 | const offsetInBucket = subBucketIndex - this.subBucketHalfCount;
|
---|
| 213 | // The following is the equivalent of ((subBucketIndex - subBucketHalfCount) + bucketBaseIndex;
|
---|
| 214 | return bucketBaseIndex + offsetInBucket;
|
---|
| 215 | }
|
---|
| 216 | /**
|
---|
| 217 | * @return the lowest (and therefore highest precision) bucket index that can represent the value
|
---|
| 218 | */
|
---|
| 219 | getBucketIndex(value) {
|
---|
| 220 | // Calculates the number of powers of two by which the value is greater than the biggest value that fits in
|
---|
| 221 | // bucket 0. This is the bucket index since each successive bucket can hold a value 2x greater.
|
---|
| 222 | // The mask maps small values to bucket 0.
|
---|
| 223 | // return this.leadingZeroCountBase - Long.numberOfLeadingZeros(value | subBucketMask);
|
---|
| 224 | return max(floor(log2(value)) -
|
---|
| 225 | this.subBucketHalfCountMagnitude -
|
---|
| 226 | this.unitMagnitude, 0);
|
---|
| 227 | }
|
---|
| 228 | getSubBucketIndex(value, bucketIndex) {
|
---|
| 229 | // For bucketIndex 0, this is just value, so it may be anywhere in 0 to subBucketCount.
|
---|
| 230 | // For other bucketIndex, this will always end up in the top half of subBucketCount: assume that for some bucket
|
---|
| 231 | // k > 0, this calculation will yield a value in the bottom half of 0 to subBucketCount. Then, because of how
|
---|
| 232 | // buckets overlap, it would have also been in the top half of bucket k-1, and therefore would have
|
---|
| 233 | // returned k-1 in getBucketIndex(). Since we would then shift it one fewer bits here, it would be twice as big,
|
---|
| 234 | // and therefore in the top half of subBucketCount.
|
---|
| 235 | return floor(value / pow(2, bucketIndex + this.unitMagnitude));
|
---|
| 236 | }
|
---|
| 237 | updateMinAndMax(value) {
|
---|
| 238 | if (value > this.maxValue) {
|
---|
| 239 | this.updatedMaxValue(value);
|
---|
| 240 | }
|
---|
| 241 | if (value < this.minNonZeroValue && value !== 0) {
|
---|
| 242 | this.updateMinNonZeroValue(value);
|
---|
| 243 | }
|
---|
| 244 | }
|
---|
| 245 | /**
|
---|
| 246 | * Get the value at a given percentile.
|
---|
| 247 | * When the given percentile is > 0.0, the value returned is the value that the given
|
---|
| 248 | * percentage of the overall recorded value entries in the histogram are either smaller than
|
---|
| 249 | * or equivalent to. When the given percentile is 0.0, the value returned is the value that all value
|
---|
| 250 | * entries in the histogram are either larger than or equivalent to.
|
---|
| 251 | * <p>
|
---|
| 252 | * Note that two values are "equivalent" in this statement if
|
---|
| 253 | * {@link org.HdrHistogram.JsHistogram#valuesAreEquivalent} would return true.
|
---|
| 254 | *
|
---|
| 255 | * @param percentile The percentile for which to return the associated value
|
---|
| 256 | * @return The value that the given percentage of the overall recorded value entries in the
|
---|
| 257 | * histogram are either smaller than or equivalent to. When the percentile is 0.0, returns the
|
---|
| 258 | * value that all value entries in the histogram are either larger than or equivalent to.
|
---|
| 259 | */
|
---|
| 260 | getValueAtPercentile(percentile) {
|
---|
| 261 | const requestedPercentile = min(percentile, 100); // Truncate down to 100%
|
---|
| 262 | // round count up to nearest integer, to ensure that the largest value that the requested percentile
|
---|
| 263 | // of overall recorded values is actually included. However, this must be done with care:
|
---|
| 264 | //
|
---|
| 265 | // First, Compute fp value for count at the requested percentile. Note that fp result end up
|
---|
| 266 | // being 1 ulp larger than the correct integer count for this percentile:
|
---|
| 267 | const fpCountAtPercentile = (requestedPercentile / 100.0) * this.totalCount;
|
---|
| 268 | // Next, round up, but make sure to prevent <= 1 ulp inaccurancies in the above fp math from
|
---|
| 269 | // making us skip a count:
|
---|
| 270 | const countAtPercentile = max(ceil(fpCountAtPercentile - ulp_1.default(fpCountAtPercentile)), // round up
|
---|
| 271 | 1 // Make sure we at least reach the first recorded entry
|
---|
| 272 | );
|
---|
| 273 | let totalToCurrentIndex = 0;
|
---|
| 274 | for (let i = 0; i < this.countsArrayLength; i++) {
|
---|
| 275 | totalToCurrentIndex += this.getCountAtIndex(i);
|
---|
| 276 | if (totalToCurrentIndex >= countAtPercentile) {
|
---|
| 277 | var valueAtIndex = this.valueFromIndex(i);
|
---|
| 278 | return percentile === 0.0
|
---|
| 279 | ? this.lowestEquivalentValue(valueAtIndex)
|
---|
| 280 | : this.highestEquivalentValue(valueAtIndex);
|
---|
| 281 | }
|
---|
| 282 | }
|
---|
| 283 | return 0;
|
---|
| 284 | }
|
---|
| 285 | valueFromIndexes(bucketIndex, subBucketIndex) {
|
---|
| 286 | return subBucketIndex * pow(2, bucketIndex + this.unitMagnitude);
|
---|
| 287 | }
|
---|
| 288 | valueFromIndex(index) {
|
---|
| 289 | let bucketIndex = floor(index / this.subBucketHalfCount) - 1;
|
---|
| 290 | let subBucketIndex = (index % this.subBucketHalfCount) + this.subBucketHalfCount;
|
---|
| 291 | if (bucketIndex < 0) {
|
---|
| 292 | subBucketIndex -= this.subBucketHalfCount;
|
---|
| 293 | bucketIndex = 0;
|
---|
| 294 | }
|
---|
| 295 | return this.valueFromIndexes(bucketIndex, subBucketIndex);
|
---|
| 296 | }
|
---|
| 297 | /**
|
---|
| 298 | * Get the lowest value that is equivalent to the given value within the histogram's resolution.
|
---|
| 299 | * Where "equivalent" means that value samples recorded for any two
|
---|
| 300 | * equivalent values are counted in a common total count.
|
---|
| 301 | *
|
---|
| 302 | * @param value The given value
|
---|
| 303 | * @return The lowest value that is equivalent to the given value within the histogram's resolution.
|
---|
| 304 | */
|
---|
| 305 | lowestEquivalentValue(value) {
|
---|
| 306 | const bucketIndex = this.getBucketIndex(value);
|
---|
| 307 | const subBucketIndex = this.getSubBucketIndex(value, bucketIndex);
|
---|
| 308 | const thisValueBaseLevel = this.valueFromIndexes(bucketIndex, subBucketIndex);
|
---|
| 309 | return thisValueBaseLevel;
|
---|
| 310 | }
|
---|
| 311 | /**
|
---|
| 312 | * Get the highest value that is equivalent to the given value within the histogram's resolution.
|
---|
| 313 | * Where "equivalent" means that value samples recorded for any two
|
---|
| 314 | * equivalent values are counted in a common total count.
|
---|
| 315 | *
|
---|
| 316 | * @param value The given value
|
---|
| 317 | * @return The highest value that is equivalent to the given value within the histogram's resolution.
|
---|
| 318 | */
|
---|
| 319 | highestEquivalentValue(value) {
|
---|
| 320 | return this.nextNonEquivalentValue(value) - 1;
|
---|
| 321 | }
|
---|
| 322 | /**
|
---|
| 323 | * Get the next value that is not equivalent to the given value within the histogram's resolution.
|
---|
| 324 | * Where "equivalent" means that value samples recorded for any two
|
---|
| 325 | * equivalent values are counted in a common total count.
|
---|
| 326 | *
|
---|
| 327 | * @param value The given value
|
---|
| 328 | * @return The next value that is not equivalent to the given value within the histogram's resolution.
|
---|
| 329 | */
|
---|
| 330 | nextNonEquivalentValue(value) {
|
---|
| 331 | return (this.lowestEquivalentValue(value) + this.sizeOfEquivalentValueRange(value));
|
---|
| 332 | }
|
---|
| 333 | /**
|
---|
| 334 | * Get the size (in value units) of the range of values that are equivalent to the given value within the
|
---|
| 335 | * histogram's resolution. Where "equivalent" means that value samples recorded for any two
|
---|
| 336 | * equivalent values are counted in a common total count.
|
---|
| 337 | *
|
---|
| 338 | * @param value The given value
|
---|
| 339 | * @return The size of the range of values equivalent to the given value.
|
---|
| 340 | */
|
---|
| 341 | sizeOfEquivalentValueRange(value) {
|
---|
| 342 | const bucketIndex = this.getBucketIndex(value);
|
---|
| 343 | const subBucketIndex = this.getSubBucketIndex(value, bucketIndex);
|
---|
| 344 | const distanceToNextValue = pow(2, this.unitMagnitude +
|
---|
| 345 | (subBucketIndex >= this.subBucketCount ? bucketIndex + 1 : bucketIndex));
|
---|
| 346 | return distanceToNextValue;
|
---|
| 347 | }
|
---|
| 348 | /**
|
---|
| 349 | * Get a value that lies in the middle (rounded up) of the range of values equivalent the given value.
|
---|
| 350 | * Where "equivalent" means that value samples recorded for any two
|
---|
| 351 | * equivalent values are counted in a common total count.
|
---|
| 352 | *
|
---|
| 353 | * @param value The given value
|
---|
| 354 | * @return The value lies in the middle (rounded up) of the range of values equivalent the given value.
|
---|
| 355 | */
|
---|
| 356 | medianEquivalentValue(value) {
|
---|
| 357 | return (this.lowestEquivalentValue(value) +
|
---|
| 358 | floor(this.sizeOfEquivalentValueRange(value) / 2));
|
---|
| 359 | }
|
---|
| 360 | /**
|
---|
| 361 | * Get the computed mean value of all recorded values in the histogram
|
---|
| 362 | *
|
---|
| 363 | * @return the mean value (in value units) of the histogram data
|
---|
| 364 | */
|
---|
| 365 | get mean() {
|
---|
| 366 | if (this.totalCount === 0) {
|
---|
| 367 | return 0;
|
---|
| 368 | }
|
---|
| 369 | this.recordedValuesIterator.reset();
|
---|
| 370 | let totalValue = 0;
|
---|
| 371 | while (this.recordedValuesIterator.hasNext()) {
|
---|
| 372 | const iterationValue = this.recordedValuesIterator.next();
|
---|
| 373 | totalValue +=
|
---|
| 374 | this.medianEquivalentValue(iterationValue.valueIteratedTo) *
|
---|
| 375 | iterationValue.countAtValueIteratedTo;
|
---|
| 376 | }
|
---|
| 377 | return totalValue / this.totalCount;
|
---|
| 378 | }
|
---|
| 379 | getStdDeviation(mean = this.mean) {
|
---|
| 380 | if (this.totalCount === 0) {
|
---|
| 381 | return 0;
|
---|
| 382 | }
|
---|
| 383 | let geometric_deviation_total = 0.0;
|
---|
| 384 | this.recordedValuesIterator.reset();
|
---|
| 385 | while (this.recordedValuesIterator.hasNext()) {
|
---|
| 386 | const iterationValue = this.recordedValuesIterator.next();
|
---|
| 387 | const deviation = this.medianEquivalentValue(iterationValue.valueIteratedTo) - mean;
|
---|
| 388 | geometric_deviation_total +=
|
---|
| 389 | deviation * deviation * iterationValue.countAddedInThisIterationStep;
|
---|
| 390 | }
|
---|
| 391 | const std_deviation = Math.sqrt(geometric_deviation_total / this.totalCount);
|
---|
| 392 | return std_deviation;
|
---|
| 393 | }
|
---|
| 394 | /**
|
---|
| 395 | * Get the computed standard deviation of all recorded values in the histogram
|
---|
| 396 | *
|
---|
| 397 | * @return the standard deviation (in value units) of the histogram data
|
---|
| 398 | */
|
---|
| 399 | get stdDeviation() {
|
---|
| 400 | if (this.totalCount === 0) {
|
---|
| 401 | return 0;
|
---|
| 402 | }
|
---|
| 403 | const mean = this.mean;
|
---|
| 404 | let geometric_deviation_total = 0.0;
|
---|
| 405 | this.recordedValuesIterator.reset();
|
---|
| 406 | while (this.recordedValuesIterator.hasNext()) {
|
---|
| 407 | const iterationValue = this.recordedValuesIterator.next();
|
---|
| 408 | const deviation = this.medianEquivalentValue(iterationValue.valueIteratedTo) - mean;
|
---|
| 409 | geometric_deviation_total +=
|
---|
| 410 | deviation * deviation * iterationValue.countAddedInThisIterationStep;
|
---|
| 411 | }
|
---|
| 412 | const std_deviation = Math.sqrt(geometric_deviation_total / this.totalCount);
|
---|
| 413 | return std_deviation;
|
---|
| 414 | }
|
---|
| 415 | /**
|
---|
| 416 | * Produce textual representation of the value distribution of histogram data by percentile. The distribution is
|
---|
| 417 | * output with exponentially increasing resolution, with each exponentially decreasing half-distance containing
|
---|
| 418 | * <i>dumpTicksPerHalf</i> percentile reporting tick points.
|
---|
| 419 | *
|
---|
| 420 | * @param printStream Stream into which the distribution will be output
|
---|
| 421 | * <p>
|
---|
| 422 | * @param percentileTicksPerHalfDistance The number of reporting points per exponentially decreasing half-distance
|
---|
| 423 | * <p>
|
---|
| 424 | * @param outputValueUnitScalingRatio The scaling factor by which to divide histogram recorded values units in
|
---|
| 425 | * output
|
---|
| 426 | * @param useCsvFormat Output in CSV format if true. Otherwise use plain text form.
|
---|
| 427 | */
|
---|
| 428 | outputPercentileDistribution(percentileTicksPerHalfDistance = 5, outputValueUnitScalingRatio = 1, useCsvFormat = false) {
|
---|
| 429 | let result = "";
|
---|
| 430 | if (useCsvFormat) {
|
---|
| 431 | result += '"Value","Percentile","TotalCount","1/(1-Percentile)"\n';
|
---|
| 432 | }
|
---|
| 433 | else {
|
---|
| 434 | result += " Value Percentile TotalCount 1/(1-Percentile)\n\n";
|
---|
| 435 | }
|
---|
| 436 | const iterator = this.percentileIterator;
|
---|
| 437 | iterator.reset(percentileTicksPerHalfDistance);
|
---|
| 438 | let lineFormatter;
|
---|
| 439 | let lastLineFormatter;
|
---|
| 440 | if (useCsvFormat) {
|
---|
| 441 | const valueFormatter = formatters_1.floatFormatter(0, this.numberOfSignificantValueDigits);
|
---|
| 442 | const percentileFormatter = formatters_1.floatFormatter(0, 12);
|
---|
| 443 | const lastFormatter = formatters_1.floatFormatter(0, 2);
|
---|
| 444 | lineFormatter = (iterationValue) => valueFormatter(iterationValue.valueIteratedTo / outputValueUnitScalingRatio) +
|
---|
| 445 | "," +
|
---|
| 446 | percentileFormatter(iterationValue.percentileLevelIteratedTo / 100) +
|
---|
| 447 | "," +
|
---|
| 448 | iterationValue.totalCountToThisValue +
|
---|
| 449 | "," +
|
---|
| 450 | lastFormatter(1 / (1 - iterationValue.percentileLevelIteratedTo / 100)) +
|
---|
| 451 | "\n";
|
---|
| 452 | lastLineFormatter = (iterationValue) => valueFormatter(iterationValue.valueIteratedTo / outputValueUnitScalingRatio) +
|
---|
| 453 | "," +
|
---|
| 454 | percentileFormatter(iterationValue.percentileLevelIteratedTo / 100) +
|
---|
| 455 | "," +
|
---|
| 456 | iterationValue.totalCountToThisValue +
|
---|
| 457 | ",Infinity\n";
|
---|
| 458 | }
|
---|
| 459 | else {
|
---|
| 460 | const valueFormatter = formatters_1.floatFormatter(12, this.numberOfSignificantValueDigits);
|
---|
| 461 | const percentileFormatter = formatters_1.floatFormatter(2, 12);
|
---|
| 462 | const totalCountFormatter = formatters_1.integerFormatter(10);
|
---|
| 463 | const lastFormatter = formatters_1.floatFormatter(14, 2);
|
---|
| 464 | lineFormatter = (iterationValue) => valueFormatter(iterationValue.valueIteratedTo / outputValueUnitScalingRatio) +
|
---|
| 465 | " " +
|
---|
| 466 | percentileFormatter(iterationValue.percentileLevelIteratedTo / 100) +
|
---|
| 467 | " " +
|
---|
| 468 | totalCountFormatter(iterationValue.totalCountToThisValue) +
|
---|
| 469 | " " +
|
---|
| 470 | lastFormatter(1 / (1 - iterationValue.percentileLevelIteratedTo / 100)) +
|
---|
| 471 | "\n";
|
---|
| 472 | lastLineFormatter = (iterationValue) => valueFormatter(iterationValue.valueIteratedTo / outputValueUnitScalingRatio) +
|
---|
| 473 | " " +
|
---|
| 474 | percentileFormatter(iterationValue.percentileLevelIteratedTo / 100) +
|
---|
| 475 | " " +
|
---|
| 476 | totalCountFormatter(iterationValue.totalCountToThisValue) +
|
---|
| 477 | "\n";
|
---|
| 478 | }
|
---|
| 479 | while (iterator.hasNext()) {
|
---|
| 480 | const iterationValue = iterator.next();
|
---|
| 481 | if (iterationValue.percentileLevelIteratedTo < 100) {
|
---|
| 482 | result += lineFormatter(iterationValue);
|
---|
| 483 | }
|
---|
| 484 | else {
|
---|
| 485 | result += lastLineFormatter(iterationValue);
|
---|
| 486 | }
|
---|
| 487 | }
|
---|
| 488 | if (!useCsvFormat) {
|
---|
| 489 | // Calculate and output mean and std. deviation.
|
---|
| 490 | // Note: mean/std. deviation numbers are very often completely irrelevant when
|
---|
| 491 | // data is extremely non-normal in distribution (e.g. in cases of strong multi-modal
|
---|
| 492 | // response time distribution associated with GC pauses). However, reporting these numbers
|
---|
| 493 | // can be very useful for contrasting with the detailed percentile distribution
|
---|
| 494 | // reported by outputPercentileDistribution(). It is not at all surprising to find
|
---|
| 495 | // percentile distributions where results fall many tens or even hundreds of standard
|
---|
| 496 | // deviations away from the mean - such results simply indicate that the data sampled
|
---|
| 497 | // exhibits a very non-normal distribution, highlighting situations for which the std.
|
---|
| 498 | // deviation metric is a useless indicator.
|
---|
| 499 | //
|
---|
| 500 | const formatter = formatters_1.floatFormatter(12, this.numberOfSignificantValueDigits);
|
---|
| 501 | const _mean = this.mean;
|
---|
| 502 | const mean = formatter(_mean / outputValueUnitScalingRatio);
|
---|
| 503 | const std_deviation = formatter(this.getStdDeviation(_mean) / outputValueUnitScalingRatio);
|
---|
| 504 | const max = formatter(this.maxValue / outputValueUnitScalingRatio);
|
---|
| 505 | const intFormatter = formatters_1.integerFormatter(12);
|
---|
| 506 | const totalCount = intFormatter(this.totalCount);
|
---|
| 507 | const bucketCount = intFormatter(this.bucketCount);
|
---|
| 508 | const subBucketCount = intFormatter(this.subBucketCount);
|
---|
| 509 | result += `#[Mean = ${mean}, StdDeviation = ${std_deviation}]
|
---|
| 510 | #[Max = ${max}, Total count = ${totalCount}]
|
---|
| 511 | #[Buckets = ${bucketCount}, SubBuckets = ${subBucketCount}]
|
---|
| 512 | `;
|
---|
| 513 | }
|
---|
| 514 | return result;
|
---|
| 515 | }
|
---|
| 516 | get summary() {
|
---|
| 517 | return Histogram_1.toSummary(this);
|
---|
| 518 | }
|
---|
| 519 | toJSON() {
|
---|
| 520 | return this.summary;
|
---|
| 521 | }
|
---|
| 522 | inspect() {
|
---|
| 523 | return this.toString();
|
---|
| 524 | }
|
---|
| 525 | [Symbol.for("nodejs.util.inspect.custom")]() {
|
---|
| 526 | return this.toString();
|
---|
| 527 | }
|
---|
| 528 | /**
|
---|
| 529 | * Provide a (conservatively high) estimate of the Histogram's total footprint in bytes
|
---|
| 530 | *
|
---|
| 531 | * @return a (conservatively high) estimate of the Histogram's total footprint in bytes
|
---|
| 532 | */
|
---|
| 533 | get estimatedFootprintInBytes() {
|
---|
| 534 | return this._getEstimatedFootprintInBytes();
|
---|
| 535 | }
|
---|
| 536 | recordSingleValueWithExpectedInterval(value, expectedIntervalBetweenValueSamples) {
|
---|
| 537 | this.recordSingleValue(value);
|
---|
| 538 | if (expectedIntervalBetweenValueSamples <= 0) {
|
---|
| 539 | return;
|
---|
| 540 | }
|
---|
| 541 | for (let missingValue = value - expectedIntervalBetweenValueSamples; missingValue >= expectedIntervalBetweenValueSamples; missingValue -= expectedIntervalBetweenValueSamples) {
|
---|
| 542 | this.recordSingleValue(missingValue);
|
---|
| 543 | }
|
---|
| 544 | }
|
---|
| 545 | recordCountAtValue(count, value) {
|
---|
| 546 | const countsIndex = this.countsArrayIndex(value);
|
---|
| 547 | if (countsIndex >= this.countsArrayLength) {
|
---|
| 548 | this.handleRecordException(count, value);
|
---|
| 549 | }
|
---|
| 550 | else {
|
---|
| 551 | this.addToCountAtIndex(countsIndex, count);
|
---|
| 552 | }
|
---|
| 553 | this.updateMinAndMax(value);
|
---|
| 554 | this.addToTotalCount(count);
|
---|
| 555 | }
|
---|
| 556 | /**
|
---|
| 557 | * Record a value in the histogram (adding to the value's current count)
|
---|
| 558 | *
|
---|
| 559 | * @param value The value to be recorded
|
---|
| 560 | * @param count The number of occurrences of this value to record
|
---|
| 561 | * @throws ArrayIndexOutOfBoundsException (may throw) if value is exceeds highestTrackableValue
|
---|
| 562 | */
|
---|
| 563 | recordValueWithCount(value, count) {
|
---|
| 564 | this.recordCountAtValue(count, value);
|
---|
| 565 | }
|
---|
| 566 | /**
|
---|
| 567 | * Record a value in the histogram.
|
---|
| 568 | * <p>
|
---|
| 569 | * To compensate for the loss of sampled values when a recorded value is larger than the expected
|
---|
| 570 | * interval between value samples, Histogram will auto-generate an additional series of decreasingly-smaller
|
---|
| 571 | * (down to the expectedIntervalBetweenValueSamples) value records.
|
---|
| 572 | * <p>
|
---|
| 573 | * Note: This is a at-recording correction method, as opposed to the post-recording correction method provided
|
---|
| 574 | * by {@link #copyCorrectedForCoordinatedOmission(long)}.
|
---|
| 575 | * The two methods are mutually exclusive, and only one of the two should be be used on a given data set to correct
|
---|
| 576 | * for the same coordinated omission issue.
|
---|
| 577 | * <p>
|
---|
| 578 | * See notes in the description of the Histogram calls for an illustration of why this corrective behavior is
|
---|
| 579 | * important.
|
---|
| 580 | *
|
---|
| 581 | * @param value The value to record
|
---|
| 582 | * @param expectedIntervalBetweenValueSamples If expectedIntervalBetweenValueSamples is larger than 0, add
|
---|
| 583 | * auto-generated value records as appropriate if value is larger
|
---|
| 584 | * than expectedIntervalBetweenValueSamples
|
---|
| 585 | * @throws ArrayIndexOutOfBoundsException (may throw) if value is exceeds highestTrackableValue
|
---|
| 586 | */
|
---|
| 587 | recordValueWithExpectedInterval(value, expectedIntervalBetweenValueSamples) {
|
---|
| 588 | this.recordSingleValueWithExpectedInterval(value, expectedIntervalBetweenValueSamples);
|
---|
| 589 | }
|
---|
| 590 | recordValueWithCountAndExpectedInterval(value, count, expectedIntervalBetweenValueSamples) {
|
---|
| 591 | this.recordCountAtValue(count, value);
|
---|
| 592 | if (expectedIntervalBetweenValueSamples <= 0) {
|
---|
| 593 | return;
|
---|
| 594 | }
|
---|
| 595 | for (let missingValue = value - expectedIntervalBetweenValueSamples; missingValue >= expectedIntervalBetweenValueSamples; missingValue -= expectedIntervalBetweenValueSamples) {
|
---|
| 596 | this.recordCountAtValue(count, missingValue);
|
---|
| 597 | }
|
---|
| 598 | }
|
---|
| 599 | /**
|
---|
| 600 | * Add the contents of another histogram to this one, while correcting the incoming data for coordinated omission.
|
---|
| 601 | * <p>
|
---|
| 602 | * To compensate for the loss of sampled values when a recorded value is larger than the expected
|
---|
| 603 | * interval between value samples, the values added will include an auto-generated additional series of
|
---|
| 604 | * decreasingly-smaller (down to the expectedIntervalBetweenValueSamples) value records for each count found
|
---|
| 605 | * in the current histogram that is larger than the expectedIntervalBetweenValueSamples.
|
---|
| 606 | *
|
---|
| 607 | * Note: This is a post-recording correction method, as opposed to the at-recording correction method provided
|
---|
| 608 | * by {@link #recordValueWithExpectedInterval(long, long) recordValueWithExpectedInterval}. The two
|
---|
| 609 | * methods are mutually exclusive, and only one of the two should be be used on a given data set to correct
|
---|
| 610 | * for the same coordinated omission issue.
|
---|
| 611 | * by
|
---|
| 612 | * <p>
|
---|
| 613 | * See notes in the description of the Histogram calls for an illustration of why this corrective behavior is
|
---|
| 614 | * important.
|
---|
| 615 | *
|
---|
| 616 | * @param otherHistogram The other histogram. highestTrackableValue and largestValueWithSingleUnitResolution must match.
|
---|
| 617 | * @param expectedIntervalBetweenValueSamples If expectedIntervalBetweenValueSamples is larger than 0, add
|
---|
| 618 | * auto-generated value records as appropriate if value is larger
|
---|
| 619 | * than expectedIntervalBetweenValueSamples
|
---|
| 620 | * @throws ArrayIndexOutOfBoundsException (may throw) if values exceed highestTrackableValue
|
---|
| 621 | */
|
---|
| 622 | addWhileCorrectingForCoordinatedOmission(otherHistogram, expectedIntervalBetweenValueSamples) {
|
---|
| 623 | const toHistogram = this;
|
---|
| 624 | const otherValues = new RecordedValuesIterator_1.default(otherHistogram);
|
---|
| 625 | while (otherValues.hasNext()) {
|
---|
| 626 | const v = otherValues.next();
|
---|
| 627 | toHistogram.recordValueWithCountAndExpectedInterval(v.valueIteratedTo, v.countAtValueIteratedTo, expectedIntervalBetweenValueSamples);
|
---|
| 628 | }
|
---|
| 629 | }
|
---|
| 630 | /**
|
---|
| 631 | * Add the contents of another histogram to this one.
|
---|
| 632 | * <p>
|
---|
| 633 | * As part of adding the contents, the start/end timestamp range of this histogram will be
|
---|
| 634 | * extended to include the start/end timestamp range of the other histogram.
|
---|
| 635 | *
|
---|
| 636 | * @param otherHistogram The other histogram.
|
---|
| 637 | * @throws (may throw) if values in fromHistogram's are
|
---|
| 638 | * higher than highestTrackableValue.
|
---|
| 639 | */
|
---|
| 640 | add(otherHistogram) {
|
---|
| 641 | if (!(otherHistogram instanceof JsHistogram)) {
|
---|
| 642 | // should be impossible to be in this situation but actually
|
---|
| 643 | // TypeScript has some flaws...
|
---|
| 644 | throw new Error("Cannot add a WASM histogram to a regular JS histogram");
|
---|
| 645 | }
|
---|
| 646 | const highestRecordableValue = this.highestEquivalentValue(this.valueFromIndex(this.countsArrayLength - 1));
|
---|
| 647 | if (highestRecordableValue < otherHistogram.maxValue) {
|
---|
| 648 | if (!this.autoResize) {
|
---|
| 649 | throw new Error("The other histogram includes values that do not fit in this histogram's range.");
|
---|
| 650 | }
|
---|
| 651 | this.resize(otherHistogram.maxValue);
|
---|
| 652 | }
|
---|
| 653 | if (this.bucketCount === otherHistogram.bucketCount &&
|
---|
| 654 | this.subBucketCount === otherHistogram.subBucketCount &&
|
---|
| 655 | this.unitMagnitude === otherHistogram.unitMagnitude) {
|
---|
| 656 | // Counts arrays are of the same length and meaning, so we can just iterate and add directly:
|
---|
| 657 | let observedOtherTotalCount = 0;
|
---|
| 658 | for (let i = 0; i < otherHistogram.countsArrayLength; i++) {
|
---|
| 659 | const otherCount = otherHistogram.getCountAtIndex(i);
|
---|
| 660 | if (otherCount > 0) {
|
---|
| 661 | this.addToCountAtIndex(i, otherCount);
|
---|
| 662 | observedOtherTotalCount += otherCount;
|
---|
| 663 | }
|
---|
| 664 | }
|
---|
| 665 | this.setTotalCount(this.totalCount + observedOtherTotalCount);
|
---|
| 666 | this.updatedMaxValue(max(this.maxValue, otherHistogram.maxValue));
|
---|
| 667 | this.updateMinNonZeroValue(min(this.minNonZeroValue, otherHistogram.minNonZeroValue));
|
---|
| 668 | }
|
---|
| 669 | else {
|
---|
| 670 | // Arrays are not a direct match (or the other could change on the fly in some valid way),
|
---|
| 671 | // so we can't just stream through and add them. Instead, go through the array and add each
|
---|
| 672 | // non-zero value found at it's proper value:
|
---|
| 673 | // Do max value first, to avoid max value updates on each iteration:
|
---|
| 674 | const otherMaxIndex = otherHistogram.countsArrayIndex(otherHistogram.maxValue);
|
---|
| 675 | let otherCount = otherHistogram.getCountAtIndex(otherMaxIndex);
|
---|
| 676 | this.recordCountAtValue(otherCount, otherHistogram.valueFromIndex(otherMaxIndex));
|
---|
| 677 | // Record the remaining values, up to but not including the max value:
|
---|
| 678 | for (let i = 0; i < otherMaxIndex; i++) {
|
---|
| 679 | otherCount = otherHistogram.getCountAtIndex(i);
|
---|
| 680 | if (otherCount > 0) {
|
---|
| 681 | this.recordCountAtValue(otherCount, otherHistogram.valueFromIndex(i));
|
---|
| 682 | }
|
---|
| 683 | }
|
---|
| 684 | }
|
---|
| 685 | this.startTimeStampMsec = min(this.startTimeStampMsec, otherHistogram.startTimeStampMsec);
|
---|
| 686 | this.endTimeStampMsec = max(this.endTimeStampMsec, otherHistogram.endTimeStampMsec);
|
---|
| 687 | }
|
---|
| 688 | /**
|
---|
| 689 | * Get the count of recorded values at a specific value (to within the histogram resolution at the value level).
|
---|
| 690 | *
|
---|
| 691 | * @param value The value for which to provide the recorded count
|
---|
| 692 | * @return The total count of values recorded in the histogram within the value range that is
|
---|
| 693 | * {@literal >=} lowestEquivalentValue(<i>value</i>) and {@literal <=} highestEquivalentValue(<i>value</i>)
|
---|
| 694 | */
|
---|
| 695 | getCountAtValue(value) {
|
---|
| 696 | const index = min(max(0, this.countsArrayIndex(value)), this.countsArrayLength - 1);
|
---|
| 697 | return this.getCountAtIndex(index);
|
---|
| 698 | }
|
---|
| 699 | /**
|
---|
| 700 | * Subtract the contents of another histogram from this one.
|
---|
| 701 | * <p>
|
---|
| 702 | * The start/end timestamps of this histogram will remain unchanged.
|
---|
| 703 | *
|
---|
| 704 | * @param otherHistogram The other histogram.
|
---|
| 705 | * @throws ArrayIndexOutOfBoundsException (may throw) if values in otherHistogram's are higher than highestTrackableValue.
|
---|
| 706 | *
|
---|
| 707 | */
|
---|
| 708 | subtract(otherHistogram) {
|
---|
| 709 | const highestRecordableValue = this.valueFromIndex(this.countsArrayLength - 1);
|
---|
| 710 | if (!(otherHistogram instanceof JsHistogram)) {
|
---|
| 711 | // should be impossible to be in this situation but actually
|
---|
| 712 | // TypeScript has some flaws...
|
---|
| 713 | throw new Error("Cannot subtract a WASM histogram to a regular JS histogram");
|
---|
| 714 | }
|
---|
| 715 | if (highestRecordableValue < otherHistogram.maxValue) {
|
---|
| 716 | if (!this.autoResize) {
|
---|
| 717 | throw new Error("The other histogram includes values that do not fit in this histogram's range.");
|
---|
| 718 | }
|
---|
| 719 | this.resize(otherHistogram.maxValue);
|
---|
| 720 | }
|
---|
| 721 | if (this.bucketCount === otherHistogram.bucketCount &&
|
---|
| 722 | this.subBucketCount === otherHistogram.subBucketCount &&
|
---|
| 723 | this.unitMagnitude === otherHistogram.unitMagnitude) {
|
---|
| 724 | // optim
|
---|
| 725 | // Counts arrays are of the same length and meaning, so we can just iterate and add directly:
|
---|
| 726 | let observedOtherTotalCount = 0;
|
---|
| 727 | for (let i = 0; i < otherHistogram.countsArrayLength; i++) {
|
---|
| 728 | const otherCount = otherHistogram.getCountAtIndex(i);
|
---|
| 729 | if (otherCount > 0) {
|
---|
| 730 | this.addToCountAtIndex(i, -otherCount);
|
---|
| 731 | observedOtherTotalCount += otherCount;
|
---|
| 732 | }
|
---|
| 733 | }
|
---|
| 734 | this.setTotalCount(this.totalCount - observedOtherTotalCount);
|
---|
| 735 | }
|
---|
| 736 | else {
|
---|
| 737 | for (let i = 0; i < otherHistogram.countsArrayLength; i++) {
|
---|
| 738 | const otherCount = otherHistogram.getCountAtIndex(i);
|
---|
| 739 | if (otherCount > 0) {
|
---|
| 740 | const otherValue = otherHistogram.valueFromIndex(i);
|
---|
| 741 | if (this.getCountAtValue(otherValue) < otherCount) {
|
---|
| 742 | throw new Error("otherHistogram count (" +
|
---|
| 743 | otherCount +
|
---|
| 744 | ") at value " +
|
---|
| 745 | otherValue +
|
---|
| 746 | " is larger than this one's (" +
|
---|
| 747 | this.getCountAtValue(otherValue) +
|
---|
| 748 | ")");
|
---|
| 749 | }
|
---|
| 750 | this.recordCountAtValue(-otherCount, otherValue);
|
---|
| 751 | }
|
---|
| 752 | }
|
---|
| 753 | }
|
---|
| 754 | // With subtraction, the max and minNonZero values could have changed:
|
---|
| 755 | if (this.getCountAtValue(this.maxValue) <= 0 ||
|
---|
| 756 | this.getCountAtValue(this.minNonZeroValue) <= 0) {
|
---|
| 757 | this.establishInternalTackingValues();
|
---|
| 758 | }
|
---|
| 759 | }
|
---|
| 760 | establishInternalTackingValues(lengthToCover = this.countsArrayLength) {
|
---|
| 761 | this.maxValue = 0;
|
---|
| 762 | this.minNonZeroValue = Number.MAX_VALUE;
|
---|
| 763 | let maxIndex = -1;
|
---|
| 764 | let minNonZeroIndex = -1;
|
---|
| 765 | let observedTotalCount = 0;
|
---|
| 766 | for (let index = 0; index < lengthToCover; index++) {
|
---|
| 767 | const countAtIndex = this.getCountAtIndex(index);
|
---|
| 768 | if (countAtIndex > 0) {
|
---|
| 769 | observedTotalCount += countAtIndex;
|
---|
| 770 | maxIndex = index;
|
---|
| 771 | if (minNonZeroIndex == -1 && index != 0) {
|
---|
| 772 | minNonZeroIndex = index;
|
---|
| 773 | }
|
---|
| 774 | }
|
---|
| 775 | }
|
---|
| 776 | if (maxIndex >= 0) {
|
---|
| 777 | this.updatedMaxValue(this.highestEquivalentValue(this.valueFromIndex(maxIndex)));
|
---|
| 778 | }
|
---|
| 779 | if (minNonZeroIndex >= 0) {
|
---|
| 780 | this.updateMinNonZeroValue(this.valueFromIndex(minNonZeroIndex));
|
---|
| 781 | }
|
---|
| 782 | this.setTotalCount(observedTotalCount);
|
---|
| 783 | }
|
---|
| 784 | reset() {
|
---|
| 785 | this.clearCounts();
|
---|
| 786 | this.setTotalCount(0);
|
---|
| 787 | this.startTimeStampMsec = 0;
|
---|
| 788 | this.endTimeStampMsec = 0;
|
---|
| 789 | this.tag = Histogram_1.NO_TAG;
|
---|
| 790 | this.maxValue = 0;
|
---|
| 791 | this.minNonZeroValue = Number.MAX_SAFE_INTEGER;
|
---|
| 792 | }
|
---|
| 793 | destroy() {
|
---|
| 794 | // no op - not needed here
|
---|
| 795 | }
|
---|
| 796 | }
|
---|
| 797 | exports.JsHistogram = JsHistogram;
|
---|
| 798 | exports.default = JsHistogram;
|
---|
| 799 | //# sourceMappingURL=JsHistogram.js.map |
---|