mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
586 lines
16 KiB
TypeScript
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"));
|
|
});
|
|
});
|