Skip to content

Commit 1242e07

Browse files
feat(tinybench-plugin): add support for perf profiling
Support is still far from perfect for async/heavy code.
1 parent 8dd7a92 commit 1242e07

File tree

4 files changed

+114
-22
lines changed

4 files changed

+114
-22
lines changed

packages/core/src/introspection.ts

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
11
import { writeFileSync } from "fs";
2+
import { getCodspeedRunnerMode } from ".";
23

34
const CUSTOM_INTROSPECTION_EXIT_CODE = 0;
45

56
export const getV8Flags = () => {
67
const nodeVersionMajor = parseInt(process.version.slice(1).split(".")[0]);
8+
const codspeedRunnerMode = getCodspeedRunnerMode();
79

8-
const flags = [
9-
"--hash-seed=1",
10-
"--random-seed=1",
11-
"--no-opt",
12-
"--predictable",
13-
"--predictable-gc-schedule",
14-
"--interpreted-frames-native-stack",
15-
"--allow-natives-syntax",
16-
"--expose-gc",
17-
"--no-concurrent-sweeping",
18-
"--max-old-space-size=4096",
19-
];
20-
if (nodeVersionMajor < 18) {
21-
flags.push("--no-randomize-hashes");
10+
const flags = ["--interpreted-frames-native-stack", "--allow-natives-syntax"];
11+
12+
if (codspeedRunnerMode === "instrumented") {
13+
flags.push(
14+
...[
15+
"--hash-seed=1",
16+
"--random-seed=1",
17+
"--no-opt",
18+
"--predictable",
19+
"--predictable-gc-schedule",
20+
"--expose-gc",
21+
"--no-concurrent-sweeping",
22+
"--max-old-space-size=4096",
23+
]
24+
);
25+
if (nodeVersionMajor < 18) {
26+
flags.push("--no-randomize-hashes");
27+
}
28+
if (nodeVersionMajor < 20) {
29+
flags.push("--no-scavenge-task");
30+
}
2231
}
23-
if (nodeVersionMajor < 20) {
24-
flags.push("--no-scavenge-task");
32+
33+
if (codspeedRunnerMode === "walltime") {
34+
flags.push(...["--perf-prof", "--perf-prof-unwinding-info"]);
2535
}
2636
return flags;
2737
};

packages/tinybench-plugin/benches/sample.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ bench
3535
});
3636

3737
(async () => {
38-
await bench.run();
38+
bench.runSync();
3939
console.table(bench.table());
4040

4141
const timingBench = withCodSpeed(
@@ -44,6 +44,6 @@ bench
4444

4545
registerTimingBenchmarks(timingBench);
4646

47-
await timingBench.run();
47+
timingBench.runSync();
4848
console.table(timingBench.table());
4949
})();

packages/tinybench-plugin/benches/timing.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ const busySleep = (ms: number): void => {
88
};
99

1010
export function registerTimingBenchmarks(bench: Bench) {
11-
bench.add("wait 1ms", async () => {
11+
bench.add("wait 1ms", () => {
1212
busySleep(1);
1313
});
1414

15-
bench.add("wait 500ms", async () => {
15+
bench.add("wait 500ms", () => {
1616
busySleep(500);
1717
});
1818

19-
bench.add("wait 1sec", async () => {
19+
bench.add("wait 1sec", () => {
2020
busySleep(1000);
2121
});
2222
}

packages/tinybench-plugin/src/walltime.ts

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
calculateQuantiles,
3+
InstrumentHooks,
34
mongoMeasurement,
45
msToNs,
56
msToS,
@@ -40,7 +41,11 @@ export function runWalltimeBench(bench: Bench, rootCallingFile: string): void {
4041
await task.warmup();
4142
}
4243
await mongoMeasurement.start(uri);
43-
const taskResult = await task.run();
44+
InstrumentHooks.startBenchmark();
45+
const taskResult = await InstrumentHooks.__codspeed_root_frame__(() =>
46+
task.run()
47+
);
48+
InstrumentHooks.stopBenchmark();
4449
await mongoMeasurement.stop(uri);
4550
results.push(taskResult);
4651

@@ -67,8 +72,85 @@ export function runWalltimeBench(bench: Bench, rootCallingFile: string): void {
6772
};
6873

6974
benchmarks.push(benchmark);
75+
console.log(` ✔ Collected walltime data for ${uri}`);
76+
InstrumentHooks.setExecutedBenchmark(process.pid, uri);
77+
} else {
78+
console.warn(` ⚠ No result data available for ${uri}`);
79+
}
80+
}
81+
82+
// Write results to JSON file using core function
83+
if (benchmarks.length > 0) {
84+
writeWalltimeResults(benchmarks);
85+
}
86+
87+
console.log(
88+
`[CodSpeed] Done collecting walltime data for ${bench.tasks.length} benches.`
89+
);
90+
// Restore our custom run method
91+
bench.run = originalRun;
92+
93+
return results;
94+
};
95+
96+
bench.runSync = () => {
97+
console.log(
98+
`[CodSpeed] running with @codspeed/tinybench v${__VERSION__} (walltime mode)`
99+
);
100+
101+
// Store the original run method before we override it
102+
const originalRun = bench.run;
70103

104+
// Temporarily restore the original run to get actual benchmark results
105+
const benchProto = Object.getPrototypeOf(bench);
106+
const prototypeRun = benchProto.run;
107+
bench.run = prototypeRun;
108+
109+
const benchmarks: Benchmark[] = [];
110+
111+
// Run the bench naturally to collect TaskResult data
112+
const results = [];
113+
114+
// Collect and report walltime data
115+
for (const task of bench.tasks) {
116+
const uri = getTaskUri(bench, task.name, rootCallingFile);
117+
118+
// run the warmup of the task right before its actual run
119+
if (bench.opts.warmup) {
120+
task.warmup();
121+
}
122+
// await mongoMeasurement.start(uri);
123+
InstrumentHooks.startBenchmark();
124+
InstrumentHooks.__codspeed_root_frame__(() => task.runSync());
125+
InstrumentHooks.stopBenchmark();
126+
// await mongoMeasurement.stop(uri);
127+
results.push(task);
128+
129+
if (task.result) {
130+
// Convert tinybench result to BenchmarkStats format
131+
const stats = convertTinybenchResultToBenchmarkStats(
132+
task.result,
133+
bench.opts.warmup ? bench.opts.warmupIterations ?? 0 : 0
134+
);
135+
136+
const benchmark: Benchmark = {
137+
name: task.name,
138+
uri,
139+
config: {
140+
max_rounds: bench.opts.iterations ?? null,
141+
max_time_ns: bench.opts.time ? msToNs(bench.opts.time) : null,
142+
min_round_time_ns: null, // tinybench does not have an option for this
143+
warmup_time_ns:
144+
bench.opts.warmup && bench.opts.warmupTime
145+
? msToNs(bench.opts.warmupTime)
146+
: null,
147+
},
148+
stats,
149+
};
150+
151+
benchmarks.push(benchmark);
71152
console.log(` ✔ Collected walltime data for ${uri}`);
153+
InstrumentHooks.setExecutedBenchmark(process.pid, uri);
72154
} else {
73155
console.warn(` ⚠ No result data available for ${uri}`);
74156
}

0 commit comments

Comments
 (0)