Initial Commit
This commit is contained in:
@@ -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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user