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.
Files changed (59) hide show
  1. package/README.md +4 -15
  2. package/build/cli.js +126 -125
  3. package/build/executor.d.ts +6 -2
  4. package/build/executor.js +59 -46
  5. package/build/gc-watcher.js +2 -6
  6. package/build/index.d.ts +10 -11
  7. package/build/index.js +153 -155
  8. package/build/register-hook.d.ts +1 -0
  9. package/build/register-hook.js +15 -0
  10. package/build/reporter.d.ts +10 -2
  11. package/build/reporter.js +176 -214
  12. package/build/runner.d.ts +1 -1
  13. package/build/runner.js +128 -119
  14. package/build/types.d.ts +6 -6
  15. package/build/types.js +9 -14
  16. package/build/utils.d.ts +1 -17
  17. package/build/utils.js +53 -85
  18. package/build/worker.js +25 -24
  19. package/package.json +7 -25
  20. package/src/__tests__/assert-no-closure.ts +135 -0
  21. package/src/__tests__/benchmark-execute.ts +48 -0
  22. package/src/cli.ts +137 -142
  23. package/src/executor.ts +45 -15
  24. package/src/index.ts +85 -57
  25. package/src/register-hook.ts +15 -0
  26. package/src/reporter.ts +26 -18
  27. package/src/runner.ts +1 -4
  28. package/src/types.ts +8 -8
  29. package/src/utils.ts +15 -54
  30. package/src/worker.ts +5 -2
  31. package/tsconfig.json +2 -1
  32. package/build/cli.cjs +0 -179
  33. package/build/cli.cjs.map +0 -1
  34. package/build/cli.js.map +0 -1
  35. package/build/executor.cjs +0 -123
  36. package/build/executor.cjs.map +0 -1
  37. package/build/executor.js.map +0 -1
  38. package/build/gc-watcher.cjs +0 -30
  39. package/build/gc-watcher.cjs.map +0 -1
  40. package/build/gc-watcher.js.map +0 -1
  41. package/build/index.cjs +0 -442
  42. package/build/index.cjs.map +0 -1
  43. package/build/index.js.map +0 -1
  44. package/build/reporter.cjs +0 -311
  45. package/build/reporter.cjs.map +0 -1
  46. package/build/reporter.js.map +0 -1
  47. package/build/runner.cjs +0 -532
  48. package/build/runner.cjs.map +0 -1
  49. package/build/runner.js.map +0 -1
  50. package/build/types.cjs +0 -66
  51. package/build/types.cjs.map +0 -1
  52. package/build/types.js.map +0 -1
  53. package/build/utils.cjs +0 -174
  54. package/build/utils.cjs.map +0 -1
  55. package/build/utils.js.map +0 -1
  56. package/build/worker.cjs +0 -155
  57. package/build/worker.cjs.map +0 -1
  58. package/build/worker.js.map +0 -1
  59. 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`)**: `baseUrl` is set to the benchmark file, so `await import('./helper.js')` works.
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 (see [Metrics](#available-metrics)) | `['ops']` |
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, Module } from 'node:module';
2
- import { fileURLToPath, pathToFileURL } from 'node:url';
3
- import { SyntheticModule, createContext, SourceTextModule } from 'node:vm';
4
- import { stat, readFile, writeFile } from 'node:fs/promises';
5
- import { Command, Option } from 'commander';
6
- import { glob } from 'glob';
7
- import { Benchmark, printTableReports, printJSONReports, printSimpleReports, printMarkdownReports, printHistogramReports, printComparisonReports, reportsToBaseline, DEFAULT_REPORT_TYPES, DEFAULT_WORKERS } from "./index.js";
8
- import { transpile } from "./utils.js";
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, description, version } = require('../package.json');
10
+ const { name, version, description } = require('../package.json');
12
11
  const BENCHMARK_URL = Symbol.for('overtake.benchmarkUrl');
13
- const commander = new Command();
14
- commander.name(name).description(description).version(version).argument('<paths...>', 'glob pattern to find benchmarks').addOption(new Option('-r, --report-types [reportTypes...]', 'statistic types to include in the report').choices(REPORT_TYPES).default(DEFAULT_REPORT_TYPES)).addOption(new Option('-w, --workers [workers]', 'number of concurent workers').default(DEFAULT_WORKERS).argParser(parseInt)).addOption(new Option('-f, --format [format]', 'output format').default('simple').choices([
15
- 'simple',
16
- 'json',
17
- 'pjson',
18
- 'table',
19
- 'markdown',
20
- 'histogram'
21
- ])).addOption(new Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseFloat)).addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseFloat)).addOption(new Option('--warmup-cycles [warmupCycles]', 'number of warmup cycles before measuring').argParser(parseInt)).addOption(new Option('--max-cycles [maxCycles]', 'maximum measurement cycles per feed').argParser(parseInt)).addOption(new Option('--min-cycles [minCycles]', 'minimum measurement cycles per feed').argParser(parseInt)).addOption(new Option('--no-gc-observer', 'disable GC overlap detection')).addOption(new Option('--progress', 'show progress bar during benchmark execution')).addOption(new Option('--save-baseline <file>', 'save benchmark results to baseline file')).addOption(new Option('--compare-baseline <file>', 'compare results against baseline file')).action(async (patterns, executeOptions)=>{
22
- let baseline = null;
23
- if (executeOptions.compareBaseline) {
24
- try {
25
- const content = await readFile(executeOptions.compareBaseline, 'utf8');
26
- baseline = JSON.parse(content);
27
- } catch {
28
- console.error(`Warning: Could not load baseline file: ${executeOptions.compareBaseline}`);
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
- const files = new Set();
32
- await Promise.all(patterns.map(async (pattern)=>{
33
- const matches = await glob(pattern, {
34
- absolute: true,
35
- cwd: process.cwd()
36
- }).catch(()=>[]);
37
- matches.forEach((file)=>files.add(file));
38
- }));
39
- for (const file of files){
40
- const stats = await stat(file).catch(()=>false);
41
- if (stats && stats.isFile()) {
42
- const content = await readFile(file, 'utf8');
43
- const identifier = pathToFileURL(file).href;
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
- globals.benchmark = benchmark;
58
- const script = new SourceTextModule(code, {
59
- identifier,
60
- context: createContext(globals),
61
- initializeImportMeta (meta) {
62
- meta.url = identifier;
63
- },
64
- async importModuleDynamically (specifier, referencingModule) {
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
- const imports = new Map();
75
- await script.link(async (specifier, referencingModule)=>{
76
- const baseIdentifier = referencingModule.identifier ?? identifier;
77
- const resolveFrom = createRequire(fileURLToPath(baseIdentifier));
78
- const target = Module.isBuiltin(specifier) ? specifier : resolveFrom.resolve(specifier);
79
- const cached = imports.get(target);
80
- if (cached) {
81
- return cached;
82
- }
83
- const mod = await import(target);
84
- const exportNames = Object.keys(mod);
85
- const imported = new SyntheticModule(exportNames, ()=>{
86
- exportNames.forEach((key)=>imported.setExport(key, mod[key]));
87
- }, {
88
- identifier: target,
89
- context: referencingModule.context
90
- });
91
- imports.set(target, imported);
92
- return imported;
93
- });
94
- await script.evaluate();
95
- if (instance) {
96
- const reports = await instance.execute({
97
- ...executeOptions,
98
- [BENCHMARK_URL]: identifier
99
- });
100
- if (executeOptions.saveBaseline) {
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
+ }
@@ -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 declare const createExecutor: <TContext, TInput, R extends ReportTypeList>(options: Required<ExecutorOptions<R>>) => import("async").QueueObject<ExecutorRunOptions<TContext, TInput>>;
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 "./reporter.js";
6
- import { cmp, assertNoClosure } from "./utils.js";
7
- import { Control, CONTROL_SLOTS, COMPLETE_VALUE } from "./types.js";
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 executor = queue(async ({ id, setup, teardown, pre, run, post, data })=>{
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) assertNoClosure(setupCode, 'setup');
20
- if (teardownCode) assertNoClosure(teardownCode, 'teardown');
21
- if (preCode) assertNoClosure(preCode, 'pre');
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) assertNoClosure(postCode, 'post');
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) clearInterval(progressIntervalId);
87
+ if (progressIntervalId)
88
+ clearInterval(progressIntervalId);
65
89
  if (exitCode !== 0) {
66
90
  workerError = `worker exited with code ${exitCode}`;
67
91
  }
68
- } catch (err) {
92
+ }
93
+ catch (err) {
69
94
  clearTimeout(timeoutId);
70
- if (progressIntervalId) clearInterval(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)sum += d;
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 report = reportTypes.map((type)=>[
88
- type,
89
- createReport(durations, type)
90
- ]).concat([
91
- [
92
- 'count',
93
- count
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
- }, workers);
110
- return executor;
124
+ };
125
+ return { pushAsync, kill() { } };
111
126
  };
112
-
113
- //# sourceMappingURL=executor.js.map
@@ -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
- constructor(title: string, setup?: SetupFn<MaybePromise<TContext>> | undefined);
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> | undefined;
51
- constructor(title: string, fn?: FeedFn<TInput> | undefined);
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: ExecutorOptions<R> & {
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
  }