overtake 1.4.0 → 2.0.1
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/README.md +4 -15
- package/build/cli.js +126 -125
- package/build/executor.d.ts +6 -2
- package/build/executor.js +59 -46
- package/build/gc-watcher.js +2 -6
- package/build/index.d.ts +10 -11
- package/build/index.js +153 -155
- package/build/register-hook.d.ts +1 -0
- package/build/register-hook.js +15 -0
- package/build/reporter.d.ts +10 -2
- package/build/reporter.js +176 -214
- package/build/runner.d.ts +1 -1
- package/build/runner.js +128 -119
- package/build/types.d.ts +6 -6
- package/build/types.js +9 -14
- package/build/utils.d.ts +1 -17
- package/build/utils.js +53 -85
- package/build/worker.js +25 -24
- package/package.json +7 -25
- package/src/__tests__/assert-no-closure.ts +135 -0
- package/src/__tests__/benchmark-execute.ts +48 -0
- package/src/cli.ts +137 -142
- package/src/executor.ts +45 -15
- package/src/index.ts +85 -57
- package/src/register-hook.ts +15 -0
- package/src/reporter.ts +26 -18
- package/src/runner.ts +1 -4
- package/src/types.ts +8 -8
- package/src/utils.ts +15 -54
- package/src/worker.ts +5 -2
- package/tsconfig.json +2 -1
- package/build/cli.cjs +0 -179
- package/build/cli.cjs.map +0 -1
- package/build/cli.js.map +0 -1
- package/build/executor.cjs +0 -123
- package/build/executor.cjs.map +0 -1
- package/build/executor.js.map +0 -1
- package/build/gc-watcher.cjs +0 -30
- package/build/gc-watcher.cjs.map +0 -1
- package/build/gc-watcher.js.map +0 -1
- package/build/index.cjs +0 -442
- package/build/index.cjs.map +0 -1
- package/build/index.js.map +0 -1
- package/build/reporter.cjs +0 -311
- package/build/reporter.cjs.map +0 -1
- package/build/reporter.js.map +0 -1
- package/build/runner.cjs +0 -532
- package/build/runner.cjs.map +0 -1
- package/build/runner.js.map +0 -1
- package/build/types.cjs +0 -66
- package/build/types.cjs.map +0 -1
- package/build/types.js.map +0 -1
- package/build/utils.cjs +0 -174
- package/build/utils.cjs.map +0 -1
- package/build/utils.js.map +0 -1
- package/build/worker.cjs +0 -155
- package/build/worker.cjs.map +0 -1
- package/build/worker.js.map +0 -1
- package/src/__tests__/assert-no-closure.js +0 -134
package/README.md
CHANGED
|
@@ -98,8 +98,7 @@ benchmark('data', getData)
|
|
|
98
98
|
|
|
99
99
|
### Importing Local Files
|
|
100
100
|
|
|
101
|
-
- **CLI mode (`npx overtake`)**:
|
|
102
|
-
- **Programmatic mode (`suite.execute`)**: pass `baseUrl: import.meta.url` (the benchmark’s file URL) so relative imports resolve correctly. If you omit it, Overtake falls back to `process.cwd()` and relative imports may fail.
|
|
101
|
+
- **CLI mode (`npx overtake`)**: relative imports resolve from the benchmark file automatically.
|
|
103
102
|
|
|
104
103
|
```typescript
|
|
105
104
|
// CLI usage – relative path is fine
|
|
@@ -109,16 +108,6 @@ benchmark('local', () => 1)
|
|
|
109
108
|
return { helper };
|
|
110
109
|
})
|
|
111
110
|
.measure('use helper', ({ helper }) => helper());
|
|
112
|
-
|
|
113
|
-
// Programmatic usage – provide baseUrl
|
|
114
|
-
const suite = new Benchmark('local');
|
|
115
|
-
suite
|
|
116
|
-
.target('helper', async () => {
|
|
117
|
-
const { helper } = await import('./helpers.js');
|
|
118
|
-
return { helper };
|
|
119
|
-
})
|
|
120
|
-
.measure('use helper', ({ helper }) => helper());
|
|
121
|
-
await suite.execute({ baseUrl: import.meta.url });
|
|
122
111
|
```
|
|
123
112
|
|
|
124
113
|
## Usage
|
|
@@ -285,7 +274,7 @@ npx overtake <pattern> [options]
|
|
|
285
274
|
| Option | Short | Description | Default |
|
|
286
275
|
| -------------------- | ----- | ----------------------------------------------------- | --------- |
|
|
287
276
|
| `--format` | `-f` | Output format (see [Output Formats](#output-formats)) | `simple` |
|
|
288
|
-
| `--report-types` | `-r` | Stats to show (
|
|
277
|
+
| `--report-types` | `-r` | Stats to show, repeat for multiple (`-r ops -r p99`) | `['ops']` |
|
|
289
278
|
| `--workers` | `-w` | Concurrent workers | CPU count |
|
|
290
279
|
| `--min-cycles` | | Minimum measurement iterations | 50 |
|
|
291
280
|
| `--max-cycles` | | Maximum measurement iterations | 1000 |
|
|
@@ -304,7 +293,7 @@ npx overtake <pattern> [options]
|
|
|
304
293
|
npx overtake "**/*.bench.ts" -f table
|
|
305
294
|
|
|
306
295
|
# Show detailed statistics
|
|
307
|
-
npx overtake bench.ts -r ops mean p95 p99
|
|
296
|
+
npx overtake bench.ts -r ops -r mean -r p95 -r p99
|
|
308
297
|
|
|
309
298
|
# Output JSON for CI
|
|
310
299
|
npx overtake bench.ts -f json > results.json
|
|
@@ -400,7 +389,7 @@ Specify with `--report-types` or `reportTypes` option.
|
|
|
400
389
|
**Example:**
|
|
401
390
|
|
|
402
391
|
```bash
|
|
403
|
-
npx overtake bench.ts -r ops mean sd rme p50 p95 p99
|
|
392
|
+
npx overtake bench.ts -r ops -r mean -r sd -r rme -r p50 -r p95 -r p99
|
|
404
393
|
```
|
|
405
394
|
|
|
406
395
|
## Baseline Comparison
|
package/build/cli.js
CHANGED
|
@@ -1,134 +1,135 @@
|
|
|
1
|
-
import { createRequire,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { stat, readFile, writeFile } from 'node:fs/promises';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
import { REPORT_TYPES } from "./types.js";
|
|
1
|
+
import { createRequire, register } from 'node:module';
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { stat, readFile, writeFile, glob } from 'node:fs/promises';
|
|
5
|
+
import { Benchmark, printTableReports, printJSONReports, printSimpleReports, printMarkdownReports, printHistogramReports, printComparisonReports, reportsToBaseline, DEFAULT_REPORT_TYPES, DEFAULT_WORKERS, } from './index.js';
|
|
6
|
+
import { REPORT_TYPES } from './types.js';
|
|
7
|
+
import { resolveHookUrl } from './utils.js';
|
|
8
|
+
register(resolveHookUrl);
|
|
10
9
|
const require = createRequire(import.meta.url);
|
|
11
|
-
const { name,
|
|
10
|
+
const { name, version, description } = require('../package.json');
|
|
12
11
|
const BENCHMARK_URL = Symbol.for('overtake.benchmarkUrl');
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
12
|
+
const FORMATS = ['simple', 'json', 'pjson', 'table', 'markdown', 'histogram'];
|
|
13
|
+
const { values: opts, positionals: patterns } = parseArgs({
|
|
14
|
+
args: process.argv.slice(2),
|
|
15
|
+
allowPositionals: true,
|
|
16
|
+
options: {
|
|
17
|
+
'report-types': { type: 'string', short: 'r', multiple: true },
|
|
18
|
+
workers: { type: 'string', short: 'w' },
|
|
19
|
+
format: { type: 'string', short: 'f' },
|
|
20
|
+
'abs-threshold': { type: 'string' },
|
|
21
|
+
'rel-threshold': { type: 'string' },
|
|
22
|
+
'warmup-cycles': { type: 'string' },
|
|
23
|
+
'max-cycles': { type: 'string' },
|
|
24
|
+
'min-cycles': { type: 'string' },
|
|
25
|
+
'no-gc-observer': { type: 'boolean' },
|
|
26
|
+
progress: { type: 'boolean' },
|
|
27
|
+
'save-baseline': { type: 'string' },
|
|
28
|
+
'compare-baseline': { type: 'string' },
|
|
29
|
+
help: { type: 'boolean', short: 'h' },
|
|
30
|
+
version: { type: 'boolean', short: 'v' },
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
if (opts.version) {
|
|
34
|
+
console.log(version);
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
if (opts.help || patterns.length === 0) {
|
|
38
|
+
console.log(`${name} v${version} - ${description}
|
|
39
|
+
|
|
40
|
+
Usage: overtake [options] <paths...>
|
|
41
|
+
|
|
42
|
+
Options:
|
|
43
|
+
-r, --report-types <type> statistic type, repeat for multiple (-r ops -r p99)
|
|
44
|
+
-w, --workers <n> number of concurrent workers (default: ${DEFAULT_WORKERS})
|
|
45
|
+
-f, --format <format> output format: ${FORMATS.join(', ')} (default: simple)
|
|
46
|
+
--abs-threshold <ns> absolute error threshold in nanoseconds
|
|
47
|
+
--rel-threshold <frac> relative error threshold (0-1)
|
|
48
|
+
--warmup-cycles <n> warmup cycles before measuring
|
|
49
|
+
--max-cycles <n> maximum measurement cycles per feed
|
|
50
|
+
--min-cycles <n> minimum measurement cycles per feed
|
|
51
|
+
--no-gc-observer disable GC overlap detection
|
|
52
|
+
--progress show progress bar
|
|
53
|
+
--save-baseline <file> save results to baseline file
|
|
54
|
+
--compare-baseline <file> compare results against baseline file
|
|
55
|
+
-v, --version show version
|
|
56
|
+
-h, --help show this help`);
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
const reportTypes = opts['report-types']?.length
|
|
60
|
+
? opts['report-types'].filter((t) => REPORT_TYPES.includes(t))
|
|
61
|
+
: DEFAULT_REPORT_TYPES;
|
|
62
|
+
const format = opts.format && FORMATS.includes(opts.format) ? opts.format : 'simple';
|
|
63
|
+
const executeOptions = {
|
|
64
|
+
reportTypes,
|
|
65
|
+
workers: opts.workers ? parseInt(opts.workers) : DEFAULT_WORKERS,
|
|
66
|
+
absThreshold: opts['abs-threshold'] ? parseFloat(opts['abs-threshold']) : undefined,
|
|
67
|
+
relThreshold: opts['rel-threshold'] ? parseFloat(opts['rel-threshold']) : undefined,
|
|
68
|
+
warmupCycles: opts['warmup-cycles'] ? parseInt(opts['warmup-cycles']) : undefined,
|
|
69
|
+
maxCycles: opts['max-cycles'] ? parseInt(opts['max-cycles']) : undefined,
|
|
70
|
+
minCycles: opts['min-cycles'] ? parseInt(opts['min-cycles']) : undefined,
|
|
71
|
+
gcObserver: !opts['no-gc-observer'],
|
|
72
|
+
progress: opts.progress ?? false,
|
|
73
|
+
format,
|
|
74
|
+
};
|
|
75
|
+
let baseline = null;
|
|
76
|
+
if (opts['compare-baseline']) {
|
|
77
|
+
try {
|
|
78
|
+
const content = await readFile(opts['compare-baseline'], 'utf8');
|
|
79
|
+
baseline = JSON.parse(content);
|
|
30
80
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const code = await transpile(content);
|
|
45
|
-
let instance;
|
|
46
|
-
const benchmark = (...args)=>{
|
|
47
|
-
if (instance) {
|
|
48
|
-
throw new Error('Only one benchmark per file is supported');
|
|
49
|
-
}
|
|
50
|
-
instance = Benchmark.create(...args);
|
|
51
|
-
return instance;
|
|
52
|
-
};
|
|
53
|
-
const globals = Object.create(null);
|
|
54
|
-
for (const k of Object.getOwnPropertyNames(globalThis)){
|
|
55
|
-
globals[k] = globalThis[k];
|
|
81
|
+
catch {
|
|
82
|
+
console.error(`Warning: Could not load baseline file: ${opts['compare-baseline']}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const files = new Set((await Promise.all(patterns.map((pattern) => Array.fromAsync(glob(pattern, { cwd: process.cwd() })).catch(() => [])))).flat());
|
|
86
|
+
for (const file of files) {
|
|
87
|
+
const stats = await stat(file).catch(() => false);
|
|
88
|
+
if (stats && stats.isFile()) {
|
|
89
|
+
const identifier = pathToFileURL(file).href;
|
|
90
|
+
let instance;
|
|
91
|
+
globalThis.benchmark = (...args) => {
|
|
92
|
+
if (instance) {
|
|
93
|
+
throw new Error('Only one benchmark per file is supported');
|
|
56
94
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (Module.isBuiltin(specifier)) {
|
|
66
|
-
return import(specifier);
|
|
67
|
-
}
|
|
68
|
-
const baseIdentifier = referencingModule.identifier ?? identifier;
|
|
69
|
-
const resolveFrom = createRequire(fileURLToPath(baseIdentifier));
|
|
70
|
-
const resolved = resolveFrom.resolve(specifier);
|
|
71
|
-
return import(resolved);
|
|
72
|
-
}
|
|
95
|
+
instance = Benchmark.create(...args);
|
|
96
|
+
return instance;
|
|
97
|
+
};
|
|
98
|
+
await import(identifier);
|
|
99
|
+
if (instance) {
|
|
100
|
+
const reports = await instance.execute({
|
|
101
|
+
...executeOptions,
|
|
102
|
+
[BENCHMARK_URL]: identifier,
|
|
73
103
|
});
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const baselineData = reportsToBaseline(reports);
|
|
102
|
-
await writeFile(executeOptions.saveBaseline, JSON.stringify(baselineData, null, 2));
|
|
103
|
-
console.log(`Baseline saved to: ${executeOptions.saveBaseline}`);
|
|
104
|
-
}
|
|
105
|
-
if (baseline) {
|
|
106
|
-
printComparisonReports(reports, baseline);
|
|
107
|
-
} else {
|
|
108
|
-
switch(executeOptions.format){
|
|
109
|
-
case 'json':
|
|
110
|
-
printJSONReports(reports);
|
|
111
|
-
break;
|
|
112
|
-
case 'pjson':
|
|
113
|
-
printJSONReports(reports, 2);
|
|
114
|
-
break;
|
|
115
|
-
case 'table':
|
|
116
|
-
printTableReports(reports);
|
|
117
|
-
break;
|
|
118
|
-
case 'markdown':
|
|
119
|
-
printMarkdownReports(reports);
|
|
120
|
-
break;
|
|
121
|
-
case 'histogram':
|
|
122
|
-
printHistogramReports(reports);
|
|
123
|
-
break;
|
|
124
|
-
default:
|
|
125
|
-
printSimpleReports(reports);
|
|
126
|
-
}
|
|
104
|
+
if (opts['save-baseline']) {
|
|
105
|
+
const baselineData = reportsToBaseline(reports);
|
|
106
|
+
await writeFile(opts['save-baseline'], JSON.stringify(baselineData, null, 2));
|
|
107
|
+
console.log(`Baseline saved to: ${opts['save-baseline']}`);
|
|
108
|
+
}
|
|
109
|
+
if (baseline) {
|
|
110
|
+
printComparisonReports(reports, baseline);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
switch (format) {
|
|
114
|
+
case 'json':
|
|
115
|
+
printJSONReports(reports);
|
|
116
|
+
break;
|
|
117
|
+
case 'pjson':
|
|
118
|
+
printJSONReports(reports, 2);
|
|
119
|
+
break;
|
|
120
|
+
case 'table':
|
|
121
|
+
printTableReports(reports);
|
|
122
|
+
break;
|
|
123
|
+
case 'markdown':
|
|
124
|
+
printMarkdownReports(reports);
|
|
125
|
+
break;
|
|
126
|
+
case 'histogram':
|
|
127
|
+
printHistogramReports(reports);
|
|
128
|
+
break;
|
|
129
|
+
default:
|
|
130
|
+
printSimpleReports(reports);
|
|
127
131
|
}
|
|
128
132
|
}
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
|
-
}
|
|
132
|
-
commander.parse(process.argv);
|
|
133
|
-
|
|
134
|
-
//# sourceMappingURL=cli.js.map
|
|
135
|
+
}
|
package/build/executor.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Report } from './reporter.js';
|
|
2
|
-
import { ExecutorRunOptions, ReportOptions, BenchmarkOptions, ReportTypeList, ProgressCallback } from './types.js';
|
|
2
|
+
import { type ExecutorRunOptions, type ReportOptions, type BenchmarkOptions, type ReportTypeList, type ProgressCallback } from './types.js';
|
|
3
3
|
export type ExecutorReport<R extends ReportTypeList> = Record<R[number], Report> & {
|
|
4
4
|
count: number;
|
|
5
5
|
heapUsedKB: number;
|
|
@@ -12,4 +12,8 @@ export interface ExecutorOptions<R extends ReportTypeList> extends BenchmarkOpti
|
|
|
12
12
|
onProgress?: ProgressCallback;
|
|
13
13
|
progressInterval?: number;
|
|
14
14
|
}
|
|
15
|
-
export
|
|
15
|
+
export interface Executor<TContext, TInput> {
|
|
16
|
+
pushAsync<T>(task: ExecutorRunOptions<TContext, TInput>): Promise<T>;
|
|
17
|
+
kill(): void;
|
|
18
|
+
}
|
|
19
|
+
export declare const createExecutor: <TContext, TInput, R extends ReportTypeList>(options: Required<ExecutorOptions<R>>) => Executor<TContext, TInput>;
|
package/build/executor.js
CHANGED
|
@@ -1,26 +1,52 @@
|
|
|
1
1
|
import { Worker } from 'node:worker_threads';
|
|
2
2
|
import { once } from 'node:events';
|
|
3
|
-
import { queue } from 'async';
|
|
4
3
|
import { pathToFileURL } from 'node:url';
|
|
5
|
-
import { createReport } from
|
|
6
|
-
import { cmp, assertNoClosure } from
|
|
7
|
-
import { Control, CONTROL_SLOTS, COMPLETE_VALUE } from
|
|
4
|
+
import { createReport, computeStats, Report } from './reporter.js';
|
|
5
|
+
import { cmp, assertNoClosure } from './utils.js';
|
|
6
|
+
import { Control, CONTROL_SLOTS, COMPLETE_VALUE, } from './types.js';
|
|
8
7
|
const BENCHMARK_URL = Symbol.for('overtake.benchmarkUrl');
|
|
9
|
-
export const createExecutor = (options)=>{
|
|
8
|
+
export const createExecutor = (options) => {
|
|
10
9
|
const { workers, warmupCycles, maxCycles, minCycles, absThreshold, relThreshold, gcObserver = true, reportTypes, onProgress, progressInterval = 100 } = options;
|
|
11
10
|
const benchmarkUrl = options[BENCHMARK_URL];
|
|
12
11
|
const resolvedBenchmarkUrl = typeof benchmarkUrl === 'string' ? benchmarkUrl : pathToFileURL(process.cwd()).href;
|
|
13
|
-
const
|
|
12
|
+
const pending = [];
|
|
13
|
+
let running = 0;
|
|
14
|
+
const schedule = async (task) => {
|
|
15
|
+
running++;
|
|
16
|
+
try {
|
|
17
|
+
return await runTask(task);
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
running--;
|
|
21
|
+
if (pending.length > 0) {
|
|
22
|
+
const next = pending.shift();
|
|
23
|
+
schedule(next.task).then(next.resolve, next.reject);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const pushAsync = (task) => {
|
|
28
|
+
if (running < workers) {
|
|
29
|
+
return schedule(task);
|
|
30
|
+
}
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
pending.push({ task, resolve: resolve, reject });
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
const runTask = async ({ id, setup, teardown, pre, run, post, data }) => {
|
|
14
36
|
const setupCode = setup?.toString();
|
|
15
37
|
const teardownCode = teardown?.toString();
|
|
16
38
|
const preCode = pre?.toString();
|
|
17
39
|
const runCode = run.toString();
|
|
18
40
|
const postCode = post?.toString();
|
|
19
|
-
if (setupCode)
|
|
20
|
-
|
|
21
|
-
if (
|
|
41
|
+
if (setupCode)
|
|
42
|
+
assertNoClosure(setupCode, 'setup');
|
|
43
|
+
if (teardownCode)
|
|
44
|
+
assertNoClosure(teardownCode, 'teardown');
|
|
45
|
+
if (preCode)
|
|
46
|
+
assertNoClosure(preCode, 'pre');
|
|
22
47
|
assertNoClosure(runCode, 'run');
|
|
23
|
-
if (postCode)
|
|
48
|
+
if (postCode)
|
|
49
|
+
assertNoClosure(postCode, 'post');
|
|
24
50
|
const controlSAB = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * CONTROL_SLOTS);
|
|
25
51
|
const durationsSAB = new SharedArrayBuffer(BigUint64Array.BYTES_PER_ELEMENT * maxCycles);
|
|
26
52
|
const workerFile = new URL('./worker.js', import.meta.url);
|
|
@@ -38,36 +64,36 @@ export const createExecutor = (options)=>{
|
|
|
38
64
|
relThreshold,
|
|
39
65
|
gcObserver,
|
|
40
66
|
controlSAB,
|
|
41
|
-
durationsSAB
|
|
67
|
+
durationsSAB,
|
|
42
68
|
};
|
|
43
69
|
const worker = new Worker(workerFile, {
|
|
44
|
-
workerData
|
|
70
|
+
workerData,
|
|
45
71
|
});
|
|
46
72
|
const control = new Int32Array(controlSAB);
|
|
47
73
|
let progressIntervalId;
|
|
48
74
|
if (onProgress && id) {
|
|
49
|
-
progressIntervalId = setInterval(()=>{
|
|
75
|
+
progressIntervalId = setInterval(() => {
|
|
50
76
|
const progress = control[Control.PROGRESS] / COMPLETE_VALUE;
|
|
51
|
-
onProgress({
|
|
52
|
-
id,
|
|
53
|
-
progress
|
|
54
|
-
});
|
|
77
|
+
onProgress({ id, progress });
|
|
55
78
|
}, progressInterval);
|
|
56
79
|
}
|
|
57
80
|
const WORKER_TIMEOUT_MS = 300_000;
|
|
58
81
|
const exitPromise = once(worker, 'exit');
|
|
59
|
-
const timeoutId = setTimeout(()=>worker.terminate(), WORKER_TIMEOUT_MS);
|
|
82
|
+
const timeoutId = setTimeout(() => worker.terminate(), WORKER_TIMEOUT_MS);
|
|
60
83
|
let workerError;
|
|
61
84
|
try {
|
|
62
85
|
const [exitCode] = await exitPromise;
|
|
63
86
|
clearTimeout(timeoutId);
|
|
64
|
-
if (progressIntervalId)
|
|
87
|
+
if (progressIntervalId)
|
|
88
|
+
clearInterval(progressIntervalId);
|
|
65
89
|
if (exitCode !== 0) {
|
|
66
90
|
workerError = `worker exited with code ${exitCode}`;
|
|
67
91
|
}
|
|
68
|
-
}
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
69
94
|
clearTimeout(timeoutId);
|
|
70
|
-
if (progressIntervalId)
|
|
95
|
+
if (progressIntervalId)
|
|
96
|
+
clearInterval(progressIntervalId);
|
|
71
97
|
workerError = err instanceof Error ? err.message : String(err);
|
|
72
98
|
}
|
|
73
99
|
const count = control[Control.INDEX];
|
|
@@ -77,37 +103,24 @@ export const createExecutor = (options)=>{
|
|
|
77
103
|
let dceWarning = false;
|
|
78
104
|
if (count > 0) {
|
|
79
105
|
let sum = 0n;
|
|
80
|
-
for (const d of durations)
|
|
106
|
+
for (const d of durations)
|
|
107
|
+
sum += d;
|
|
81
108
|
const avgNs = Number(sum / BigInt(count)) / 1000;
|
|
82
109
|
const opsPerSec = avgNs > 0 ? 1_000_000_000 / avgNs : Infinity;
|
|
83
110
|
if (opsPerSec > DCE_THRESHOLD_OPS) {
|
|
84
111
|
dceWarning = true;
|
|
85
112
|
}
|
|
86
113
|
}
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
[
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
],
|
|
95
|
-
[
|
|
96
|
-
'heapUsedKB',
|
|
97
|
-
heapUsedKB
|
|
98
|
-
],
|
|
99
|
-
[
|
|
100
|
-
'dceWarning',
|
|
101
|
-
dceWarning
|
|
102
|
-
],
|
|
103
|
-
[
|
|
104
|
-
'error',
|
|
105
|
-
workerError
|
|
106
|
-
]
|
|
114
|
+
const stats = count > 0 ? computeStats(durations) : undefined;
|
|
115
|
+
const report = reportTypes
|
|
116
|
+
.map((type) => [type, createReport(durations, type, stats)])
|
|
117
|
+
.concat([
|
|
118
|
+
['count', count],
|
|
119
|
+
['heapUsedKB', heapUsedKB],
|
|
120
|
+
['dceWarning', dceWarning],
|
|
121
|
+
['error', workerError],
|
|
107
122
|
]);
|
|
108
123
|
return Object.fromEntries(report);
|
|
109
|
-
}
|
|
110
|
-
return
|
|
124
|
+
};
|
|
125
|
+
return { pushAsync, kill() { } };
|
|
111
126
|
};
|
|
112
|
-
|
|
113
|
-
//# sourceMappingURL=executor.js.map
|
package/build/gc-watcher.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
export class GCWatcher {
|
|
2
|
-
#registry = new FinalizationRegistry(()=>{});
|
|
2
|
+
#registry = new FinalizationRegistry(() => { });
|
|
3
3
|
start() {
|
|
4
4
|
const target = {};
|
|
5
5
|
const ref = new WeakRef(target);
|
|
6
6
|
this.#registry.register(target, null, ref);
|
|
7
|
-
return {
|
|
8
|
-
ref
|
|
9
|
-
};
|
|
7
|
+
return { ref };
|
|
10
8
|
}
|
|
11
9
|
seen(marker) {
|
|
12
10
|
const collected = marker.ref.deref() === undefined;
|
|
@@ -16,5 +14,3 @@ export class GCWatcher {
|
|
|
16
14
|
return collected;
|
|
17
15
|
}
|
|
18
16
|
}
|
|
19
|
-
|
|
20
|
-
//# sourceMappingURL=gc-watcher.js.map
|
package/build/index.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { ExecutorOptions, ExecutorReport } from './executor.js';
|
|
2
|
-
import { MaybePromise, StepFn, SetupFn, TeardownFn, FeedFn, ReportType, ReportTypeList } from './types.js';
|
|
1
|
+
import { type ExecutorOptions, type ExecutorReport } from './executor.js';
|
|
2
|
+
import { type MaybePromise, type StepFn, type SetupFn, type TeardownFn, type FeedFn, type ReportType, type ReportTypeList } from './types.js';
|
|
3
3
|
declare global {
|
|
4
4
|
const benchmark: typeof Benchmark.create;
|
|
5
5
|
}
|
|
6
6
|
export declare const DEFAULT_WORKERS: number;
|
|
7
|
-
export declare const AsyncFunction: Function;
|
|
8
7
|
export interface TargetReport<R extends ReportTypeList> {
|
|
9
8
|
target: string;
|
|
10
9
|
measures: MeasureReport<R>[];
|
|
@@ -20,10 +19,10 @@ export interface FeedReport<R extends ReportTypeList> {
|
|
|
20
19
|
export declare const DEFAULT_REPORT_TYPES: readonly ["ops"];
|
|
21
20
|
export type DefaultReportTypes = (typeof DEFAULT_REPORT_TYPES)[number];
|
|
22
21
|
export declare class MeasureContext<TContext, TInput> {
|
|
23
|
-
title: string;
|
|
24
|
-
run: StepFn<TContext, TInput>;
|
|
25
22
|
pre?: StepFn<TContext, TInput>;
|
|
26
23
|
post?: StepFn<TContext, TInput>;
|
|
24
|
+
title: string;
|
|
25
|
+
run: StepFn<TContext, TInput>;
|
|
27
26
|
constructor(title: string, run: StepFn<TContext, TInput>);
|
|
28
27
|
}
|
|
29
28
|
export declare class Measure<TContext, TInput> {
|
|
@@ -33,11 +32,11 @@ export declare class Measure<TContext, TInput> {
|
|
|
33
32
|
post(fn: StepFn<TContext, TInput>): Measure<TContext, TInput>;
|
|
34
33
|
}
|
|
35
34
|
export declare class TargetContext<TContext, TInput> {
|
|
36
|
-
readonly title: string;
|
|
37
|
-
readonly setup?: SetupFn<MaybePromise<TContext>> | undefined;
|
|
38
35
|
teardown?: TeardownFn<TContext>;
|
|
39
36
|
measures: MeasureContext<TContext, TInput>[];
|
|
40
|
-
|
|
37
|
+
readonly title: string;
|
|
38
|
+
readonly setup?: SetupFn<MaybePromise<TContext>>;
|
|
39
|
+
constructor(title: string, setup?: SetupFn<MaybePromise<TContext>>);
|
|
41
40
|
}
|
|
42
41
|
export declare class Target<TContext, TInput> {
|
|
43
42
|
#private;
|
|
@@ -47,8 +46,8 @@ export declare class Target<TContext, TInput> {
|
|
|
47
46
|
}
|
|
48
47
|
export declare class FeedContext<TInput> {
|
|
49
48
|
readonly title: string;
|
|
50
|
-
readonly fn?: FeedFn<TInput
|
|
51
|
-
constructor(title: string, fn?: FeedFn<TInput>
|
|
49
|
+
readonly fn?: FeedFn<TInput>;
|
|
50
|
+
constructor(title: string, fn?: FeedFn<TInput>);
|
|
52
51
|
}
|
|
53
52
|
export declare class Benchmark<TInput> {
|
|
54
53
|
#private;
|
|
@@ -60,7 +59,7 @@ export declare class Benchmark<TInput> {
|
|
|
60
59
|
feed<I>(title: string, fn: FeedFn<I>): Benchmark<TInput | I>;
|
|
61
60
|
target<TContext>(title: string): Target<void, TInput>;
|
|
62
61
|
target<TContext>(title: string, setup: SetupFn<Awaited<TContext>>): Target<TContext, TInput>;
|
|
63
|
-
execute<R extends readonly ReportType[] = typeof DEFAULT_REPORT_TYPES>(options
|
|
62
|
+
execute<R extends readonly ReportType[] = typeof DEFAULT_REPORT_TYPES>(options?: Partial<ExecutorOptions<R>> & {
|
|
64
63
|
progress?: boolean;
|
|
65
64
|
}): Promise<TargetReport<R>[]>;
|
|
66
65
|
}
|