You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
171 lines
5.4 KiB
171 lines
5.4 KiB
/*
|
|
* This is a TypeScript port of the original Java version, which was written by
|
|
* Gil Tene as described in
|
|
* https://github.com/HdrHistogram/HdrHistogram
|
|
* and released to the public domain, as explained at
|
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
|
*/
|
|
import * as fc from "fast-check";
|
|
import * as hdr from "./index";
|
|
import { initWebAssembly } from "./wasm";
|
|
import Histogram, { BitBucketSize } from "./Histogram";
|
|
|
|
const runFromStryker = __dirname.includes("stryker");
|
|
|
|
const runnerOptions = {
|
|
numRuns: runFromStryker ? 10 : 1000,
|
|
verbose: true,
|
|
};
|
|
|
|
describe("Histogram percentile computation", () => {
|
|
beforeAll(initWebAssembly);
|
|
|
|
const numberOfSignificantValueDigits = 3;
|
|
[true, false].forEach((useWebAssembly) =>
|
|
[16, "packed"].forEach((bitBucketSize: BitBucketSize) =>
|
|
it(`Histogram ${bitBucketSize} (wasm: ${useWebAssembly}) should be accurate according to its significant figures`, async () => {
|
|
await initWebAssembly();
|
|
|
|
fc.assert(
|
|
fc.property(arbData(2000), (numbers) => {
|
|
const histogram = hdr.build({
|
|
bitBucketSize,
|
|
numberOfSignificantValueDigits,
|
|
useWebAssembly,
|
|
});
|
|
numbers.forEach((n) => histogram.recordValue(n));
|
|
const actual = quantile(numbers, 90);
|
|
const got = histogram.getValueAtPercentile(90);
|
|
const relativeError = Math.abs(1 - got / actual);
|
|
const variation = Math.pow(10, -numberOfSignificantValueDigits);
|
|
histogram.destroy();
|
|
return relativeError < variation;
|
|
}),
|
|
runnerOptions
|
|
);
|
|
})
|
|
)
|
|
);
|
|
});
|
|
|
|
describe("Histogram percentile computation (packed vs classic)", () => {
|
|
const numberOfSignificantValueDigits = 3;
|
|
const classicHistogram = hdr.build({
|
|
numberOfSignificantValueDigits,
|
|
});
|
|
const histogram = hdr.build({
|
|
numberOfSignificantValueDigits,
|
|
bitBucketSize: "packed",
|
|
useWebAssembly: false,
|
|
});
|
|
|
|
it(`should be accurate according to its significant figures`, () => {
|
|
fc.assert(
|
|
fc.property(arbData(5), (numbers) => {
|
|
histogram.reset();
|
|
classicHistogram.reset();
|
|
numbers.forEach((n) => histogram.recordValue(n));
|
|
numbers.forEach((n) => classicHistogram.recordValue(n));
|
|
const actual = classicHistogram.getValueAtPercentile(90);
|
|
const got = histogram.getValueAtPercentile(90);
|
|
return actual === got;
|
|
}),
|
|
runnerOptions
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Histogram percentile computation with CO correction (wasm vs js)", () => {
|
|
beforeAll(initWebAssembly);
|
|
|
|
let jsHistogram: Histogram;
|
|
let wasmHistogram: Histogram;
|
|
|
|
beforeEach(() => {
|
|
jsHistogram = hdr.build({
|
|
useWebAssembly: false,
|
|
});
|
|
wasmHistogram = hdr.build({
|
|
useWebAssembly: true,
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
jsHistogram.destroy();
|
|
wasmHistogram.destroy();
|
|
});
|
|
|
|
it(`should be accurate according to its significant figures`, () => {
|
|
fc.assert(
|
|
fc.property(arbData(1, 100 * 1000), (numbers) => {
|
|
jsHistogram.reset();
|
|
wasmHistogram.reset();
|
|
numbers.forEach((n) => {
|
|
jsHistogram.recordValueWithExpectedInterval(n, 1000);
|
|
});
|
|
numbers.forEach((n) => {
|
|
wasmHistogram.recordValueWithExpectedInterval(n, 1000);
|
|
});
|
|
const js = jsHistogram.getValueAtPercentile(90);
|
|
const wasm = wasmHistogram.getValueAtPercentile(90);
|
|
const relativeError = Math.abs(1 - js / wasm);
|
|
const variation = Math.pow(10, -3);
|
|
if (relativeError >= variation) {
|
|
console.log({ js, wasm });
|
|
}
|
|
return relativeError < variation;
|
|
}),
|
|
runnerOptions
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Histogram encoding/decoding", () => {
|
|
beforeAll(initWebAssembly);
|
|
|
|
const numberOfSignificantValueDigits = 3;
|
|
[true, false].forEach((useWebAssembly) =>
|
|
[8, 16, 32, 64, "packed"].forEach((bitBucketSize: BitBucketSize) => {
|
|
it(`Histogram ${bitBucketSize} (wasm: ${useWebAssembly}) should keep all data after an encoding/decoding roundtrip`, () => {
|
|
fc.assert(
|
|
fc.property(arbData(1), fc.double(50, 100), (numbers, percentile) => {
|
|
const histogram = hdr.build({
|
|
bitBucketSize,
|
|
numberOfSignificantValueDigits,
|
|
useWebAssembly,
|
|
});
|
|
numbers.forEach((n) => histogram.recordValue(n));
|
|
const encodedHistogram = hdr.encodeIntoCompressedBase64(histogram);
|
|
const decodedHistogram = hdr.decodeFromCompressedBase64(
|
|
encodedHistogram
|
|
);
|
|
const actual = histogram.getValueAtPercentile(percentile);
|
|
const got = decodedHistogram.getValueAtPercentile(percentile);
|
|
histogram.destroy();
|
|
decodedHistogram.destroy();
|
|
return actual === got;
|
|
}),
|
|
runnerOptions
|
|
);
|
|
});
|
|
})
|
|
);
|
|
});
|
|
|
|
const arbData = (size: number, max: number = Number.MAX_SAFE_INTEGER) =>
|
|
fc.array(fc.integer(1, max), size, size);
|
|
|
|
// reference implementation
|
|
const quantile = (inputData: number[], percentile: number) => {
|
|
const data = [...inputData].sort((a, b) => a - b);
|
|
const index = (percentile / 100) * (data.length - 1);
|
|
let result: number;
|
|
if (Math.floor(index) === index) {
|
|
result = data[index];
|
|
} else {
|
|
const i = Math.floor(index);
|
|
const fraction = index - i;
|
|
result = data[i] + (data[i + 1] - data[i]) * fraction;
|
|
}
|
|
return result;
|
|
};
|