flexi-bench 0.0.0-alpha.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +373 -0
  3. package/dist/api-types.d.ts +28 -7
  4. package/dist/api-types.js +35 -0
  5. package/dist/benchmark-runner.d.ts +79 -0
  6. package/dist/benchmark-runner.js +172 -0
  7. package/dist/benchmark.d.ts +13 -12
  8. package/dist/benchmark.js +188 -120
  9. package/dist/index.d.ts +6 -1
  10. package/dist/index.js +6 -1
  11. package/dist/performance-observer.d.ts +12 -0
  12. package/dist/performance-observer.js +47 -0
  13. package/dist/{benchmark-console-reporter.d.ts → reporters/benchmark-console-reporter.d.ts} +5 -3
  14. package/dist/reporters/benchmark-console-reporter.js +29 -0
  15. package/dist/reporters/markdown-benchmark-reporter.d.ts +12 -0
  16. package/dist/reporters/markdown-benchmark-reporter.js +29 -0
  17. package/dist/reporters/noop-reporter.d.ts +4 -0
  18. package/dist/reporters/noop-reporter.js +7 -0
  19. package/dist/{suite-console-reporter.d.ts → reporters/suite-console-reporter.d.ts} +2 -1
  20. package/dist/reporters/suite-console-reporter.js +16 -0
  21. package/dist/results.d.ts +42 -0
  22. package/dist/results.js +26 -0
  23. package/dist/shared-api.d.ts +15 -0
  24. package/dist/shared-api.js +36 -0
  25. package/dist/suite.d.ts +18 -4
  26. package/dist/suite.js +51 -23
  27. package/dist/utils.d.ts +1 -0
  28. package/dist/utils.js +14 -0
  29. package/dist/variation.d.ts +22 -8
  30. package/dist/variation.js +39 -15
  31. package/package.json +27 -10
  32. package/dist/benchmark-console-reporter.js +0 -31
  33. package/dist/console-reporter.d.ts +0 -8
  34. package/dist/console-reporter.js +0 -31
  35. package/dist/suite-console-reporter.js +0 -15
package/CHANGELOG.md CHANGED
@@ -1,3 +1,42 @@
1
+ ## 0.1.0 (2024-07-28)
2
+
3
+
4
+ ### 🚀 Features
5
+
6
+ - add different error strategies for handling failing actions ([#11](https://github.com/AgentEnder/flexi-bench/pull/11))
7
+ - **repo:** add ability to publish only docs ([4cde432](https://github.com/AgentEnder/flexi-bench/commit/4cde432))
8
+
9
+ ### ❤️ Thank You
10
+
11
+ - Craigory Coppola @AgentEnder
12
+
13
+ # 0.0.0 (2024-07-28)
14
+
15
+
16
+ ### 🚀 Features
17
+
18
+ - add different error strategies for handling failing actions ([#11](https://github.com/AgentEnder/flexi-bench/pull/11))
19
+ - **repo:** add ability to publish only docs ([4cde432](https://github.com/AgentEnder/flexi-bench/commit/4cde432))
20
+
21
+ ### ❤️ Thank You
22
+
23
+ - Craigory Coppola @AgentEnder
24
+
25
+ ## 0.0.0-alpha.4 (2024-07-17)
26
+
27
+
28
+ ### 🚀 Features
29
+
30
+ - add suites ([caaa5ed](https://github.com/AgentEnder/flexi-bench/commit/caaa5ed))
31
+ - add syntax to run commands ([#6](https://github.com/AgentEnder/flexi-bench/pull/6))
32
+ - add performance-observer API draft ([#7](https://github.com/AgentEnder/flexi-bench/pull/7))
33
+ - initial draft of runner API ([#8](https://github.com/AgentEnder/flexi-bench/pull/8))
34
+ - docs-site ([#9](https://github.com/AgentEnder/flexi-bench/pull/9))
35
+
36
+ ### ❤️ Thank You
37
+
38
+ - Craigory Coppola @AgentEnder
39
+
1
40
  ## 0.0.0-alpha.3 (2024-07-11)
2
41
 
3
42
  This was a version bump only, there were no code changes.
package/README.md CHANGED
@@ -0,0 +1,373 @@
1
+ # FlexiBench
2
+
3
+ FlexiBench is a flexible benchmarking library for JavaScript and TypeScript. It is designed to be simple to use, but also powerful and flexible. It is inspired by common testing framework APIs, and aims to provide a similar experience for benchmarking.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install --save-dev flexi-bench
9
+ ```
10
+
11
+ ```bash
12
+ yarn add --dev flexi-bench
13
+ ```
14
+
15
+ ```bash
16
+ pnpm add --save-dev flexi-bench
17
+ ```
18
+
19
+ ## Features
20
+
21
+ - [Variations](#variations): Run the same benchmark with different configurations.
22
+ - [Setup and Teardown](#setup-and-teardown): Run setup and teardown code before and after the benchmark.
23
+ - [Commands](#commands): Run simple commands as benchmarks.
24
+
25
+ ## Usage
26
+
27
+ ### Runner API
28
+
29
+ The runner API is the simplest way to run benchmarks. It allows running a single benchmark or a suite of benchmarks.
30
+
31
+ ```javascript
32
+ const { suite, benchmark, setup, teardown } = require('flexi-bench');
33
+
34
+ // A `suite` call at the top level will be evaluated as a whole suite.
35
+ suite('My Suite', () => {
36
+ // Nested `benchmark` calls will not be evaluated until the parent `suite` is evaluated.
37
+ benchmark('My Benchmark', (b) => {
38
+ setup(() => {
39
+ // some setup to run before the entire benchmark
40
+ });
41
+
42
+ teardown(() => {
43
+ // some teardown to run after the entire benchmark
44
+ });
45
+
46
+ b.withAction(() => {
47
+ // some action to benchmark
48
+ });
49
+ });
50
+ });
51
+
52
+ // Top-level `benchmark` calls will be evaluated immediately.
53
+ benchmark('My Benchmark', (b) => {
54
+ b.withIterations(10).withAction(() => {
55
+ // some action to benchmark
56
+ });
57
+ });
58
+ ```
59
+
60
+ Within the callbacks for each `suite` or `benchmark` you can utilize the builder API for full customization of the benchmark. Variations can either be added directly via the builder API, or by nesting a `variation` call within the `benchmark` call, or at the `suite` level to apply the variation to all benchmarks in the suite.
61
+
62
+ ```javascript
63
+ const { suite, benchmark, variation } = require('flexi-bench');
64
+
65
+ suite('My Suite', () => {
66
+ benchmark('My Benchmark', (b) => {
67
+ b.withIterations(10).withAction(() => {
68
+ // some action to benchmark
69
+ });
70
+
71
+ variation('with NO_DAEMON', (v) =>
72
+ v.withEnvironmentVariable('NO_DAEMON', 'true'),
73
+ );
74
+ });
75
+ });
76
+ ```
77
+
78
+ ### Basic Benchmarks
79
+
80
+ More detailed documentation will come soon. For now, here is a simple example:
81
+
82
+ ```javascript
83
+ const { Benchmark } = require('flexi-bench');
84
+
85
+ const benchmark = new Benchmark('My Benchmark', {
86
+ iterations: 10,
87
+ action: () => {
88
+ // some action to benchmark
89
+ },
90
+ });
91
+
92
+ await benchmark.run();
93
+ ```
94
+
95
+ Most options for the benchmark can also be provided by a builder API:
96
+
97
+ ```javascript
98
+ const { Benchmark } = require('flexi-bench');
99
+
100
+ const benchmark = new Benchmark('My Benchmark')
101
+ .withIterations(10)
102
+ .withSetup(() => {
103
+ // some setup to run before the entire benchmark
104
+ })
105
+ .withAction(() => {
106
+ // some action to benchmark
107
+ });
108
+ ```
109
+
110
+ #### Setup and Teardown
111
+
112
+ Some benchmarks will require some work before the benchmark to setup the environment, and some work after the benchmark to clean up. This can be done with the `setup`/`setupEach` and `teardown`/`teardownEach` methods:
113
+
114
+ ```javascript
115
+ benchmark('My Benchmark', (b) => {
116
+ setup(() => {
117
+ // some setup to run before the entire benchmark
118
+ });
119
+
120
+ setupEach(() => {
121
+ // some setup that is ran before each iteration of the benchmark
122
+ });
123
+
124
+ teardown(() => {
125
+ // some teardown to run after the entire benchmark
126
+ });
127
+
128
+ teardownEach(() => {
129
+ // some teardown that is ran after each iteration of the benchmark
130
+ });
131
+ });
132
+ ```
133
+
134
+ These can also be set using the builder API:
135
+
136
+ ```javascript
137
+ const { Benchmark } = require('flexi-bench');
138
+
139
+ const benchmark = new Benchmark('My Benchmark')
140
+ .withIterations(10)
141
+ .withSetup(() => {
142
+ // some setup to run before the entire benchmark
143
+ })
144
+ .withSetupEach(() => {
145
+ // some setup that is ran before each iteration of the benchmark
146
+ })
147
+ .withTeardown(() => {
148
+ // some teardown to run after the entire benchmark
149
+ })
150
+ .withTeardownEach(() => {
151
+ // some teardown that is ran after each iteration of the benchmark
152
+ });
153
+ ```
154
+
155
+ #### Understanding Actions
156
+
157
+ The `action` for a benchmark provides the actual event that is being benchmarked. FlexiBench can currently benchmark 2 types of actions:
158
+
159
+ - Callbacks
160
+ - Commands
161
+
162
+ An `action` can be specified via the `action` property of the `benchmark` or `variation` constructor, or via `.withAction` on the builder API. Actions specified on the running `variation` will override the action specified on the parent `benchmark`.
163
+
164
+ For example, the following code will run the `action` specified on the `variation`, and not the `action` specified on the `benchmark`. This would result in the output `bar` being printed 10 times, instead of `foo`:
165
+
166
+ ```javascript
167
+ const { Benchmark } = require('flexi-bench');
168
+
169
+ const benchmark = new Benchmark('My Benchmark', {
170
+ iterations: 10,
171
+ action: () => {
172
+ console.log('foo');
173
+ },
174
+ }).withVariation('with NO_DAEMON', (v) =>
175
+ v.withAction(() => {
176
+ console.log('bar');
177
+ }),
178
+ );
179
+ ```
180
+
181
+ ##### Callbacks
182
+
183
+ If the process you are benchmarking is available as a JavaScript function, you can pass it directly to the `action` property or execute it within the `action` callback. For example, if you are benchmarking a function that sorts an array, you can do the following:
184
+
185
+ ```javascript
186
+ const { benchmark, setup } = require('flexi-bench');
187
+
188
+ benchmark('My Benchmark', (b) => {
189
+ let array;
190
+
191
+ setup(() => {
192
+ array = Array.from({ length: 1000 }, () => Math.random());
193
+ });
194
+
195
+ b.withIterations(10).withAction(() => {
196
+ array.sort();
197
+ });
198
+ });
199
+ ```
200
+
201
+ ##### Commands
202
+
203
+ If the process you are benchmarking is not available as a JavaScript function, you can run it as a command. This can be done by passing a string to the `action` property. For example, if you are benchmarking the runtime of a cli command, you can do the following:
204
+
205
+ ```javascript
206
+ const { Benchmark } = require('flexi-bench');
207
+
208
+ const benchmark = new Benchmark('My Benchmark', {
209
+ iterations: 10,
210
+ action: 'echo "Hello, World!"',
211
+ });
212
+
213
+ await benchmark.run();
214
+ ```
215
+
216
+ While it would be possible to write an action that runs a command using `child_process` methods, using the syntactic sugar provided by flexi-bench is more convenient and provides a few benefits.
217
+
218
+ Utilizing the syntactic sugar also means that flexi-bench is aware that you are indeed running a command. This sounds obvious, but opens up some neat possibilities since we are running the command from within flexi-bench. For example, we can add variations based on tailoring CLI options which would not be possible if you ran your command directly using `child_process` methods on your own.
219
+
220
+ ```javascript
221
+ const { Benchmark } = require('flexi-bench');
222
+
223
+ const benchmark = new Benchmark('My Benchmark', {
224
+ iterations: 10,
225
+ action: 'echo',
226
+ })
227
+ .withVariation('with argument', (v) => v.withArgument('Hello, Earth!'))
228
+ .withVariation('with argument', (v) => v.withArgument('Hello, Mars!'));
229
+
230
+ await benchmark.run();
231
+ ```
232
+
233
+ When running commands via the syntactic sugar, the command is invoked with a function similar to below:
234
+
235
+ ```typescript
236
+ const child = spawn(action, variation.cliArgs, {
237
+ shell: true,
238
+ windowsHide: true,
239
+ });
240
+ child.on('exit', (code) => {
241
+ if (code === 0) {
242
+ resolve();
243
+ } else {
244
+ reject(`Action failed with code ${code}`);
245
+ }
246
+ });
247
+ ```
248
+
249
+ For more information on the simple command API, see the example: ./examples/simple-command.ts
250
+
251
+ ### Suites
252
+
253
+ A suite is a collection of benchmarks that can be run together:
254
+
255
+ ```javascript
256
+ const { Benchmark, Suite } = require('flexi-bench');
257
+
258
+ const suite = new Suite('My Suite')
259
+ .addBenchmark(
260
+ new Benchmark('My Benchmark 1', {
261
+ iterations: 10,
262
+ action: () => {
263
+ // some action to benchmark
264
+ },
265
+ }),
266
+ )
267
+ .addBenchmark(
268
+ new Benchmark('My Benchmark 2', {
269
+ iterations: 10,
270
+ action: () => {
271
+ // some action to benchmark
272
+ },
273
+ }),
274
+ );
275
+ ```
276
+
277
+ Suites can also be created using the runner API:
278
+
279
+ ```javascript
280
+ const { suite, benchmark } = require('flexi-bench');
281
+
282
+ suite('My Suite', () => {
283
+ benchmark('My Benchmark 1', (b) => {
284
+ b.withIterations(10).withAction(() => {
285
+ // some action to benchmark
286
+ });
287
+ });
288
+
289
+ benchmark('My Benchmark 2', (b) => {
290
+ b.withIterations(10).withAction(() => {
291
+ // some action to benchmark
292
+ });
293
+ });
294
+ });
295
+ ```
296
+
297
+ ### Variations
298
+
299
+ Variations allow running the same benchmark with different configurations:
300
+
301
+ ```javascript
302
+ const { Benchmark, Variation } = require('flexi-bench');
303
+
304
+ const benchmark = new Benchmark('My Benchmark', {
305
+ iterations: 10,
306
+ action: () => {
307
+ // some action to benchmark
308
+ },
309
+ }).withVariation('with NO_DAEMON', (v) =>
310
+ v.withEnvironmentVariable('NO_DAEMON', 'true'),
311
+ );
312
+ ```
313
+
314
+ Variations can do most things that the main benchmark can do, including having their own setup and teardown functions, or even a custom action.
315
+
316
+ Some helper functions are provided on the `Variation` class to make it easier to set up variations:
317
+
318
+ ```javascript
319
+ const { Benchmark, Variation } = require('flexi-bench');
320
+
321
+ const benchmark = new Benchmark('My Benchmark', {
322
+ iterations: 10,
323
+ action: () => {
324
+ // some action to benchmark
325
+ },
326
+ }).withVariations(
327
+ // Adds 4 variations with all possible combinations of the given environment variables
328
+ Variation.FromEnvironmentVariables([
329
+ ['NO_DAEMON', ['true', 'false']],
330
+ ['OTHER_VAR', ['value1', 'value2']],
331
+ ]),
332
+ );
333
+ ```
334
+
335
+ Variations can also be added to suites. Variations added to a suite will be applied to all benchmarks in the suite.
336
+
337
+ For example, the below suite would run each benchmark with 'NO_DAEMON' set to true, and then with 'OTHER_VAR' set to 'value1' for a total of 4 benchmark runs in the suite:
338
+
339
+ ```javascript
340
+ const { Benchmark, Suite, Variation } = require('flexi-bench');
341
+
342
+ const suite = new Suite('My Suite')
343
+ .addBenchmark(
344
+ new Benchmark('My Benchmark 1', {
345
+ iterations: 10,
346
+ action: () => {
347
+ // some action to benchmark
348
+ },
349
+ }),
350
+ )
351
+ .addBenchmark(
352
+ new Benchmark('My Benchmark 2', {
353
+ iterations: 10,
354
+ action: () => {
355
+ // some action to benchmark
356
+ },
357
+ }),
358
+ )
359
+ .withVariation('with NO_DAEMON', (v) =>
360
+ v.withEnvironmentVariable('NO_DAEMON', 'true'),
361
+ )
362
+ .withVariation('with OTHER_VAR', (v) =>
363
+ v.withEnvironmentVariable('OTHER_VAR', 'value1'),
364
+ );
365
+ ```
366
+
367
+ ## Examples
368
+
369
+ See examples folder.
370
+
371
+ - ./examples/benchmark.ts is the motivation for this project. It benchmarks the performance of Nx commands with and without a daemon.
372
+ - ./examples/performance-observer.ts is a simple example of how to use the PerformanceObserver API to measure the performance of a function.
373
+ - ./examples/simple-command.ts demonstrates how to benchmark a simple command.
@@ -1,17 +1,13 @@
1
1
  import { Variation } from './variation';
2
2
  import { Benchmark } from './benchmark';
3
+ import { Result } from './results';
3
4
  export type MaybePromise<T> = T | Promise<T>;
4
5
  export type SetupMethod = (variation: Variation) => MaybePromise<void>;
5
6
  export type TeardownMethod = (variation: Variation) => MaybePromise<void>;
6
7
  export type ActionMethod = (variation: Variation) => MaybePromise<void>;
8
+ export type ActionCommand = string;
9
+ export type Action = ActionMethod | ActionCommand;
7
10
  export type EnvironmentVariableOptions = readonly (readonly [key: string, values: readonly string[]])[] | [key: string, values: string[]][];
8
- export type Result = {
9
- label: string;
10
- min: number;
11
- max: number;
12
- average: number;
13
- p95: number;
14
- };
15
11
  export interface ProgressContext {
16
12
  totalIterations?: number;
17
13
  completedIterations: number;
@@ -25,3 +21,28 @@ export interface BenchmarkReporter {
25
21
  export interface SuiteReporter {
26
22
  report: (results: Record<string, Result[]>) => void;
27
23
  }
24
+ /**
25
+ * The strategy to use when an error occurs during a benchmark run.
26
+ */
27
+ export declare enum ErrorStrategy {
28
+ /**
29
+ * Continue running the benchmark. Errors will be collected and reported at the end. This is the default behavior.
30
+ */
31
+ Continue = "continue",
32
+ /**
33
+ * Abort the benchmark run immediately when an error occurs.
34
+ */
35
+ Abort = "abort",
36
+ /**
37
+ * Delay the error until the end of the benchmark run. This is useful when you want to see all the errors at once
38
+ */
39
+ DelayedThrow = "delayed-throw"
40
+ }
41
+ export declare class AggregateBenchmarkError extends Error {
42
+ results: Result[];
43
+ constructor(results: Result[]);
44
+ }
45
+ export declare class AggregateSuiteError extends Error {
46
+ results: Record<string, Result[]>;
47
+ constructor(results: Record<string, Result[]>);
48
+ }
package/dist/api-types.js CHANGED
@@ -1,2 +1,37 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AggregateSuiteError = exports.AggregateBenchmarkError = exports.ErrorStrategy = void 0;
4
+ /**
5
+ * The strategy to use when an error occurs during a benchmark run.
6
+ */
7
+ var ErrorStrategy;
8
+ (function (ErrorStrategy) {
9
+ /**
10
+ * Continue running the benchmark. Errors will be collected and reported at the end. This is the default behavior.
11
+ */
12
+ ErrorStrategy["Continue"] = "continue";
13
+ /**
14
+ * Abort the benchmark run immediately when an error occurs.
15
+ */
16
+ ErrorStrategy["Abort"] = "abort";
17
+ /**
18
+ * Delay the error until the end of the benchmark run. This is useful when you want to see all the errors at once
19
+ */
20
+ ErrorStrategy["DelayedThrow"] = "delayed-throw";
21
+ })(ErrorStrategy || (exports.ErrorStrategy = ErrorStrategy = {}));
22
+ class AggregateBenchmarkError extends Error {
23
+ results;
24
+ constructor(results) {
25
+ super('[AggregateBenchmarkError]: One or more benchmarks failed. Check the results for more information.');
26
+ this.results = results;
27
+ }
28
+ }
29
+ exports.AggregateBenchmarkError = AggregateBenchmarkError;
30
+ class AggregateSuiteError extends Error {
31
+ results;
32
+ constructor(results) {
33
+ super('[AggregateSuiteError]: One or more benchmarks failed. Check the results for more information.');
34
+ this.results = results;
35
+ }
36
+ }
37
+ exports.AggregateSuiteError = AggregateSuiteError;
@@ -0,0 +1,79 @@
1
+ import { SetupMethod, TeardownMethod } from './api-types';
2
+ import { Benchmark } from './benchmark';
3
+ import { Suite } from './suite';
4
+ import { Variation } from './variation';
5
+ /**
6
+ * Registers a new suite to run.
7
+ * @param name The name of the suite.
8
+ * @param fn Callback to register benchmarks and update the suite.
9
+ * @returns The results of the suite. `Record<string, Result[]>`
10
+ */
11
+ export declare function suite(name: string, fn: (suite: Suite) => Suite | void): Promise<Record<string, import("./results").Result[]>>;
12
+ /**
13
+ * Registers a new benchmark to run. If inside a {@link suite} callback, it will be added to the suite. Otherwise, it will run immediately.
14
+ * @param name The name of the benchmark.
15
+ * @param fn Callback to register variations and update the benchmark.
16
+ * @returns If not inside a suite, the results of the benchmark. `Result[]`. Else, `void`.
17
+ */
18
+ export declare function benchmark(name: string, fn: (benchmark: Pick<Benchmark, Extract<keyof Benchmark, `with${string}`>>) => Benchmark | void): Promise<import("./results").Result[]> | undefined;
19
+ /**
20
+ * Registers a new variation to run. Must be inside a {@link benchmark} or {@link suite} callback.
21
+ * @param name The name of the variation.
22
+ * @param fn A callback to update the variation.
23
+ * @returns `void`
24
+ */
25
+ export declare function variation(name: string, fn: (variation: Variation) => Variation | void): void;
26
+ /**
27
+ * Registers a setup method to run before the benchmark or variation. Ran once per benchmark or variation.
28
+ * @param fn The setup method.
29
+ */
30
+ export declare function setup(fn: SetupMethod): void;
31
+ /**
32
+ * Registers a setup method to run before each iteration of the benchmark or variation.
33
+ * @param fn The setup method.
34
+ */
35
+ export declare function setupEach(fn: SetupMethod): void;
36
+ /**
37
+ * Registers a teardown method to run after the benchmark or variation. Ran once per benchmark or variation.
38
+ * @param fn The teardown method.
39
+ */
40
+ export declare function teardown(fn: TeardownMethod): void;
41
+ /**
42
+ * Registers a teardown method to run after each iteration of the benchmark or variation.
43
+ * @param fn The teardown method.
44
+ */
45
+ export declare function teardownEach(fn: TeardownMethod): void;
46
+ /**
47
+ * Alias for `setup`.
48
+ */
49
+ export declare const beforeAll: typeof setup;
50
+ /**
51
+ * Alias for `setupEach`.
52
+ */
53
+ export declare const beforeEach: typeof setupEach;
54
+ /**
55
+ * Alias for `teardown`.
56
+ */
57
+ export declare const afterAll: typeof teardown;
58
+ /**
59
+ * Alias for `teardownEach`.
60
+ */
61
+ export declare const afterEach: typeof teardownEach;
62
+ /**
63
+ * Alias for `suite`.
64
+ */
65
+ export declare const describe: typeof suite;
66
+ /**
67
+ * Alias for `benchmark`.
68
+ */
69
+ export declare const test: typeof benchmark;
70
+ /**
71
+ * Alias for `benchmark`.
72
+ */
73
+ export declare const it: typeof benchmark;
74
+ export declare const xsuite: typeof suite;
75
+ export declare const xdescribe: typeof suite;
76
+ export declare const xbenchmark: typeof benchmark;
77
+ export declare const xtest: typeof benchmark;
78
+ export declare const xit: typeof benchmark;
79
+ export declare const xvariation: typeof variation;