Files
bun.sh/test/js/bun/perf_hooks/histogram.test.ts
2025-05-26 21:18:22 -07:00

586 lines
16 KiB
TypeScript

import assert from "node:assert";
import { describe, test } from "node:test";
import { inspect } from "node:util";
import { createHistogram } from "perf_hooks";
describe("Histogram", () => {
test("basic histogram creation and initial state", () => {
const h = createHistogram();
assert.strictEqual(h.min, 9223372036854776000);
assert.strictEqual(h.minBigInt, 9223372036854775807n);
assert.strictEqual(h.max, 0);
assert.strictEqual(h.maxBigInt, 0n);
assert.strictEqual(h.exceeds, 0);
assert.strictEqual(h.exceedsBigInt, 0n);
assert.ok(Number.isNaN(h.mean));
assert.ok(Number.isNaN(h.stddev));
assert.strictEqual(h.count, 0);
assert.strictEqual(h.countBigInt, 0n);
});
test("recording values", () => {
const h = createHistogram();
h.record(1);
assert.strictEqual(h.count, 1);
assert.strictEqual(h.min, 1);
assert.strictEqual(h.max, 1);
h.record(5);
assert.strictEqual(h.count, 2);
assert.strictEqual(h.min, 1);
assert.strictEqual(h.max, 5);
});
test("recording multiple values", () => {
const h = createHistogram();
for (let i = 1; i <= 10; i++) {
h.record(i);
}
assert.strictEqual(h.count, 10);
assert.strictEqual(h.min, 1);
assert.strictEqual(h.max, 10);
assert.strictEqual(h.mean, 5.5);
});
test("percentiles", () => {
const h = createHistogram();
for (let i = 1; i <= 100; i++) {
h.record(i);
}
assert.strictEqual(h.percentile(50), 50);
assert.strictEqual(h.percentile(90), 90);
assert.strictEqual(h.percentile(99), 99);
});
test("invalid record arguments", () => {
const h = createHistogram();
assert.throws(() => h.record(0), /out of range/);
assert.throws(() => h.record(-1), /out of range/);
assert.throws(() => h.record("invalid"), /must be of type number/);
});
test("histogram with custom options", () => {
const h = createHistogram({ lowest: 1, highest: 11, figures: 1 });
h.record(5);
assert.strictEqual(h.count, 1);
assert.strictEqual(h.min, 5);
assert.strictEqual(h.max, 5);
});
test("invalid histogram options", () => {
assert.throws(() => createHistogram({ figures: 6 }));
assert.throws(() => createHistogram({ figures: 0 }));
});
test("adding histograms", () => {
const h1 = createHistogram();
const h2 = createHistogram();
h1.record(1);
h1.record(2);
h2.record(3);
h2.record(4);
const originalCount1 = h1.count;
const originalCount2 = h2.count;
h1.add(h2);
assert.strictEqual(h1.count, originalCount1 + originalCount2);
assert.strictEqual(h1.min, 1);
assert.strictEqual(h1.max, 4);
});
test("reset functionality", () => {
const h = createHistogram();
h.record(1);
h.record(2);
h.record(3);
assert.strictEqual(h.count, 3);
h.reset();
assert.strictEqual(h.count, 0);
assert.strictEqual(h.exceeds, 0);
assert.ok(Number.isNaN(h.mean));
assert.ok(Number.isNaN(h.stddev));
});
test("recordDelta functionality", async () => {
const h = createHistogram();
h.recordDelta();
await new Promise(resolve => setTimeout(resolve, 10));
h.recordDelta();
assert.strictEqual(h.count, 1);
});
describe("exceeds functionality", () => {
test("basic exceeds counting", () => {
const h = createHistogram({ lowest: 1, highest: 10, figures: 1 });
assert.strictEqual(h.exceeds, 0);
h.record(5);
assert.strictEqual(h.exceeds, 0);
assert.strictEqual(h.count, 1);
h.record(100);
assert.strictEqual(h.exceeds, 1);
assert.strictEqual(h.count, 1);
assert.strictEqual(h.min, 5);
assert.strictEqual(h.max, 5);
});
test("exceeds with BigInt", () => {
const h = createHistogram({ lowest: 1, highest: 10, figures: 1 });
h.record(5);
h.record(100);
assert.strictEqual(h.exceeds, 1);
assert.strictEqual(h.exceedsBigInt, 1n);
assert.strictEqual(h.count, 1);
});
test("exceeds count in add operation", () => {
const h1 = createHistogram({ lowest: 1, highest: 10, figures: 1 });
const h2 = createHistogram({ lowest: 1, highest: 10, figures: 1 });
h1.record(5);
h1.record(100);
assert.strictEqual(h1.exceeds, 1);
assert.strictEqual(h1.count, 1);
h2.record(8);
h2.record(200);
assert.strictEqual(h2.exceeds, 1);
assert.strictEqual(h2.count, 1);
h1.add(h2);
assert.strictEqual(h1.exceeds, 2);
assert.strictEqual(h1.count, 2);
});
test("exceeds count after reset", () => {
const h = createHistogram({ lowest: 1, highest: 10, figures: 1 });
h.record(5);
h.record(100);
assert.strictEqual(h.exceeds, 1);
assert.strictEqual(h.count, 1);
h.reset();
assert.strictEqual(h.exceeds, 0);
assert.strictEqual(h.count, 0);
});
test("exceeds with very small range", () => {
const h = createHistogram({ lowest: 1, highest: 2, figures: 1 });
h.record(1);
h.record(50);
h.record(100);
assert.strictEqual(h.exceeds, 2);
assert.strictEqual(h.count, 1);
assert.strictEqual(h.min, 1);
assert.strictEqual(h.max, 1);
});
});
describe("percentiles functionality", () => {
test("percentiles with map", () => {
const h = createHistogram();
for (let i = 1; i <= 10; i++) {
h.record(i);
}
const percentiles = h.percentiles;
assert.strictEqual(typeof percentiles, "object");
assert.ok(percentiles.size > 0);
assert.ok(percentiles.has(50));
assert.ok(percentiles.has(100));
});
test("percentilesBigInt with map", () => {
const h = createHistogram();
for (let i = 1; i <= 5; i++) {
h.record(i);
}
const percentiles = h.percentilesBigInt;
assert.strictEqual(typeof percentiles, "object");
assert.ok(percentiles.size > 0);
for (const [key, value] of percentiles) {
assert.strictEqual(typeof key, "number");
assert.strictEqual(typeof value, "bigint");
}
});
});
describe("edge cases", () => {
test("recording zero", () => {
const h = createHistogram();
assert.throws(() => h.record(0), /out of range/);
});
test("recording negative values", () => {
const h = createHistogram();
assert.throws(() => h.record(-5), /out of range/);
});
test("very large values", () => {
const h = createHistogram();
h.record(Number.MAX_SAFE_INTEGER);
assert.strictEqual(h.count, 1);
});
test("histogram with same lowest and highest", () => {
assert.throws(() => createHistogram({ lowest: 5, highest: 5, figures: 1 }), /out of range/);
});
test("multiple add operations", () => {
const h1 = createHistogram();
const h2 = createHistogram();
const h3 = createHistogram();
h1.record(1);
h2.record(2);
h3.record(3);
h1.add(h2);
h1.add(h3);
assert.strictEqual(h1.count, 3);
assert.strictEqual(h1.min, 1);
assert.strictEqual(h1.max, 3);
});
});
describe("BigInt support", () => {
test("recording BigInt values", () => {
const h = createHistogram();
h.record(1n);
h.record(5n);
assert.strictEqual(h.count, 2);
assert.strictEqual(h.min, 1);
assert.strictEqual(h.max, 5);
});
test("BigInt getters", () => {
const h = createHistogram();
h.record(42);
assert.strictEqual(h.countBigInt, 1n);
assert.strictEqual(h.minBigInt, 42n);
assert.strictEqual(h.maxBigInt, 42n);
});
});
describe("comprehensive validation tests", () => {
test("createHistogram with BigInt parameters", () => {
const h = createHistogram({ lowest: 1n, highest: 1000n, figures: 3 });
h.record(500);
assert.strictEqual(h.count, 1);
assert.strictEqual(h.min, 500);
assert.strictEqual(h.max, 500);
});
test("createHistogram parameter validation", () => {
assert.throws(
() => createHistogram({ figures: -1 }),
err => {
return err.code === "ERR_OUT_OF_RANGE" && err.message.includes("options.figures");
},
);
assert.throws(
() => createHistogram({ figures: 6 }),
err => {
return err.code === "ERR_OUT_OF_RANGE" && err.message.includes("options.figures");
},
);
assert.throws(
() => createHistogram({ lowest: 0 }),
err => {
return err.code === "ERR_OUT_OF_RANGE" && err.message.includes("options.lowest");
},
);
assert.throws(
() => createHistogram({ lowest: -1 }),
err => {
return err.code === "ERR_OUT_OF_RANGE" && err.message.includes("options.lowest");
},
);
assert.throws(
() => createHistogram({ lowest: 10, highest: 15 }),
err => {
return err.code === "ERR_OUT_OF_RANGE" && err.message.includes("options.highest");
},
);
assert.throws(
() => createHistogram({ lowest: 5, highest: 9 }),
err => {
return err.code === "ERR_OUT_OF_RANGE" && err.message.includes("options.highest");
},
);
assert.throws(
() => createHistogram({ figures: "invalid" }),
err => {
return err.code === "ERR_INVALID_ARG_TYPE" && err.message.includes("options.figures");
},
);
assert.throws(
() => createHistogram({ lowest: "invalid" }),
err => {
return err.code === "ERR_INVALID_ARG_TYPE" && err.message.includes("options.lowest");
},
);
assert.throws(
() => createHistogram({ highest: "invalid" }),
err => {
return err.code === "ERR_INVALID_ARG_TYPE" && err.message.includes("options.highest");
},
);
const h = createHistogram({ lowest: 5, highest: 10, figures: 1 });
assert.strictEqual(h.count, 0);
});
test("percentile validation", () => {
const h = createHistogram();
h.record(50);
assert.throws(() => h.percentile(0), /out of range/);
assert.throws(() => h.percentile(-1), /out of range/);
assert.throws(() => h.percentile(101), /out of range/);
assert.throws(() => h.percentile(NaN), /out of range/);
assert.strictEqual(typeof h.percentile(1), "number");
assert.strictEqual(typeof h.percentile(50), "number");
assert.strictEqual(typeof h.percentile(100), "number");
});
test("percentileBigInt validation", () => {
const h = createHistogram();
h.record(50);
assert.throws(() => h.percentileBigInt(0), /out of range/);
assert.throws(() => h.percentileBigInt(-1), /out of range/);
assert.throws(() => h.percentileBigInt(101), /out of range/);
assert.throws(() => h.percentileBigInt(NaN), /out of range/);
assert.strictEqual(typeof h.percentileBigInt(1), "bigint");
assert.strictEqual(typeof h.percentileBigInt(50), "bigint");
assert.strictEqual(typeof h.percentileBigInt(100), "bigint");
});
test("record with very large BigInt values", () => {
const h = createHistogram();
const largeBigInt = BigInt(Number.MAX_SAFE_INTEGER);
h.record(largeBigInt);
assert.strictEqual(h.count, 1);
assert.strictEqual(h.countBigInt, 1n);
});
test("add with empty histograms", () => {
const h1 = createHistogram();
const h2 = createHistogram();
h1.add(h2);
assert.strictEqual(h1.count, 0);
assert.strictEqual(h1.exceeds, 0);
h2.record(42);
h1.add(h2);
assert.strictEqual(h1.count, 1);
assert.strictEqual(h1.min, 42);
assert.strictEqual(h1.max, 42);
});
test("reset preserves initial state", () => {
const h = createHistogram();
h.record(10);
h.record(20);
h.record(30);
h.reset();
assert.strictEqual(h.count, 0);
assert.strictEqual(h.countBigInt, 0n);
assert.strictEqual(h.min, 9223372036854776000);
assert.strictEqual(h.minBigInt, 9223372036854775807n);
assert.strictEqual(h.max, 0);
assert.strictEqual(h.maxBigInt, 0n);
assert.strictEqual(h.exceeds, 0);
assert.strictEqual(h.exceedsBigInt, 0n);
assert.ok(Number.isNaN(h.mean));
assert.ok(Number.isNaN(h.stddev));
});
test("percentiles map properties", () => {
const h = createHistogram();
for (let i = 1; i <= 100; i++) {
h.record(i);
}
const percentiles = h.percentiles;
const percentilesBigInt = h.percentilesBigInt;
assert.ok(typeof percentiles.size === "number");
assert.ok(typeof percentiles.has === "function");
assert.ok(typeof percentiles.get === "function");
assert.ok(typeof percentiles[Symbol.iterator] === "function");
assert.ok(typeof percentilesBigInt.size === "number");
assert.ok(typeof percentilesBigInt.has === "function");
assert.ok(typeof percentilesBigInt.get === "function");
assert.ok(typeof percentilesBigInt[Symbol.iterator] === "function");
assert.strictEqual(percentiles.size, percentilesBigInt.size);
for (const [key, value] of percentiles) {
assert.strictEqual(typeof key, "number");
assert.strictEqual(typeof value, "bigint");
assert.ok(percentilesBigInt.has(key));
const bigIntValue = percentilesBigInt.get(key);
assert.strictEqual(typeof bigIntValue, "bigint");
assert.strictEqual(value, bigIntValue);
}
});
test("statistical accuracy", () => {
const h = createHistogram();
for (let i = 1; i <= 1000; i++) {
h.record(i);
}
assert.strictEqual(h.count, 1000);
assert.strictEqual(h.min, 1);
assert.strictEqual(h.max, 1000);
assert.strictEqual(h.mean, 500.5);
assert.ok(Math.abs(h.percentile(50) - 500) <= 1);
assert.ok(Math.abs(h.percentile(90) - 900) <= 10);
assert.ok(Math.abs(h.percentile(99) - 990) <= 10);
});
test("recordDelta timing accuracy", async () => {
const h = createHistogram();
h.recordDelta();
const start = Date.now();
await new Promise(resolve => setTimeout(resolve, 50));
const end = Date.now();
h.recordDelta();
assert.strictEqual(h.count, 1);
const expectedNs = (end - start) * 1000000;
const actualValue = h.min;
assert.ok(actualValue > expectedNs * 0.5);
assert.ok(actualValue < expectedNs * 2);
});
test("toJSON method", () => {
const h = createHistogram();
h.record(10);
h.record(20);
h.record(30);
if (typeof h.toJSON === "function") {
const json = h.toJSON();
assert.strictEqual(typeof json, "object");
assert.strictEqual(json.count, 3);
assert.strictEqual(json.min, 10);
assert.strictEqual(json.max, 30);
assert.strictEqual(json.mean, 20);
assert.strictEqual(json.exceeds, 0);
assert.strictEqual(typeof json.stddev, "number");
assert.strictEqual(typeof json.percentiles, "object");
assert.ok(!json.percentiles.has);
assert.ok(typeof json.percentiles === "object");
} else {
console.log("toJSON method not implemented yet - skipping test");
}
});
test("extreme value handling", () => {
const h = createHistogram();
h.record(1);
assert.strictEqual(h.min, 1);
assert.strictEqual(h.max, 1);
assert.strictEqual(h.count, 1);
const largeValue = Number.MAX_SAFE_INTEGER;
h.record(largeValue);
assert.strictEqual(h.min, 1);
assert.strictEqual(h.max, largeValue);
assert.strictEqual(h.count, 2);
});
test("concurrent operations", () => {
const h = createHistogram();
for (let i = 0; i < 100; i++) {
h.record(i + 1);
if (i % 10 === 0) {
const count = h.count;
const min = h.min;
const max = h.max;
assert.ok(count > 0);
assert.ok(min >= 1);
assert.ok(max >= min);
}
}
assert.strictEqual(h.count, 100);
assert.strictEqual(h.min, 1);
assert.strictEqual(h.max, 100);
});
});
test("inspect output", () => {
const h = createHistogram();
h.record(1);
const inspected = inspect(h);
assert.ok(inspected.includes("Histogram"));
});
});