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

/*
* 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;
};