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.
- package/CHANGELOG.md +39 -0
- package/README.md +373 -0
- package/dist/api-types.d.ts +28 -7
- package/dist/api-types.js +35 -0
- package/dist/benchmark-runner.d.ts +79 -0
- package/dist/benchmark-runner.js +172 -0
- package/dist/benchmark.d.ts +13 -12
- package/dist/benchmark.js +188 -120
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/performance-observer.d.ts +12 -0
- package/dist/performance-observer.js +47 -0
- package/dist/{benchmark-console-reporter.d.ts → reporters/benchmark-console-reporter.d.ts} +5 -3
- package/dist/reporters/benchmark-console-reporter.js +29 -0
- package/dist/reporters/markdown-benchmark-reporter.d.ts +12 -0
- package/dist/reporters/markdown-benchmark-reporter.js +29 -0
- package/dist/reporters/noop-reporter.d.ts +4 -0
- package/dist/reporters/noop-reporter.js +7 -0
- package/dist/{suite-console-reporter.d.ts → reporters/suite-console-reporter.d.ts} +2 -1
- package/dist/reporters/suite-console-reporter.js +16 -0
- package/dist/results.d.ts +42 -0
- package/dist/results.js +26 -0
- package/dist/shared-api.d.ts +15 -0
- package/dist/shared-api.js +36 -0
- package/dist/suite.d.ts +18 -4
- package/dist/suite.js +51 -23
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +14 -0
- package/dist/variation.d.ts +22 -8
- package/dist/variation.js +39 -15
- package/package.json +27 -10
- package/dist/benchmark-console-reporter.js +0 -31
- package/dist/console-reporter.d.ts +0 -8
- package/dist/console-reporter.js +0 -31
- package/dist/suite-console-reporter.js +0 -15
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.xvariation = exports.xit = exports.xtest = exports.xbenchmark = exports.xdescribe = exports.xsuite = exports.it = exports.test = exports.describe = exports.afterEach = exports.afterAll = exports.beforeEach = exports.beforeAll = void 0;
|
|
4
|
+
exports.suite = suite;
|
|
5
|
+
exports.benchmark = benchmark;
|
|
6
|
+
exports.variation = variation;
|
|
7
|
+
exports.setup = setup;
|
|
8
|
+
exports.setupEach = setupEach;
|
|
9
|
+
exports.teardown = teardown;
|
|
10
|
+
exports.teardownEach = teardownEach;
|
|
11
|
+
const benchmark_1 = require("./benchmark");
|
|
12
|
+
const suite_1 = require("./suite");
|
|
13
|
+
const variation_1 = require("./variation");
|
|
14
|
+
let activeSuite = null;
|
|
15
|
+
/**
|
|
16
|
+
* Registers a new suite to run.
|
|
17
|
+
* @param name The name of the suite.
|
|
18
|
+
* @param fn Callback to register benchmarks and update the suite.
|
|
19
|
+
* @returns The results of the suite. `Record<string, Result[]>`
|
|
20
|
+
*/
|
|
21
|
+
function suite(name, fn) {
|
|
22
|
+
const suite = new suite_1.Suite(name);
|
|
23
|
+
activeSuite = suite;
|
|
24
|
+
const transformed = fn(suite);
|
|
25
|
+
activeSuite = null;
|
|
26
|
+
return (transformed ?? suite).run();
|
|
27
|
+
}
|
|
28
|
+
let activeBenchmark = null;
|
|
29
|
+
/**
|
|
30
|
+
* Registers a new benchmark to run. If inside a {@link suite} callback, it will be added to the suite. Otherwise, it will run immediately.
|
|
31
|
+
* @param name The name of the benchmark.
|
|
32
|
+
* @param fn Callback to register variations and update the benchmark.
|
|
33
|
+
* @returns If not inside a suite, the results of the benchmark. `Result[]`. Else, `void`.
|
|
34
|
+
*/
|
|
35
|
+
function benchmark(name, fn) {
|
|
36
|
+
const benchmark = new benchmark_1.Benchmark(name);
|
|
37
|
+
activeBenchmark = benchmark;
|
|
38
|
+
const transformed = fn(benchmark);
|
|
39
|
+
activeBenchmark = null;
|
|
40
|
+
if (activeSuite) {
|
|
41
|
+
activeSuite.addBenchmark(transformed ?? benchmark);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
return (transformed ?? benchmark).run();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
let activeVariation = null;
|
|
48
|
+
/**
|
|
49
|
+
* Registers a new variation to run. Must be inside a {@link benchmark} or {@link suite} callback.
|
|
50
|
+
* @param name The name of the variation.
|
|
51
|
+
* @param fn A callback to update the variation.
|
|
52
|
+
* @returns `void`
|
|
53
|
+
*/
|
|
54
|
+
function variation(name, fn) {
|
|
55
|
+
const variation = new variation_1.Variation(name);
|
|
56
|
+
activeVariation = variation;
|
|
57
|
+
const transformed = fn(variation);
|
|
58
|
+
activeVariation = null;
|
|
59
|
+
if (activeBenchmark) {
|
|
60
|
+
activeBenchmark.withVariation(transformed ?? variation);
|
|
61
|
+
}
|
|
62
|
+
else if (activeSuite) {
|
|
63
|
+
activeSuite.withVariation(transformed ?? variation);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
throw new Error('`variation` must be called within a benchmark or suite');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Registers a setup method to run before the benchmark or variation. Ran once per benchmark or variation.
|
|
71
|
+
* @param fn The setup method.
|
|
72
|
+
*/
|
|
73
|
+
function setup(fn) {
|
|
74
|
+
if (activeVariation) {
|
|
75
|
+
activeVariation.withSetup(fn);
|
|
76
|
+
}
|
|
77
|
+
else if (activeBenchmark) {
|
|
78
|
+
activeBenchmark.withSetup(fn);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
throw new Error('`beforeAll` must be called within a variation or benchmark');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Registers a setup method to run before each iteration of the benchmark or variation.
|
|
86
|
+
* @param fn The setup method.
|
|
87
|
+
*/
|
|
88
|
+
function setupEach(fn) {
|
|
89
|
+
if (activeVariation) {
|
|
90
|
+
activeVariation.withSetupEach(fn);
|
|
91
|
+
}
|
|
92
|
+
else if (activeBenchmark) {
|
|
93
|
+
activeBenchmark.withSetupEach(fn);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
throw new Error('`beforeEach` must be called within a variation or benchmark');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Registers a teardown method to run after the benchmark or variation. Ran once per benchmark or variation.
|
|
101
|
+
* @param fn The teardown method.
|
|
102
|
+
*/
|
|
103
|
+
function teardown(fn) {
|
|
104
|
+
if (activeVariation) {
|
|
105
|
+
activeVariation.withTeardown(fn);
|
|
106
|
+
}
|
|
107
|
+
else if (activeBenchmark) {
|
|
108
|
+
activeBenchmark.withTeardown(fn);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
throw new Error('`afterAll` must be called within a variation or benchmark');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Registers a teardown method to run after each iteration of the benchmark or variation.
|
|
116
|
+
* @param fn The teardown method.
|
|
117
|
+
*/
|
|
118
|
+
function teardownEach(fn) {
|
|
119
|
+
if (activeVariation) {
|
|
120
|
+
activeVariation.withTeardownEach(fn);
|
|
121
|
+
}
|
|
122
|
+
else if (activeBenchmark) {
|
|
123
|
+
activeBenchmark.withTeardownEach(fn);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
throw new Error('`afterEach` must be called within a variation or benchmark');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// ALIASES
|
|
130
|
+
// We provide some aliases for comfort when users are familiar with other testing tools.
|
|
131
|
+
/**
|
|
132
|
+
* Alias for `setup`.
|
|
133
|
+
*/
|
|
134
|
+
exports.beforeAll = setup;
|
|
135
|
+
/**
|
|
136
|
+
* Alias for `setupEach`.
|
|
137
|
+
*/
|
|
138
|
+
exports.beforeEach = setupEach;
|
|
139
|
+
/**
|
|
140
|
+
* Alias for `teardown`.
|
|
141
|
+
*/
|
|
142
|
+
exports.afterAll = teardown;
|
|
143
|
+
/**
|
|
144
|
+
* Alias for `teardownEach`.
|
|
145
|
+
*/
|
|
146
|
+
exports.afterEach = teardownEach;
|
|
147
|
+
/**
|
|
148
|
+
* Alias for `suite`.
|
|
149
|
+
*/
|
|
150
|
+
exports.describe = suite;
|
|
151
|
+
/**
|
|
152
|
+
* Alias for `benchmark`.
|
|
153
|
+
*/
|
|
154
|
+
exports.test = benchmark;
|
|
155
|
+
/**
|
|
156
|
+
* Alias for `benchmark`.
|
|
157
|
+
*/
|
|
158
|
+
exports.it = benchmark;
|
|
159
|
+
// DISABLED FUNCTIONS
|
|
160
|
+
const xsuite = () => {
|
|
161
|
+
return Promise.resolve({});
|
|
162
|
+
};
|
|
163
|
+
exports.xsuite = xsuite;
|
|
164
|
+
exports.xdescribe = exports.xsuite;
|
|
165
|
+
const xbenchmark = () => {
|
|
166
|
+
return Promise.resolve([]);
|
|
167
|
+
};
|
|
168
|
+
exports.xbenchmark = xbenchmark;
|
|
169
|
+
exports.xtest = exports.xbenchmark;
|
|
170
|
+
exports.xit = exports.xbenchmark;
|
|
171
|
+
const xvariation = () => { };
|
|
172
|
+
exports.xvariation = xvariation;
|
package/dist/benchmark.d.ts
CHANGED
|
@@ -1,33 +1,34 @@
|
|
|
1
1
|
import { Variation } from './variation';
|
|
2
|
-
import { BenchmarkReporter,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
export declare class Benchmark {
|
|
2
|
+
import { TeardownMethod, SetupMethod, Action, BenchmarkReporter, ErrorStrategy } from './api-types';
|
|
3
|
+
import { Result } from './results';
|
|
4
|
+
import { PerformanceObserverOptions } from './performance-observer';
|
|
5
|
+
import { BenchmarkBase } from './shared-api';
|
|
6
|
+
export declare class Benchmark extends BenchmarkBase {
|
|
7
7
|
name: string;
|
|
8
|
-
private setupMethods;
|
|
9
|
-
private teardownMethods;
|
|
10
|
-
private action?;
|
|
11
8
|
variations: Variation[];
|
|
12
9
|
private iterations?;
|
|
13
10
|
private timeout?;
|
|
14
11
|
private reporter;
|
|
12
|
+
private watcher?;
|
|
13
|
+
errorStrategy: ErrorStrategy;
|
|
15
14
|
constructor(name: string, options?: {
|
|
16
15
|
setup?: SetupMethod;
|
|
17
16
|
teardown?: TeardownMethod;
|
|
18
|
-
action?:
|
|
17
|
+
action?: Action;
|
|
19
18
|
iterations?: number;
|
|
20
19
|
timeout?: number;
|
|
21
20
|
reporter?: BenchmarkReporter;
|
|
22
21
|
});
|
|
23
22
|
withVariation(name: string, builder: (variation: Variation) => Variation): this;
|
|
23
|
+
withVariation(variation: Variation): this;
|
|
24
24
|
withVariations(variations: Variation[]): this;
|
|
25
|
-
withSetup(setup:
|
|
26
|
-
withTeardown(teardown:
|
|
27
|
-
withAction(action:
|
|
25
|
+
withSetup(setup: SetupMethod): this;
|
|
26
|
+
withTeardown(teardown: TeardownMethod): this;
|
|
27
|
+
withAction(action: Action): this;
|
|
28
28
|
withIterations(iterations: number): this;
|
|
29
29
|
withTimeout(timeout: number): this;
|
|
30
30
|
withReporter(reporter: BenchmarkReporter): this;
|
|
31
|
+
withPerformanceObserver(options?: PerformanceObserverOptions): this;
|
|
31
32
|
run(): Promise<Result[]>;
|
|
32
33
|
private validate;
|
|
33
34
|
}
|
package/dist/benchmark.js
CHANGED
|
@@ -1,42 +1,52 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
3
|
exports.Benchmark = void 0;
|
|
13
|
-
const benchmark_console_reporter_1 = require("./benchmark-console-reporter");
|
|
4
|
+
const benchmark_console_reporter_1 = require("./reporters/benchmark-console-reporter");
|
|
14
5
|
const variation_1 = require("./variation");
|
|
15
|
-
|
|
6
|
+
const api_types_1 = require("./api-types");
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const results_1 = require("./results");
|
|
9
|
+
const performance_observer_1 = require("./performance-observer");
|
|
10
|
+
const shared_api_1 = require("./shared-api");
|
|
11
|
+
class Benchmark extends shared_api_1.BenchmarkBase {
|
|
12
|
+
name;
|
|
13
|
+
variations = [];
|
|
14
|
+
iterations;
|
|
15
|
+
timeout;
|
|
16
|
+
reporter;
|
|
17
|
+
watcher;
|
|
18
|
+
errorStrategy = api_types_1.ErrorStrategy.Continue;
|
|
16
19
|
constructor(name, options) {
|
|
20
|
+
super();
|
|
17
21
|
this.name = name;
|
|
18
|
-
|
|
19
|
-
this.teardownMethods = [];
|
|
20
|
-
this.variations = [];
|
|
21
|
-
if (options === null || options === void 0 ? void 0 : options.action) {
|
|
22
|
+
if (options?.action) {
|
|
22
23
|
this.action = options.action;
|
|
23
24
|
}
|
|
24
|
-
if (options
|
|
25
|
+
if (options?.setup) {
|
|
25
26
|
this.setupMethods.push(options.setup);
|
|
26
27
|
}
|
|
27
|
-
if (options
|
|
28
|
+
if (options?.teardown) {
|
|
28
29
|
this.teardownMethods.push(options.teardown);
|
|
29
30
|
}
|
|
30
|
-
if (options
|
|
31
|
+
if (options?.iterations) {
|
|
31
32
|
this.iterations = options.iterations;
|
|
32
33
|
}
|
|
33
|
-
if (options
|
|
34
|
+
if (options?.timeout) {
|
|
34
35
|
this.timeout = options.timeout;
|
|
35
36
|
}
|
|
36
|
-
this.reporter =
|
|
37
|
+
this.reporter = options?.reporter || new benchmark_console_reporter_1.BenchmarkConsoleReporter();
|
|
37
38
|
}
|
|
38
|
-
withVariation(
|
|
39
|
-
|
|
39
|
+
withVariation(nameOrVariation, builder) {
|
|
40
|
+
if (nameOrVariation instanceof variation_1.Variation) {
|
|
41
|
+
this.variations.push(nameOrVariation);
|
|
42
|
+
}
|
|
43
|
+
else if (builder) {
|
|
44
|
+
this.variations.push(builder(new variation_1.Variation(nameOrVariation)));
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
throw new Error('`withVariation` must be called with either a Variation or a name and a builder function.');
|
|
49
|
+
}
|
|
40
50
|
return this;
|
|
41
51
|
}
|
|
42
52
|
withVariations(variations) {
|
|
@@ -67,113 +77,139 @@ class Benchmark {
|
|
|
67
77
|
this.reporter = reporter;
|
|
68
78
|
return this;
|
|
69
79
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
withPerformanceObserver(options) {
|
|
81
|
+
this.watcher = new performance_observer_1.PerformanceWatcher(options);
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
async run() {
|
|
85
|
+
this.validate();
|
|
86
|
+
let results = [];
|
|
87
|
+
if (this.variations.length === 0) {
|
|
88
|
+
this.variations.push(new variation_1.Variation('default'));
|
|
89
|
+
}
|
|
90
|
+
const totalIterations = this.iterations
|
|
91
|
+
? this.variations.length * this.iterations
|
|
92
|
+
: undefined;
|
|
93
|
+
const startTime = performance.now();
|
|
94
|
+
let totalCompletedIterations = 0;
|
|
95
|
+
for (let variationIndex = 0; variationIndex < this.variations.length; variationIndex++) {
|
|
96
|
+
const variation = this.variations[variationIndex];
|
|
97
|
+
const iterationResults = [];
|
|
98
|
+
// SETUP
|
|
99
|
+
const oldEnv = { ...process.env };
|
|
100
|
+
process.env = {
|
|
101
|
+
...process.env,
|
|
102
|
+
...variation.environment,
|
|
103
|
+
};
|
|
104
|
+
for (const setup of this.setupMethods) {
|
|
105
|
+
await setup(variation);
|
|
76
106
|
}
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
for (const setup of variation.setupMethods) {
|
|
92
|
-
yield setup(variation);
|
|
93
|
-
}
|
|
94
|
-
// ACT
|
|
95
|
-
const benchmarkThis = this;
|
|
96
|
-
yield new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
97
|
-
let completedIterations = 0;
|
|
98
|
-
let timeout = benchmarkThis.timeout
|
|
99
|
-
? setTimeout(() => {
|
|
100
|
-
running = false;
|
|
101
|
-
if ((benchmarkThis === null || benchmarkThis === void 0 ? void 0 : benchmarkThis.iterations) &&
|
|
102
|
-
completedIterations < benchmarkThis.iterations) {
|
|
103
|
-
reject('Timeout');
|
|
104
|
-
}
|
|
105
|
-
resolve();
|
|
106
|
-
}, benchmarkThis.timeout)
|
|
107
|
-
: null;
|
|
108
|
-
let running = true;
|
|
109
|
-
while (running) {
|
|
110
|
-
const a = performance.now();
|
|
111
|
-
if (variation.action) {
|
|
112
|
-
yield variation.action(variation);
|
|
113
|
-
}
|
|
114
|
-
else if (this.action) {
|
|
115
|
-
yield this.action(variation);
|
|
116
|
-
}
|
|
117
|
-
const b = performance.now();
|
|
118
|
-
const duration = b - a;
|
|
119
|
-
completedIterations++;
|
|
120
|
-
totalCompletedIterations++;
|
|
121
|
-
timings.push(duration);
|
|
122
|
-
if (this.reporter.progress &&
|
|
123
|
-
totalIterations &&
|
|
124
|
-
benchmarkThis.iterations) {
|
|
125
|
-
this.reporter.progress(variation.name, totalCompletedIterations / totalIterations, {
|
|
126
|
-
timeElapsed: performance.now() - startTime,
|
|
127
|
-
totalIterations,
|
|
128
|
-
completedIterations: totalCompletedIterations,
|
|
129
|
-
timeout: benchmarkThis.timeout,
|
|
130
|
-
});
|
|
107
|
+
for (const setup of variation.setupMethods) {
|
|
108
|
+
await setup(variation);
|
|
109
|
+
}
|
|
110
|
+
// ACT
|
|
111
|
+
const benchmarkThis = this;
|
|
112
|
+
await new Promise(async (resolve, reject) => {
|
|
113
|
+
let completedIterations = 0;
|
|
114
|
+
let timeout = benchmarkThis.timeout
|
|
115
|
+
? setTimeout(() => {
|
|
116
|
+
running = false;
|
|
117
|
+
if (benchmarkThis?.iterations &&
|
|
118
|
+
completedIterations < benchmarkThis.iterations) {
|
|
119
|
+
reject('Timeout');
|
|
131
120
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
121
|
+
resolve();
|
|
122
|
+
}, benchmarkThis.timeout)
|
|
123
|
+
: null;
|
|
124
|
+
let running = true;
|
|
125
|
+
while (running) {
|
|
126
|
+
for (const setup of this.setupEachMethods.concat(variation.setupEachMethods)) {
|
|
127
|
+
await setup(variation);
|
|
128
|
+
}
|
|
129
|
+
const result = await runAndMeasureAction(benchmarkThis, variation);
|
|
130
|
+
const errorStrategy = variation.errorStrategy ?? benchmarkThis.errorStrategy;
|
|
131
|
+
if (errorStrategy === api_types_1.ErrorStrategy.Abort &&
|
|
132
|
+
result instanceof Error) {
|
|
133
|
+
reject(result);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
for (const teardown of this.teardownEachMethods.concat(variation.teardownEachMethods)) {
|
|
137
|
+
await teardown(variation);
|
|
138
|
+
}
|
|
139
|
+
completedIterations++;
|
|
140
|
+
totalCompletedIterations++;
|
|
141
|
+
iterationResults.push(result);
|
|
142
|
+
if (this.reporter.progress &&
|
|
143
|
+
totalIterations &&
|
|
144
|
+
benchmarkThis.iterations) {
|
|
145
|
+
this.reporter.progress(variation.name, totalCompletedIterations / totalIterations, {
|
|
146
|
+
timeElapsed: performance.now() - startTime,
|
|
147
|
+
totalIterations,
|
|
148
|
+
completedIterations: totalCompletedIterations,
|
|
149
|
+
timeout: benchmarkThis.timeout,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (benchmarkThis?.iterations &&
|
|
153
|
+
completedIterations >= benchmarkThis.iterations) {
|
|
154
|
+
running = false;
|
|
155
|
+
if (timeout) {
|
|
156
|
+
clearTimeout(timeout);
|
|
139
157
|
}
|
|
158
|
+
resolve();
|
|
140
159
|
}
|
|
141
|
-
}));
|
|
142
|
-
if (this.reporter.progress && !totalIterations) {
|
|
143
|
-
this.reporter.progress(variation.name, variationIndex / this.variations.length, {
|
|
144
|
-
timeElapsed: performance.now() - startTime,
|
|
145
|
-
timeout: this.timeout,
|
|
146
|
-
completedIterations: totalCompletedIterations,
|
|
147
|
-
});
|
|
148
160
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
process.env = oldEnv;
|
|
157
|
-
// REPORT
|
|
158
|
-
const min = Math.min(...timings);
|
|
159
|
-
const max = Math.max(...timings);
|
|
160
|
-
const sum = timings.reduce((a, b) => a + b, 0);
|
|
161
|
-
const average = sum / timings.length;
|
|
162
|
-
const sortedTimings = [...timings].sort((a, b) => a - b);
|
|
163
|
-
const p95 = sortedTimings[Math.floor(sortedTimings.length * 0.95)];
|
|
164
|
-
results.push({
|
|
165
|
-
label: variation.name,
|
|
166
|
-
min,
|
|
167
|
-
max,
|
|
168
|
-
average,
|
|
169
|
-
p95,
|
|
161
|
+
});
|
|
162
|
+
if (this.reporter.progress && !totalIterations) {
|
|
163
|
+
this.reporter.progress(variation.name, variationIndex / this.variations.length, {
|
|
164
|
+
timeElapsed: performance.now() - startTime,
|
|
165
|
+
timeout: this.timeout,
|
|
166
|
+
completedIterations: totalCompletedIterations,
|
|
170
167
|
});
|
|
171
168
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
169
|
+
// TEARDOWN
|
|
170
|
+
for (const teardown of variation.teardownMethods) {
|
|
171
|
+
await teardown(variation);
|
|
172
|
+
}
|
|
173
|
+
for (const teardown of this.teardownMethods) {
|
|
174
|
+
await teardown(variation);
|
|
175
|
+
}
|
|
176
|
+
process.env = oldEnv;
|
|
177
|
+
// REPORT
|
|
178
|
+
const result = (0, results_1.calculateResultsFromDurations)(variation.name, iterationResults);
|
|
179
|
+
// PerformanceObserver needs a chance to flush
|
|
180
|
+
if (this.watcher) {
|
|
181
|
+
const measures = await this.watcher.getMeasures();
|
|
182
|
+
for (const key in measures) {
|
|
183
|
+
result.subresults ??= [];
|
|
184
|
+
result.subresults.push((0, results_1.calculateResultsFromDurations)(key, measures[key]));
|
|
185
|
+
}
|
|
186
|
+
this.watcher.clearMeasures();
|
|
187
|
+
}
|
|
188
|
+
results.push(result);
|
|
189
|
+
}
|
|
190
|
+
this.watcher?.disconnect();
|
|
191
|
+
this.reporter.report(this, results);
|
|
192
|
+
if (this.errorStrategy === api_types_1.ErrorStrategy.DelayedThrow &&
|
|
193
|
+
results.some((r) => r.failed)) {
|
|
194
|
+
throw new api_types_1.AggregateBenchmarkError(results);
|
|
195
|
+
}
|
|
196
|
+
return results;
|
|
175
197
|
}
|
|
176
198
|
validate() {
|
|
199
|
+
if (!this.timeout && !this.iterations) {
|
|
200
|
+
this.iterations = 5;
|
|
201
|
+
}
|
|
202
|
+
const missingActions = [];
|
|
203
|
+
for (const variation of this.variations) {
|
|
204
|
+
let action = variation.action || this.action;
|
|
205
|
+
if (!action) {
|
|
206
|
+
missingActions.push(variation.name);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (typeof action !== 'string' && variation.cliArgs.length > 0) {
|
|
210
|
+
throw new Error(`Cannot use CLI args with a non-command action for ${this.name}:${variation.name}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
177
213
|
if (!this.action) {
|
|
178
214
|
const missingActions = this.variations.filter((v) => !v.action);
|
|
179
215
|
if (missingActions.length > 0) {
|
|
@@ -188,3 +224,35 @@ class Benchmark {
|
|
|
188
224
|
}
|
|
189
225
|
}
|
|
190
226
|
exports.Benchmark = Benchmark;
|
|
227
|
+
async function runAndMeasureAction(benchmark, variation) {
|
|
228
|
+
try {
|
|
229
|
+
const a = performance.now();
|
|
230
|
+
await runAction((variation.action ?? benchmark.action), variation);
|
|
231
|
+
const b = performance.now();
|
|
232
|
+
return b - a;
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
return e instanceof Error ? e : new Error('Unknown error during action.');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async function runAction(action, variation) {
|
|
239
|
+
if (typeof action === 'string') {
|
|
240
|
+
return new Promise((resolve, reject) => {
|
|
241
|
+
const child = (0, child_process_1.spawn)(action, variation.cliArgs, {
|
|
242
|
+
shell: true,
|
|
243
|
+
windowsHide: true,
|
|
244
|
+
});
|
|
245
|
+
child.on('exit', (code) => {
|
|
246
|
+
if (code === 0) {
|
|
247
|
+
resolve();
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
reject(`Action failed with code ${code}`);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
return action(variation);
|
|
257
|
+
}
|
|
258
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
export * from './reporters/benchmark-console-reporter';
|
|
2
|
+
export * from './reporters/markdown-benchmark-reporter';
|
|
3
|
+
export * from './reporters/suite-console-reporter';
|
|
4
|
+
export * from './reporters/noop-reporter';
|
|
1
5
|
export * from './api-types';
|
|
2
6
|
export * from './benchmark';
|
|
3
7
|
export * from './variation';
|
|
4
|
-
export * from './benchmark-console-reporter';
|
|
5
8
|
export * from './suite';
|
|
9
|
+
export * from './performance-observer';
|
|
10
|
+
export * from './benchmark-runner';
|
package/dist/index.js
CHANGED
|
@@ -14,8 +14,13 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./reporters/benchmark-console-reporter"), exports);
|
|
18
|
+
__exportStar(require("./reporters/markdown-benchmark-reporter"), exports);
|
|
19
|
+
__exportStar(require("./reporters/suite-console-reporter"), exports);
|
|
20
|
+
__exportStar(require("./reporters/noop-reporter"), exports);
|
|
17
21
|
__exportStar(require("./api-types"), exports);
|
|
18
22
|
__exportStar(require("./benchmark"), exports);
|
|
19
23
|
__exportStar(require("./variation"), exports);
|
|
20
|
-
__exportStar(require("./benchmark-console-reporter"), exports);
|
|
21
24
|
__exportStar(require("./suite"), exports);
|
|
25
|
+
__exportStar(require("./performance-observer"), exports);
|
|
26
|
+
__exportStar(require("./benchmark-runner"), exports);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class PerformanceWatcher {
|
|
2
|
+
private observer?;
|
|
3
|
+
private measures;
|
|
4
|
+
constructor(opts?: PerformanceObserverOptions);
|
|
5
|
+
getMeasures(): Promise<Record<string, number[]>>;
|
|
6
|
+
clearMeasures(): void;
|
|
7
|
+
disconnect(): void;
|
|
8
|
+
}
|
|
9
|
+
export type PerformanceObserverOptions = {
|
|
10
|
+
measureFilter?: (entry: PerformanceEntry) => boolean;
|
|
11
|
+
label?: Record<string, string> | ((label: string) => string);
|
|
12
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PerformanceWatcher = void 0;
|
|
4
|
+
const node_perf_hooks_1 = require("node:perf_hooks");
|
|
5
|
+
class PerformanceWatcher {
|
|
6
|
+
observer;
|
|
7
|
+
measures = {};
|
|
8
|
+
constructor(opts = {}) {
|
|
9
|
+
this.observer = new node_perf_hooks_1.PerformanceObserver((list) => {
|
|
10
|
+
for (const entry of list.getEntries()) {
|
|
11
|
+
if (entry.entryType === 'measure' &&
|
|
12
|
+
(!opts.measureFilter || opts.measureFilter(entry))) {
|
|
13
|
+
const label = normalizeLabel(entry.name, opts?.label);
|
|
14
|
+
this.measures[label] ??= [];
|
|
15
|
+
this.measures[label].push(entry.duration);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
this.observer.observe({ entryTypes: ['measure'] });
|
|
20
|
+
}
|
|
21
|
+
// This method is async to give the observer time to collect the measures...
|
|
22
|
+
// If the action being ran by the benchmark is synchronous, the observer will
|
|
23
|
+
// not fire until the next tick so we need that to happen before the measures are
|
|
24
|
+
// retrieved. Since this method is async, it must be awaited, which actually gives
|
|
25
|
+
// the observer time to collect the measures.
|
|
26
|
+
async getMeasures() {
|
|
27
|
+
return this.measures;
|
|
28
|
+
}
|
|
29
|
+
clearMeasures() {
|
|
30
|
+
for (const key in this.measures) {
|
|
31
|
+
delete this.measures[key];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
disconnect() {
|
|
35
|
+
this.observer?.disconnect();
|
|
36
|
+
delete this.observer;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.PerformanceWatcher = PerformanceWatcher;
|
|
40
|
+
function normalizeLabel(label, transform) {
|
|
41
|
+
if (transform) {
|
|
42
|
+
return typeof transform === 'function'
|
|
43
|
+
? transform(label)
|
|
44
|
+
: transform[label];
|
|
45
|
+
}
|
|
46
|
+
return label;
|
|
47
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Benchmark } from '
|
|
1
|
+
import { BenchmarkReporter, ProgressContext } from '../api-types';
|
|
2
|
+
import { Benchmark } from '../benchmark';
|
|
3
|
+
import { SingleBar } from 'cli-progress';
|
|
4
|
+
import { Result } from '../results';
|
|
3
5
|
export declare class BenchmarkConsoleReporter implements BenchmarkReporter {
|
|
4
|
-
|
|
6
|
+
bar: SingleBar;
|
|
5
7
|
constructor();
|
|
6
8
|
progress(name: string, percent: number, context: ProgressContext): void;
|
|
7
9
|
report(benchmark: Benchmark, results: Result[]): void;
|