81 lines
2.6 KiB
JavaScript
81 lines
2.6 KiB
JavaScript
// Stage 1 ELK layout perf benchmark.
|
|
//
|
|
// Mirrors `applyElkLayout` from `src/utils/elk-layout.ts` using `elkjs`
|
|
// directly. The dashboard build is a Vite bundle (hashed chunks), so it has
|
|
// no per-module `dist/utils/elk-layout.js` we can import. The Stage 1 hot
|
|
// path is `elk.layout()` on a sized input, which we reproduce faithfully
|
|
// here — same default node dimensions, same dim-defaulting behavior.
|
|
//
|
|
// Targets (spec §8.3):
|
|
// - Stage 1 < 200ms at 500 nodes
|
|
// - Stage 1 < 500ms at 3000 nodes
|
|
//
|
|
// Usage:
|
|
// node understand-anything-plugin/packages/dashboard/scripts/benchmark-layout.mjs
|
|
|
|
import { performance } from "node:perf_hooks";
|
|
import ELK from "elkjs/lib/elk.bundled.js";
|
|
|
|
// Keep in lockstep with NODE_WIDTH / NODE_HEIGHT in src/utils/layout.ts.
|
|
const DEFAULT_NODE_WIDTH = 280;
|
|
const DEFAULT_NODE_HEIGHT = 120;
|
|
|
|
const elk = new ELK();
|
|
|
|
/**
|
|
* Default missing width/height on every node (mirrors repairElkInput's
|
|
* ensureNodeDimensions step). Stage 1 in prod always feeds ELK sized nodes,
|
|
* but the repair pass is part of the measured path so we model it.
|
|
*/
|
|
function fillDims(children) {
|
|
return children.map((c) => {
|
|
const next = { ...c };
|
|
if (next.width == null) next.width = DEFAULT_NODE_WIDTH;
|
|
if (next.height == null) next.height = DEFAULT_NODE_HEIGHT;
|
|
if (next.children) next.children = fillDims(next.children);
|
|
return next;
|
|
});
|
|
}
|
|
|
|
async function applyElkLayout(input) {
|
|
const repaired = { ...input, children: fillDims(input.children) };
|
|
return elk.layout(repaired);
|
|
}
|
|
|
|
/**
|
|
* Synthetic Stage 1 graph: top-level container nodes with a sparse edge mesh.
|
|
* Stage 1 only lays out containers (lazy children — see plan §3), so the
|
|
* "node count" parameter is interpreted as total leaves while the container
|
|
* count scales sub-linearly, matching production shape.
|
|
*/
|
|
function makeGraph(nodeCount, containerCount = Math.min(20, Math.ceil(nodeCount / 25))) {
|
|
const containers = Array.from({ length: containerCount }, (_, i) => ({
|
|
id: `c${i}`,
|
|
width: 400,
|
|
height: 300,
|
|
}));
|
|
const edges = [];
|
|
for (let i = 0; i < containerCount; i++) {
|
|
for (let j = i + 1; j < containerCount; j++) {
|
|
if (Math.random() < 0.3) {
|
|
edges.push({ id: `e-${i}-${j}`, sources: [`c${i}`], targets: [`c${j}`] });
|
|
}
|
|
}
|
|
}
|
|
return { id: "root", children: containers, edges };
|
|
}
|
|
|
|
async function bench(label, n) {
|
|
const input = makeGraph(n);
|
|
const t0 = performance.now();
|
|
await applyElkLayout(input);
|
|
const t1 = performance.now();
|
|
const ms = t1 - t0;
|
|
console.log(`${label} (${n} nodes): ${ms.toFixed(1)}ms`);
|
|
return ms;
|
|
}
|
|
|
|
await bench("Stage1", 500);
|
|
await bench("Stage1", 1000);
|
|
await bench("Stage1", 3000);
|