Skip to content

Commit 5c4b514

Browse files
committed
feat(benchmark.js-plugin): support deffered benchmarks
1 parent 927bb75 commit 5c4b514

File tree

3 files changed

+178
-64
lines changed

3 files changed

+178
-64
lines changed
Lines changed: 148 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,79 @@
1-
import { initCore, measurement, optimizeFunctionSync } from "@codspeed/core";
1+
import {
2+
initCore,
3+
measurement,
4+
optimizeFunction,
5+
optimizeFunctionSync,
6+
} from "@codspeed/core";
27
import Benchmark from "benchmark";
3-
import { findUpSync, Options } from "find-up";
8+
import { findUpSync, Options as FindupOptions } from "find-up";
49
import path, { dirname } from "path";
510
import { get as getStackTrace } from "stack-trace";
611

712
declare const __VERSION__: string;
813

9-
export function withCodSpeed(suite: Benchmark): Benchmark;
10-
export function withCodSpeed(suite: Benchmark.Suite): Benchmark.Suite;
14+
interface WithCodSpeedBenchmark
15+
extends Omit<
16+
Benchmark,
17+
"run" | "abort" | "clone" | "compare" | "emit" | "off" | "on" | "reset"
18+
> {
19+
abort(): WithCodSpeedBenchmark;
20+
clone(options: Benchmark.Options): WithCodSpeedBenchmark;
21+
compare(benchmark: Benchmark): number;
22+
off(
23+
type?: string,
24+
listener?: CallableFunction
25+
): Benchmark | Promise<Benchmark>;
26+
off(types: string[]): WithCodSpeedBenchmark;
27+
on(type?: string, listener?: CallableFunction): WithCodSpeedBenchmark;
28+
on(types: string[]): WithCodSpeedBenchmark;
29+
reset(): WithCodSpeedBenchmark;
30+
// Makes run an async function
31+
run(options?: Benchmark.Options): Benchmark | Promise<Benchmark>;
32+
}
33+
34+
interface WithCodSpeedSuite
35+
extends Omit<
36+
Benchmark.Suite,
37+
| "run"
38+
| "abort"
39+
| "clone"
40+
| "compare"
41+
| "emit"
42+
| "off"
43+
| "on"
44+
| "reset"
45+
| "add"
46+
| "filter"
47+
| "each"
48+
| "forEach"
49+
> {
50+
abort(): WithCodSpeedSuite;
51+
add(
52+
name: string,
53+
fn: CallableFunction | string,
54+
options?: Benchmark.Options
55+
): WithCodSpeedSuite;
56+
add(
57+
fn: CallableFunction | string,
58+
options?: Benchmark.Options
59+
): WithCodSpeedSuite;
60+
add(name: string, options?: Benchmark.Options): WithCodSpeedSuite;
61+
add(options: Benchmark.Options): WithCodSpeedSuite;
62+
clone(options: Benchmark.Options): WithCodSpeedSuite;
63+
filter(callback: CallableFunction | string): WithCodSpeedSuite;
64+
off(type?: string, callback?: CallableFunction): WithCodSpeedSuite;
65+
off(types: string[]): WithCodSpeedSuite;
66+
on(type?: string, callback?: CallableFunction): WithCodSpeedSuite;
67+
on(types: string[]): WithCodSpeedSuite;
68+
reset(): WithCodSpeedSuite;
69+
each(callback: CallableFunction): WithCodSpeedSuite;
70+
forEach(callback: CallableFunction): WithCodSpeedSuite;
71+
72+
run(options?: Benchmark.Options): Benchmark.Suite | Promise<Benchmark.Suite>;
73+
}
74+
75+
export function withCodSpeed(suite: Benchmark): WithCodSpeedBenchmark;
76+
export function withCodSpeed(suite: Benchmark.Suite): WithCodSpeedSuite;
1177
export function withCodSpeed(item: unknown): unknown {
1278
if ((item as { length?: number }).length === undefined) {
1379
return withCodSpeedBenchmark(item as Benchmark);
@@ -16,7 +82,7 @@ export function withCodSpeed(item: unknown): unknown {
1682
}
1783
}
1884

19-
function withCodSpeedBenchmark(bench: Benchmark): Benchmark {
85+
function withCodSpeedBenchmark(bench: Benchmark): WithCodSpeedBenchmark {
2086
if (!measurement.isInstrumented()) {
2187
const rawRun = bench.run;
2288
bench.run = (options?: Benchmark.Options) => {
@@ -27,27 +93,22 @@ function withCodSpeedBenchmark(bench: Benchmark): Benchmark {
2793
};
2894
return bench;
2995
}
30-
initCore();
3196
const callingFile = getCallingFile();
32-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
33-
bench.run = function (options?: Benchmark.Options): Benchmark {
34-
console.log(
35-
`[CodSpeed] running with @codspeed/benchmark.js v${__VERSION__}`
36-
);
37-
const uri = callingFile + "::" + (bench.name ?? "unknown");
38-
const fn = bench.fn as CallableFunction;
39-
optimizeFunctionSync(fn);
40-
measurement.startInstrumentation();
41-
fn();
42-
measurement.stopInstrumentation(uri);
43-
console.log(` ✔ Measured ${uri}`);
44-
console.log("[CodSpeed] Done running 1 bench.");
97+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/ban-ts-comment
98+
// @ts-ignore
99+
bench.run = async function (options?: Benchmark.Options): Promise<Benchmark> {
100+
await runBenchmarks({
101+
benches: [bench as unknown as BenchmarkWithOptions],
102+
baseUri: callingFile,
103+
benchmarkCompletedListeners: bench.listeners("complete"),
104+
options,
105+
});
45106
return bench;
46107
};
47108
return bench;
48109
}
49110

50-
function withCodSpeedSuite(suite: Benchmark.Suite): Benchmark.Suite {
111+
function withCodSpeedSuite(suite: Benchmark.Suite): WithCodSpeedSuite {
51112
if (!measurement.isInstrumented()) {
52113
const rawRun = suite.run;
53114
suite.run = (options?: Benchmark.Options) => {
@@ -56,35 +117,82 @@ function withCodSpeedSuite(suite: Benchmark.Suite): Benchmark.Suite {
56117
);
57118
return rawRun.bind(suite)(options);
58119
};
59-
return suite;
120+
return suite as WithCodSpeedSuite;
60121
}
61-
initCore();
62122
const callingFile = getCallingFile();
63-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
64-
suite.run = function (options?: Benchmark.Options): Benchmark.Suite {
65-
console.log(
66-
`[CodSpeed] running with @codspeed/benchmark.js v${__VERSION__}`
67-
);
123+
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/ban-ts-comment
124+
// @ts-ignore
125+
suite.run = async function (
126+
options?: Benchmark.Options
127+
): Promise<Benchmark.Suite> {
68128
const suiteName = suite.name;
69-
const benches = this as unknown as Benchmark[];
129+
const benches = this as unknown as BenchmarkWithOptions[];
70130
let baseUri = callingFile;
71131
if (suiteName !== undefined) {
72132
baseUri += `::${suiteName}`;
73133
}
74-
for (let i = 0; i < benches.length; i++) {
75-
const bench = benches[i];
76-
const uri = baseUri + "::" + (bench.name ?? `unknown_${i}`);
77-
const fn = bench.fn as CallableFunction;
78-
optimizeFunctionSync(fn);
134+
await runBenchmarks({
135+
benches,
136+
baseUri,
137+
benchmarkCompletedListeners: suite.listeners("complete"),
138+
options,
139+
});
140+
return suite;
141+
};
142+
return suite as WithCodSpeedSuite;
143+
}
144+
145+
type BenchmarkWithOptions = Benchmark & { options: Benchmark.Options };
146+
147+
interface RunBenchmarksOptions {
148+
benches: BenchmarkWithOptions[];
149+
baseUri: string;
150+
benchmarkCompletedListeners: CallableFunction[];
151+
options?: Benchmark.Options;
152+
}
153+
154+
async function runBenchmarks({
155+
benches,
156+
baseUri,
157+
benchmarkCompletedListeners,
158+
options,
159+
}: RunBenchmarksOptions): Promise<void> {
160+
console.log(`[CodSpeed] running with @codspeed/benchmark.js v${__VERSION__}`);
161+
initCore();
162+
for (let i = 0; i < benches.length; i++) {
163+
const bench = benches[i];
164+
const uri = baseUri + "::" + (bench.name ?? `unknown_${i}`);
165+
const isAsync =
166+
bench.options.async || bench.options.defer || options?.async;
167+
let benchPayload;
168+
if (bench.options.defer) {
169+
benchPayload = () => {
170+
return new Promise((resolve, reject) => {
171+
(bench.fn as CallableFunction)({ resolve, reject });
172+
});
173+
};
174+
} else if (bench.options.async) {
175+
benchPayload = async () => {
176+
await (bench.fn as CallableFunction)();
177+
};
178+
} else {
179+
benchPayload = bench.fn as CallableFunction;
180+
}
181+
if (isAsync) {
182+
await optimizeFunction(benchPayload);
79183
measurement.startInstrumentation();
80-
(bench.fn as CallableFunction)();
184+
await benchPayload();
185+
measurement.stopInstrumentation(uri);
186+
} else {
187+
optimizeFunctionSync(benchPayload);
188+
measurement.startInstrumentation();
189+
benchPayload();
81190
measurement.stopInstrumentation(uri);
82-
console.log(` ✔ Measured ${uri}`);
83191
}
84-
console.log(`[CodSpeed] Done running ${suite.length} benches.`);
85-
return suite;
86-
};
87-
return suite;
192+
console.log(` ✔ Measured ${uri}`);
193+
benchmarkCompletedListeners.forEach((listener) => listener());
194+
}
195+
console.log(`[CodSpeed] Done running ${benches.length} benches.`);
88196
}
89197

90198
function getCallingFile(): string {
@@ -101,6 +209,6 @@ function getGitDir(path: string): string | undefined {
101209
const dotGitPath = findUpSync(".git", {
102210
cwd: path,
103211
type: "directory",
104-
} as Options);
212+
} as FindupOptions);
105213
return dotGitPath ? dirname(dotGitPath) : undefined;
106214
}

packages/benchmark.js-plugin/tests/__snapshots__/unit.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ exports[`Benchmark check console output(instrumented=true) 1`] = `
2121
" ✔ Measured packages/benchmark.js-plugin/tests/unit.test.ts::RegExpSingle",
2222
],
2323
[
24-
"[CodSpeed] Done running 1 bench.",
24+
"[CodSpeed] Done running 1 benches.",
2525
],
2626
],
2727
"warn": [],

packages/benchmark.js-plugin/tests/unit.test.ts

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const benchOptions: Benchmark.Options = {
2020
};
2121

2222
describe("Benchmark", () => {
23-
it("simple benchmark", () => {
23+
it("simple benchmark", async () => {
2424
mockCore.isInstrumented.mockReturnValue(false);
2525
const bench = withCodSpeed(
2626
new Benchmark(
@@ -33,22 +33,27 @@ describe("Benchmark", () => {
3333
);
3434
const onComplete = jest.fn();
3535
bench.on("complete", onComplete);
36-
bench.run();
36+
await bench.run();
3737
expect(onComplete).toHaveBeenCalled();
3838
expect(mockCore.startInstrumentation).not.toHaveBeenCalled();
3939
expect(mockCore.stopInstrumentation).not.toHaveBeenCalled();
4040
});
41-
it("check core methods are called", () => {
41+
it("check core methods are called", async () => {
4242
mockCore.isInstrumented.mockReturnValue(true);
43-
withCodSpeed(
43+
44+
const bench = withCodSpeed(
4445
new Benchmark(
4546
"RegExpSingle",
4647
function () {
4748
/o/.test("Hello World!");
4849
},
4950
benchOptions
5051
)
51-
).run();
52+
);
53+
const onComplete = jest.fn();
54+
bench.on("complete", onComplete);
55+
await bench.run();
56+
expect(onComplete).toHaveBeenCalled();
5257
expect(mockCore.startInstrumentation).toHaveBeenCalled();
5358
expect(mockCore.stopInstrumentation).toHaveBeenCalledWith(
5459
"packages/benchmark.js-plugin/tests/unit.test.ts::RegExpSingle"
@@ -65,15 +70,15 @@ describe("Benchmark", () => {
6570
benchOptions
6671
)
6772
);
68-
expect(() => bench.run()).toThrowError("test");
73+
await expect(bench.run()).rejects.toThrowError("test");
6974
});
7075
it.each([true, false])(
7176
"check console output(instrumented=%p) ",
7277
async (instrumented) => {
7378
const logSpy = jest.spyOn(console, "log");
7479
const warnSpy = jest.spyOn(console, "warn");
7580
mockCore.isInstrumented.mockReturnValue(instrumented);
76-
withCodSpeed(
81+
await withCodSpeed(
7782
new Benchmark(
7883
"RegExpSingle",
7984
function () {
@@ -91,7 +96,7 @@ describe("Benchmark", () => {
9196
});
9297

9398
describe("Benchmark.Suite", () => {
94-
it("simple suite", () => {
99+
it("simple suite", async () => {
95100
mockCore.isInstrumented.mockReturnValue(false);
96101
const suite = withCodSpeed(new Benchmark.Suite());
97102
suite.add(
@@ -103,30 +108,31 @@ describe("Benchmark.Suite", () => {
103108
);
104109
const onComplete = jest.fn();
105110
suite.on("complete", onComplete);
106-
suite.run({ maxTime: 0.1, initCount: 1 });
111+
await suite.run({ maxTime: 0.1, initCount: 1 });
107112
expect(onComplete).toHaveBeenCalled();
108113
expect(mockCore.startInstrumentation).not.toHaveBeenCalled();
109114
expect(mockCore.stopInstrumentation).not.toHaveBeenCalled();
110115
});
111-
it("check core methods are called", () => {
116+
it("check core methods are called", async () => {
112117
mockCore.isInstrumented.mockReturnValue(true);
113-
withCodSpeed(new Benchmark.Suite())
114-
.add(
115-
"RegExp",
116-
function () {
117-
/o/.test("Hello World!");
118-
},
119-
benchOptions
120-
)
121-
.run();
118+
const suite = withCodSpeed(new Benchmark.Suite()).add(
119+
"RegExp",
120+
function () {
121+
/o/.test("Hello World!");
122+
},
123+
benchOptions
124+
);
125+
const onComplete = jest.fn();
126+
suite.on("complete", onComplete);
127+
await suite.run({ maxTime: 0.1, initCount: 1 });
122128
expect(mockCore.startInstrumentation).toHaveBeenCalled();
123129
expect(mockCore.stopInstrumentation).toHaveBeenCalledWith(
124130
"packages/benchmark.js-plugin/tests/unit.test.ts::RegExp"
125131
);
126132
});
127-
it("check suite name is in the uri", () => {
133+
it("check suite name is in the uri", async () => {
128134
mockCore.isInstrumented.mockReturnValue(true);
129-
withCodSpeed(new Benchmark.Suite("thesuite"))
135+
await withCodSpeed(new Benchmark.Suite("thesuite"))
130136
.add(
131137
"RegExp",
132138
function () {
@@ -153,15 +159,15 @@ describe("Benchmark.Suite", () => {
153159
throw new Error("test");
154160
}
155161
);
156-
expect(() => bench.run()).toThrowError("test");
162+
await expect(bench.run()).rejects.toThrowError("test");
157163
});
158164
it.each([true, false])(
159165
"check console output(instrumented=%p) ",
160166
async (instrumented) => {
161167
const logSpy = jest.spyOn(console, "log");
162168
const warnSpy = jest.spyOn(console, "warn");
163169
mockCore.isInstrumented.mockReturnValue(instrumented);
164-
withCodSpeed(new Benchmark.Suite("thesuite"))
170+
await withCodSpeed(new Benchmark.Suite("thesuite"))
165171
.add(
166172
"RegExp",
167173
function () {

0 commit comments

Comments
 (0)