Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,119 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import type { ProfileEntry } from "../types/profiler.types.js";
import { FlameGraphGenerator } from "./flamegraph.js";
function entry(label: string, start: number, end: number): ProfileEntry {
return { label, startTime: start, endTime: end, duration: end - start };
}
describe("FlameGraphGenerator", () => {
it("builds a containment tree", { timeout: 5000 }, () => {
const gen = new FlameGraphGenerator();
const tree = gen.generate([
entry("outer", 0, 100),
entry("middle", 10, 90),
entry("inner", 20, 50),
]);
expect(tree.name).toBe("root");
expect(tree.children).toHaveLength(1);
expect(tree.children[0]?.name).toBe("outer");
expect(tree.children[0]?.children[0]?.name).toBe("middle");
expect(tree.children[0]?.children[0]?.children[0]?.name).toBe("inner");
});
it("filters incomplete entries", { timeout: 5000 }, () => {
const gen = new FlameGraphGenerator();
const tree = gen.generate([
entry("ok", 0, 50),
{ label: "pending", startTime: 0 },
]);
expect(tree.children).toHaveLength(1);
});
it("computes selfTime as value minus children", { timeout: 5000 }, () => {
const gen = new FlameGraphGenerator();
const tree = gen.generate([
entry("parent", 0, 100),
entry("child", 10, 30),
]);
const parent = tree.children[0];
expect(parent?.value).toBe(100);
expect(parent?.selfTime).toBe(80);
});
it("entry that contains existing siblings absorbs them", {
timeout: 5000,
}, () => {
const gen = new FlameGraphGenerator();
// Insert child first, then outer that contains it. The sort puts
// outer first because earlier startTime + wider; this still covers
// the entryContainsNode path when an entry shares startTime with a
// previously inserted sibling.
const tree = gen.generate([
entry("sibling-1", 5, 10),
entry("sibling-2", 15, 20),
entry("wide", 0, 50),
]);
const wide = tree.children.find((c) => c.name === "wide");
expect(wide).toBeDefined();
expect(wide?.children.map((c) => c.name).sort()).toEqual([
"sibling-1",
"sibling-2",
]);
});
it("toD3Format emits d3-compatible JSON", { timeout: 5000 }, () => {
const gen = new FlameGraphGenerator();
const tree = gen.generate([entry("only", 0, 100)]);
const d3 = gen.toD3Format(tree);
expect(d3).toEqual({
name: "root",
value: 100,
children: [{ name: "only", value: 100, children: [] }],
});
});
it("collapseRecursive merges same-named parent/child", {
timeout: 5000,
}, () => {
const gen = new FlameGraphGenerator();
const tree = gen.generate([
entry("rec", 0, 100),
entry("rec", 10, 90),
entry("leaf", 20, 50),
]);
const collapsed = gen.collapseRecursive(tree);
// walk tree finding rec
const recNodes: number[] = [];
const walk = (n: typeof collapsed): void => {
if (n.name === "rec") recNodes.push(n.value);
for (const c of n.children) walk(c);
};
walk(collapsed);
expect(recNodes).toHaveLength(1);
});
it("late wide entry contains some siblings but not others", {
timeout: 5000,
}, () => {
const gen = new FlameGraphGenerator();
const tree = gen.generate([
entry("early", 0, 10),
entry("mid", 20, 30),
entry("late-wide", 15, 40),
]);
const names = tree.children.map((c) => c.name).sort();
expect(names).toContain("early");
expect(names).toContain("late-wide");
const late = tree.children.find((c) => c.name === "late-wide");
expect(late?.children.map((c) => c.name)).toEqual(["mid"]);
});
it("ties on startTime break by wider span first", { timeout: 5000 }, () => {
const gen = new FlameGraphGenerator();
const tree = gen.generate([entry("a", 0, 10), entry("b", 0, 50)]);
// b is wider so should appear first; a is contained within b
const b = tree.children.find((c) => c.name === "b");
expect(b?.children.map((c) => c.name)).toContain("a");
});
});