overtake 1.0.4 → 1.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 (56) hide show
  1. package/README.md +68 -25
  2. package/bin/overtake.js +1 -1
  3. package/build/cli.cjs +44 -33
  4. package/build/cli.cjs.map +1 -1
  5. package/build/cli.js +43 -32
  6. package/build/cli.js.map +1 -1
  7. package/build/executor.cjs +6 -3
  8. package/build/executor.cjs.map +1 -1
  9. package/build/executor.d.ts +3 -2
  10. package/build/executor.js +6 -3
  11. package/build/executor.js.map +1 -1
  12. package/build/gc-watcher.cjs +31 -0
  13. package/build/gc-watcher.cjs.map +1 -0
  14. package/build/gc-watcher.d.ts +9 -0
  15. package/build/gc-watcher.js +21 -0
  16. package/build/gc-watcher.js.map +1 -0
  17. package/build/index.cjs +9 -1
  18. package/build/index.cjs.map +1 -1
  19. package/build/index.d.ts +1 -1
  20. package/build/index.js +9 -1
  21. package/build/index.js.map +1 -1
  22. package/build/runner.cjs +226 -18
  23. package/build/runner.cjs.map +1 -1
  24. package/build/runner.d.ts +1 -1
  25. package/build/runner.js +226 -18
  26. package/build/runner.js.map +1 -1
  27. package/build/types.cjs.map +1 -1
  28. package/build/types.d.ts +4 -0
  29. package/build/types.js.map +1 -1
  30. package/build/utils.cjs +21 -0
  31. package/build/utils.cjs.map +1 -1
  32. package/build/utils.d.ts +1 -0
  33. package/build/utils.js +18 -0
  34. package/build/utils.js.map +1 -1
  35. package/build/worker.cjs +95 -8
  36. package/build/worker.cjs.map +1 -1
  37. package/build/worker.js +54 -8
  38. package/build/worker.js.map +1 -1
  39. package/examples/accuracy.ts +54 -0
  40. package/examples/complete.ts +3 -3
  41. package/examples/custom-reports.ts +21 -0
  42. package/examples/imports.ts +47 -0
  43. package/examples/quick-start.ts +10 -9
  44. package/package.json +10 -9
  45. package/src/cli.ts +46 -30
  46. package/src/executor.ts +8 -2
  47. package/src/gc-watcher.ts +23 -0
  48. package/src/index.ts +11 -0
  49. package/src/runner.ts +266 -17
  50. package/src/types.ts +4 -0
  51. package/src/utils.ts +20 -0
  52. package/src/worker.ts +59 -9
  53. package/CLAUDE.md +0 -145
  54. package/examples/array-copy.ts +0 -17
  55. package/examples/object-merge.ts +0 -41
  56. package/examples/serialization.ts +0 -22
package/README.md CHANGED
@@ -19,10 +19,11 @@ const suite = benchmark('1M numbers', () => Array.from({ length: 1e6 }, (_, i) =
19
19
  suite.target('for loop').measure('sum', (_, arr) => {
20
20
  let sum = 0;
21
21
  for (let i = 0; i < arr.length; i++) sum += arr[i];
22
- return sum;
23
22
  });
24
23
 
25
- suite.target('reduce').measure('sum', (_, arr) => arr.reduce((a, b) => a + b));
24
+ suite.target('reduce').measure('sum', (_, arr) => {
25
+ arr.reduce((a, b) => a + b);
26
+ });
26
27
  ```
27
28
 
28
29
  ```bash
@@ -70,16 +71,16 @@ pnpm add -D overtake
70
71
  yarn add -D overtake
71
72
  ```
72
73
 
73
- ## ⚠️ Critical: Dynamic Imports Required
74
+ ## ⚠️ Critical: Capture-Free Functions Required
74
75
 
75
- **Benchmarks run in isolated workers. Modules MUST be imported dynamically:**
76
+ Functions you pass to `target/measure/setup/pre/post` are stringified and re-evaluated in a worker. Anything they close over (including statically imported bindings) is **not** available. Pull dependencies inside the function body—typically with `await import(...)`.
76
77
 
77
78
  ```typescript
78
- // ❌ WRONG - Static import won't work in worker
79
+ // ❌ WRONG: closes over serialize; it is undefined in the worker
79
80
  import { serialize } from 'node:v8';
80
- benchmark('data', getData).target('v8', () => ({ serialize })); // serialize is undefined!
81
+ benchmark('data', getData).target('v8', () => ({ serialize }));
81
82
 
82
- // ✅ CORRECT - Dynamic import inside target
83
+ // ✅ CORRECT: import inside the worker-run function
83
84
  benchmark('data', getData)
84
85
  .target('v8', async () => {
85
86
  const { serialize } = await import('node:v8');
@@ -88,6 +89,29 @@ benchmark('data', getData)
88
89
  .measure('serialize', ({ serialize }, input) => serialize(input));
89
90
  ```
90
91
 
92
+ ### Importing Local Files
93
+
94
+ - **CLI mode (`npx overtake`)**: `baseUrl` is set to the benchmark file, so `await import('./helper.js')` works.
95
+ - **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.
96
+
97
+ ```typescript
98
+ // CLI usage – relative path is fine
99
+ benchmark('local', () => 1)
100
+ .target('helper', async () => {
101
+ const { helper } = await import('./helpers.js');
102
+ return { helper };
103
+ })
104
+ .measure('use helper', ({ helper }) => helper());
105
+
106
+ // Programmatic usage – provide baseUrl
107
+ const suite = new Benchmark('local');
108
+ suite.target('helper', async () => {
109
+ const { helper } = await import('./helpers.js');
110
+ return { helper };
111
+ });
112
+ await suite.execute({ baseUrl: import.meta.url });
113
+ ```
114
+
91
115
  ## Usage
92
116
 
93
117
  ### CLI Mode (Recommended)
@@ -99,11 +123,13 @@ When using `npx overtake`, a global `benchmark` function is provided:
99
123
  benchmark('small', () => generateSmallData())
100
124
  .feed('large', () => generateLargeData())
101
125
  .target('algorithm A')
102
- .measure('process', (_, input) => processA(input))
126
+ .measure('process', (_, input) => {
127
+ processA(input);
128
+ })
103
129
  .target('algorithm B')
104
- .measure('process', (_, input) => processB(input));
105
-
106
- // No .execute() needed - CLI handles it
130
+ .measure('process', (_, input) => {
131
+ processB(input);
132
+ });
107
133
  ```
108
134
 
109
135
  ```bash
@@ -119,7 +145,9 @@ import { Benchmark, printTableReports } from 'overtake';
119
145
 
120
146
  const suite = new Benchmark('dataset', () => getData());
121
147
 
122
- suite.target('impl').measure('op', (_, input) => process(input));
148
+ suite.target('impl').measure('op', (_, input) => {
149
+ process(input);
150
+ });
123
151
 
124
152
  // Must explicitly execute
125
153
  const reports = await suite.execute({
@@ -167,7 +195,6 @@ suite
167
195
  // ctx contains setup return value
168
196
  const hash = createHash('sha256').update(input).digest();
169
197
  cache.set(input, hash);
170
- return hash;
171
198
  });
172
199
  ```
173
200
 
@@ -184,29 +211,45 @@ suite
184
211
  .measure('process', ({ gcBlock }, input) => {
185
212
  const result = input.map((x) => x * x);
186
213
  gcBlock.add(result); // Prevent GC during measurement
187
- return result;
188
214
  });
189
215
  ```
190
216
 
191
217
  ## Examples
192
218
 
193
- ### Serialization Comparison
219
+ ### Compare Algorithms
194
220
 
195
221
  ```typescript
196
- // Compare V8 vs JSON serialization
197
- const suite = benchmark('10K strings', () => Array.from({ length: 10_000 }, (_, i) => `string-${i}`));
222
+ // examples/quick-start.ts
223
+ const sumBenchmark = benchmark('1M numbers', () => Array.from({ length: 1_000_000 }, (_, i) => i));
198
224
 
199
- suite
200
- .target('V8', async () => {
201
- const { serialize } = await import('node:v8');
202
- return { serialize };
203
- })
204
- .measure('serialize', ({ serialize }, input) => serialize(input));
225
+ sumBenchmark.target('for loop').measure('sum', (_, numbers) => {
226
+ let sum = 0;
227
+ for (let i = 0; i < numbers.length; i++) sum += numbers[i];
228
+ });
205
229
 
206
- suite.target('JSON').measure('serialize', (_, input) => JSON.stringify(input));
230
+ sumBenchmark.target('reduce').measure('sum', (_, numbers) => {
231
+ numbers.reduce((a, b) => a + b, 0);
232
+ });
233
+ ```
234
+
235
+ ### Import Local Modules
236
+
237
+ ```typescript
238
+ // examples/imports.ts - Correct way to import local files
239
+ .target('local files', async () => {
240
+ const { join } = await import('node:path');
241
+ const modulePath = join(process.cwd(), './build/myModule.js');
242
+ const { myFunction } = await import(modulePath);
243
+ return { myFunction };
244
+ })
207
245
  ```
208
246
 
209
- **[📁 More examples in `/examples`](./examples/)**
247
+ **[📁 See all examples](./examples/):**
248
+
249
+ - `quick-start.ts` - Minimal benchmark example
250
+ - `complete.ts` - All features (setup/teardown, pre/post hooks, multiple feeds)
251
+ - `imports.ts` - Import patterns and memory management
252
+ - `custom-reports.ts` - Statistics and custom report types
210
253
 
211
254
  ## CLI Options
212
255
 
package/bin/overtake.js CHANGED
@@ -1,2 +1,2 @@
1
- #!/usr/bin/env -S node --experimental-vm-modules --no-warnings
1
+ #!/usr/bin/env -S node --experimental-vm-modules --no-warnings --expose-gc
2
2
  import '../build/cli.js';
package/build/cli.cjs CHANGED
@@ -3,12 +3,13 @@ Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
5
  const _nodemodule = require("node:module");
6
+ const _nodeurl = require("node:url");
6
7
  const _nodevm = require("node:vm");
7
8
  const _promises = require("node:fs/promises");
8
- const _core = require("@swc/core");
9
9
  const _commander = require("commander");
10
10
  const _glob = require("glob");
11
11
  const _indexcjs = require("./index.cjs");
12
+ const _utilscjs = require("./utils.cjs");
12
13
  const _typescjs = require("./types.cjs");
13
14
  function _getRequireWildcardCache(nodeInterop) {
14
15
  if (typeof WeakMap !== "function") return null;
@@ -54,38 +55,26 @@ function _interop_require_wildcard(obj, nodeInterop) {
54
55
  const require1 = (0, _nodemodule.createRequire)(require("url").pathToFileURL(__filename).toString());
55
56
  const { name, description, version } = require1('../package.json');
56
57
  const commander = new _commander.Command();
57
- const transpile = async (code)=>{
58
- const output = await (0, _core.transform)(code, {
59
- filename: 'benchmark.ts',
60
- jsc: {
61
- parser: {
62
- syntax: 'typescript',
63
- tsx: false,
64
- dynamicImport: true
65
- },
66
- target: 'esnext'
67
- },
68
- module: {
69
- type: 'es6'
70
- }
71
- });
72
- return output.code;
73
- };
74
- commander.name(name).description(description).version(version).argument('<path>', 'glob pattern to find benchmarks').addOption(new _commander.Option('-r, --report-types [reportTypes...]', 'statistic types to include in the report').choices(_typescjs.REPORT_TYPES).default(_indexcjs.DEFAULT_REPORT_TYPES)).addOption(new _commander.Option('-w, --workers [workers]', 'number of concurent workers').default(_indexcjs.DEFAULT_WORKERS).argParser(parseInt)).addOption(new _commander.Option('-f, --format [format]', 'output format').default('simple').choices([
58
+ commander.name(name).description(description).version(version).argument('<paths...>', 'glob pattern to find benchmarks').addOption(new _commander.Option('-r, --report-types [reportTypes...]', 'statistic types to include in the report').choices(_typescjs.REPORT_TYPES).default(_indexcjs.DEFAULT_REPORT_TYPES)).addOption(new _commander.Option('-w, --workers [workers]', 'number of concurent workers').default(_indexcjs.DEFAULT_WORKERS).argParser(parseInt)).addOption(new _commander.Option('-f, --format [format]', 'output format').default('simple').choices([
75
59
  'simple',
76
60
  'json',
77
61
  'pjson',
78
62
  'table'
79
- ])).addOption(new _commander.Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseInt)).addOption(new _commander.Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseInt)).addOption(new _commander.Option('--warmup-cycles [warmupCycles]', 'number of warmup cycles before measuring').argParser(parseInt)).addOption(new _commander.Option('--max-cycles [maxCycles]', 'maximum measurement cycles per feed').argParser(parseInt)).addOption(new _commander.Option('--min-cycles [minCycles]', 'minimum measurement cycles per feed').argParser(parseInt)).action(async (path, executeOptions)=>{
80
- const files = await (0, _glob.glob)(path, {
81
- absolute: true,
82
- cwd: process.cwd()
83
- }).catch(()=>[]);
63
+ ])).addOption(new _commander.Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseInt)).addOption(new _commander.Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseInt)).addOption(new _commander.Option('--warmup-cycles [warmupCycles]', 'number of warmup cycles before measuring').argParser(parseInt)).addOption(new _commander.Option('--max-cycles [maxCycles]', 'maximum measurement cycles per feed').argParser(parseInt)).addOption(new _commander.Option('--min-cycles [minCycles]', 'minimum measurement cycles per feed').argParser(parseInt)).addOption(new _commander.Option('--no-gc-observer', 'disable GC overlap detection')).action(async (patterns, executeOptions)=>{
64
+ const files = new Set();
65
+ await Promise.all(patterns.map(async (pattern)=>{
66
+ const matches = await (0, _glob.glob)(pattern, {
67
+ absolute: true,
68
+ cwd: process.cwd()
69
+ }).catch(()=>[]);
70
+ matches.forEach((file)=>files.add(file));
71
+ }));
84
72
  for (const file of files){
85
73
  const stats = await (0, _promises.stat)(file).catch(()=>false);
86
74
  if (stats && stats.isFile()) {
87
75
  const content = await (0, _promises.readFile)(file, 'utf8');
88
- const code = await transpile(content);
76
+ const identifier = (0, _nodeurl.pathToFileURL)(file).href;
77
+ const code = await (0, _utilscjs.transpile)(content);
89
78
  let instance;
90
79
  const benchmark = (...args)=>{
91
80
  if (instance) {
@@ -95,29 +84,51 @@ commander.name(name).description(description).version(version).argument('<path>'
95
84
  return instance;
96
85
  };
97
86
  const script = new _nodevm.SourceTextModule(code, {
87
+ identifier,
98
88
  context: (0, _nodevm.createContext)({
99
- benchmark
100
- })
89
+ benchmark,
90
+ Buffer,
91
+ console
92
+ }),
93
+ initializeImportMeta (meta) {
94
+ meta.url = identifier;
95
+ },
96
+ async importModuleDynamically (specifier, referencingModule) {
97
+ if (_nodemodule.Module.isBuiltin(specifier)) {
98
+ return Promise.resolve(specifier).then((p)=>/*#__PURE__*/ _interop_require_wildcard(require(p)));
99
+ }
100
+ const baseIdentifier = referencingModule.identifier ?? identifier;
101
+ const resolveFrom = (0, _nodemodule.createRequire)((0, _nodeurl.fileURLToPath)(baseIdentifier));
102
+ const resolved = resolveFrom.resolve(specifier);
103
+ return Promise.resolve(resolved).then((p)=>/*#__PURE__*/ _interop_require_wildcard(require(p)));
104
+ }
101
105
  });
102
106
  const imports = new Map();
103
107
  await script.link(async (specifier, referencingModule)=>{
104
- if (imports.has(specifier)) {
105
- return imports.get(specifier);
108
+ const baseIdentifier = referencingModule.identifier ?? identifier;
109
+ const resolveFrom = (0, _nodemodule.createRequire)((0, _nodeurl.fileURLToPath)(baseIdentifier));
110
+ const target = _nodemodule.Module.isBuiltin(specifier) ? specifier : resolveFrom.resolve(specifier);
111
+ const cached = imports.get(target);
112
+ if (cached) {
113
+ return cached;
106
114
  }
107
- const mod = await Promise.resolve(_nodemodule.Module.isBuiltin(specifier) ? specifier : require1.resolve(specifier)).then((p)=>/*#__PURE__*/ _interop_require_wildcard(require(p)));
115
+ const mod = await Promise.resolve(target).then((p)=>/*#__PURE__*/ _interop_require_wildcard(require(p)));
108
116
  const exportNames = Object.keys(mod);
109
117
  const imported = new _nodevm.SyntheticModule(exportNames, ()=>{
110
118
  exportNames.forEach((key)=>imported.setExport(key, mod[key]));
111
119
  }, {
112
- identifier: specifier,
120
+ identifier: target,
113
121
  context: referencingModule.context
114
122
  });
115
- imports.set(specifier, imported);
123
+ imports.set(target, imported);
116
124
  return imported;
117
125
  });
118
126
  await script.evaluate();
119
127
  if (instance) {
120
- const reports = await instance.execute(executeOptions);
128
+ const reports = await instance.execute({
129
+ ...executeOptions,
130
+ baseUrl: identifier
131
+ });
121
132
  switch(executeOptions.format){
122
133
  case 'json':
123
134
  {
package/build/cli.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { createRequire, Module } from 'node:module';\nimport { SyntheticModule, createContext, SourceTextModule } from 'node:vm';\nimport { stat, readFile } from 'node:fs/promises';\nimport { transform } from '@swc/core';\nimport { Command, Option } from 'commander';\nimport { glob } from 'glob';\nimport { Benchmark, printTableReports, printJSONReports, printSimpleReports, DEFAULT_REPORT_TYPES, DEFAULT_WORKERS } from './index.js';\nimport { REPORT_TYPES } from './types.js';\n\nconst require = createRequire(import.meta.url);\nconst { name, description, version } = require('../package.json');\n\nconst commander = new Command();\n\nconst transpile = async (code: string): Promise<string> => {\n const output = await transform(code, {\n filename: 'benchmark.ts',\n jsc: {\n parser: {\n syntax: 'typescript',\n tsx: false,\n dynamicImport: true,\n },\n target: 'esnext',\n },\n module: {\n type: 'es6',\n },\n });\n return output.code;\n};\n\ncommander\n .name(name)\n .description(description)\n .version(version)\n .argument('<path>', 'glob pattern to find benchmarks')\n .addOption(new Option('-r, --report-types [reportTypes...]', 'statistic types to include in the report').choices(REPORT_TYPES).default(DEFAULT_REPORT_TYPES))\n .addOption(new Option('-w, --workers [workers]', 'number of concurent workers').default(DEFAULT_WORKERS).argParser(parseInt))\n .addOption(new Option('-f, --format [format]', 'output format').default('simple').choices(['simple', 'json', 'pjson', 'table']))\n .addOption(new Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseInt))\n .addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseInt))\n .addOption(new Option('--warmup-cycles [warmupCycles]', 'number of warmup cycles before measuring').argParser(parseInt))\n .addOption(new Option('--max-cycles [maxCycles]', 'maximum measurement cycles per feed').argParser(parseInt))\n .addOption(new Option('--min-cycles [minCycles]', 'minimum measurement cycles per feed').argParser(parseInt))\n .action(async (path, executeOptions) => {\n const files = await glob(path, { absolute: true, cwd: process.cwd() }).catch(() => []);\n for (const file of files) {\n const stats = await stat(file).catch(() => false as const);\n if (stats && stats.isFile()) {\n const content = await readFile(file, 'utf8');\n const code = await transpile(content);\n let instance: Benchmark<unknown> | undefined;\n const benchmark = (...args: Parameters<(typeof Benchmark)['create']>) => {\n if (instance) {\n throw new Error('Only one benchmark per file is supported');\n }\n instance = Benchmark.create(...args);\n return instance;\n };\n const script = new SourceTextModule(code, {\n context: createContext({ benchmark }),\n });\n const imports = new Map();\n await script.link(async (specifier: string, referencingModule) => {\n if (imports.has(specifier)) {\n return imports.get(specifier);\n }\n const mod = await import(Module.isBuiltin(specifier) ? specifier : require.resolve(specifier));\n const exportNames = Object.keys(mod);\n const imported = new SyntheticModule(\n exportNames,\n () => {\n exportNames.forEach((key) => imported.setExport(key, mod[key]));\n },\n { identifier: specifier, context: referencingModule.context },\n );\n\n imports.set(specifier, imported);\n return imported;\n });\n await script.evaluate();\n\n if (instance) {\n const reports = await instance.execute(executeOptions);\n switch (executeOptions.format) {\n case 'json':\n {\n printJSONReports(reports);\n }\n break;\n case 'pjson':\n {\n printJSONReports(reports, 2);\n }\n break;\n case 'table':\n {\n printTableReports(reports);\n }\n break;\n default:\n printSimpleReports(reports);\n }\n }\n }\n }\n });\n\ncommander.parse(process.argv);\n"],"names":["require","createRequire","name","description","version","commander","Command","transpile","code","output","transform","filename","jsc","parser","syntax","tsx","dynamicImport","target","module","type","argument","addOption","Option","choices","REPORT_TYPES","default","DEFAULT_REPORT_TYPES","DEFAULT_WORKERS","argParser","parseInt","action","path","executeOptions","files","glob","absolute","cwd","process","catch","file","stats","stat","isFile","content","readFile","instance","benchmark","args","Error","Benchmark","create","script","SourceTextModule","context","createContext","imports","Map","link","specifier","referencingModule","has","get","mod","Module","isBuiltin","resolve","exportNames","Object","keys","imported","SyntheticModule","forEach","key","setExport","identifier","set","evaluate","reports","execute","format","printJSONReports","printTableReports","printSimpleReports","parse","argv"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { createRequire, Module } from 'node:module';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport { SyntheticModule, createContext, SourceTextModule } from 'node:vm';\nimport { stat, readFile } from 'node:fs/promises';\nimport { Command, Option } from 'commander';\nimport { glob } from 'glob';\nimport { Benchmark, printTableReports, printJSONReports, printSimpleReports, DEFAULT_REPORT_TYPES, DEFAULT_WORKERS } from './index.js';\nimport { transpile } from './utils.js';\nimport { REPORT_TYPES } from './types.js';\n\nconst require = createRequire(import.meta.url);\nconst { name, description, version } = require('../package.json');\n\nconst commander = new Command();\n\ncommander\n .name(name)\n .description(description)\n .version(version)\n .argument('<paths...>', 'glob pattern to find benchmarks')\n .addOption(new Option('-r, --report-types [reportTypes...]', 'statistic types to include in the report').choices(REPORT_TYPES).default(DEFAULT_REPORT_TYPES))\n .addOption(new Option('-w, --workers [workers]', 'number of concurent workers').default(DEFAULT_WORKERS).argParser(parseInt))\n .addOption(new Option('-f, --format [format]', 'output format').default('simple').choices(['simple', 'json', 'pjson', 'table']))\n .addOption(new Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseInt))\n .addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseInt))\n .addOption(new Option('--warmup-cycles [warmupCycles]', 'number of warmup cycles before measuring').argParser(parseInt))\n .addOption(new Option('--max-cycles [maxCycles]', 'maximum measurement cycles per feed').argParser(parseInt))\n .addOption(new Option('--min-cycles [minCycles]', 'minimum measurement cycles per feed').argParser(parseInt))\n .addOption(new Option('--no-gc-observer', 'disable GC overlap detection'))\n .action(async (patterns: string[], executeOptions) => {\n const files = new Set<string>();\n await Promise.all(\n patterns.map(async (pattern) => {\n const matches = await glob(pattern, { absolute: true, cwd: process.cwd() }).catch(() => []);\n matches.forEach((file) => files.add(file));\n }),\n );\n\n for (const file of files) {\n const stats = await stat(file).catch(() => false as const);\n if (stats && stats.isFile()) {\n const content = await readFile(file, 'utf8');\n const identifier = pathToFileURL(file).href;\n const code = await transpile(content);\n let instance: Benchmark<unknown> | undefined;\n const benchmark = (...args: Parameters<(typeof Benchmark)['create']>) => {\n if (instance) {\n throw new Error('Only one benchmark per file is supported');\n }\n instance = Benchmark.create(...args);\n return instance;\n };\n const script = new SourceTextModule(code, {\n identifier,\n context: createContext({\n benchmark,\n Buffer,\n console,\n }),\n initializeImportMeta(meta) {\n meta.url = identifier;\n },\n async importModuleDynamically(specifier, referencingModule) {\n if (Module.isBuiltin(specifier)) {\n return import(specifier);\n }\n const baseIdentifier = referencingModule.identifier ?? identifier;\n const resolveFrom = createRequire(fileURLToPath(baseIdentifier));\n const resolved = resolveFrom.resolve(specifier);\n return import(resolved);\n },\n });\n const imports = new Map<string, SyntheticModule>();\n await script.link(async (specifier: string, referencingModule) => {\n const baseIdentifier = referencingModule.identifier ?? identifier;\n const resolveFrom = createRequire(fileURLToPath(baseIdentifier));\n const target = Module.isBuiltin(specifier) ? specifier : resolveFrom.resolve(specifier);\n const cached = imports.get(target);\n if (cached) {\n return cached;\n }\n const mod = await import(target);\n const exportNames = Object.keys(mod);\n const imported = new SyntheticModule(\n exportNames,\n () => {\n exportNames.forEach((key) => imported.setExport(key, mod[key]));\n },\n { identifier: target, context: referencingModule.context },\n );\n\n imports.set(target, imported);\n return imported;\n });\n await script.evaluate();\n\n if (instance) {\n const reports = await instance.execute({\n ...executeOptions,\n baseUrl: identifier,\n });\n switch (executeOptions.format) {\n case 'json':\n {\n printJSONReports(reports);\n }\n break;\n case 'pjson':\n {\n printJSONReports(reports, 2);\n }\n break;\n case 'table':\n {\n printTableReports(reports);\n }\n break;\n default:\n printSimpleReports(reports);\n }\n }\n }\n }\n });\n\ncommander.parse(process.argv);\n"],"names":["require","createRequire","name","description","version","commander","Command","argument","addOption","Option","choices","REPORT_TYPES","default","DEFAULT_REPORT_TYPES","DEFAULT_WORKERS","argParser","parseInt","action","patterns","executeOptions","files","Set","Promise","all","map","pattern","matches","glob","absolute","cwd","process","catch","forEach","file","add","stats","stat","isFile","content","readFile","identifier","pathToFileURL","href","code","transpile","instance","benchmark","args","Error","Benchmark","create","script","SourceTextModule","context","createContext","Buffer","console","initializeImportMeta","meta","url","importModuleDynamically","specifier","referencingModule","Module","isBuiltin","baseIdentifier","resolveFrom","fileURLToPath","resolved","resolve","imports","Map","link","target","cached","get","mod","exportNames","Object","keys","imported","SyntheticModule","key","setExport","set","evaluate","reports","execute","baseUrl","format","printJSONReports","printTableReports","printSimpleReports","parse","argv"],"mappings":"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"}
package/build/cli.js CHANGED
@@ -1,45 +1,34 @@
1
1
  import { createRequire, Module } from 'node:module';
2
+ import { fileURLToPath, pathToFileURL } from 'node:url';
2
3
  import { SyntheticModule, createContext, SourceTextModule } from 'node:vm';
3
4
  import { stat, readFile } from 'node:fs/promises';
4
- import { transform } from '@swc/core';
5
5
  import { Command, Option } from 'commander';
6
6
  import { glob } from 'glob';
7
7
  import { Benchmark, printTableReports, printJSONReports, printSimpleReports, DEFAULT_REPORT_TYPES, DEFAULT_WORKERS } from "./index.js";
8
+ import { transpile } from "./utils.js";
8
9
  import { REPORT_TYPES } from "./types.js";
9
10
  const require = createRequire(import.meta.url);
10
11
  const { name, description, version } = require('../package.json');
11
12
  const commander = new Command();
12
- const transpile = async (code)=>{
13
- const output = await transform(code, {
14
- filename: 'benchmark.ts',
15
- jsc: {
16
- parser: {
17
- syntax: 'typescript',
18
- tsx: false,
19
- dynamicImport: true
20
- },
21
- target: 'esnext'
22
- },
23
- module: {
24
- type: 'es6'
25
- }
26
- });
27
- return output.code;
28
- };
29
- commander.name(name).description(description).version(version).argument('<path>', '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([
13
+ 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([
30
14
  'simple',
31
15
  'json',
32
16
  'pjson',
33
17
  'table'
34
- ])).addOption(new Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseInt)).addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseInt)).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)).action(async (path, executeOptions)=>{
35
- const files = await glob(path, {
36
- absolute: true,
37
- cwd: process.cwd()
38
- }).catch(()=>[]);
18
+ ])).addOption(new Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseInt)).addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseInt)).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')).action(async (patterns, executeOptions)=>{
19
+ const files = new Set();
20
+ await Promise.all(patterns.map(async (pattern)=>{
21
+ const matches = await glob(pattern, {
22
+ absolute: true,
23
+ cwd: process.cwd()
24
+ }).catch(()=>[]);
25
+ matches.forEach((file)=>files.add(file));
26
+ }));
39
27
  for (const file of files){
40
28
  const stats = await stat(file).catch(()=>false);
41
29
  if (stats && stats.isFile()) {
42
30
  const content = await readFile(file, 'utf8');
31
+ const identifier = pathToFileURL(file).href;
43
32
  const code = await transpile(content);
44
33
  let instance;
45
34
  const benchmark = (...args)=>{
@@ -50,29 +39,51 @@ commander.name(name).description(description).version(version).argument('<path>'
50
39
  return instance;
51
40
  };
52
41
  const script = new SourceTextModule(code, {
42
+ identifier,
53
43
  context: createContext({
54
- benchmark
55
- })
44
+ benchmark,
45
+ Buffer,
46
+ console
47
+ }),
48
+ initializeImportMeta (meta) {
49
+ meta.url = identifier;
50
+ },
51
+ async importModuleDynamically (specifier, referencingModule) {
52
+ if (Module.isBuiltin(specifier)) {
53
+ return import(specifier);
54
+ }
55
+ const baseIdentifier = referencingModule.identifier ?? identifier;
56
+ const resolveFrom = createRequire(fileURLToPath(baseIdentifier));
57
+ const resolved = resolveFrom.resolve(specifier);
58
+ return import(resolved);
59
+ }
56
60
  });
57
61
  const imports = new Map();
58
62
  await script.link(async (specifier, referencingModule)=>{
59
- if (imports.has(specifier)) {
60
- return imports.get(specifier);
63
+ const baseIdentifier = referencingModule.identifier ?? identifier;
64
+ const resolveFrom = createRequire(fileURLToPath(baseIdentifier));
65
+ const target = Module.isBuiltin(specifier) ? specifier : resolveFrom.resolve(specifier);
66
+ const cached = imports.get(target);
67
+ if (cached) {
68
+ return cached;
61
69
  }
62
- const mod = await import(Module.isBuiltin(specifier) ? specifier : require.resolve(specifier));
70
+ const mod = await import(target);
63
71
  const exportNames = Object.keys(mod);
64
72
  const imported = new SyntheticModule(exportNames, ()=>{
65
73
  exportNames.forEach((key)=>imported.setExport(key, mod[key]));
66
74
  }, {
67
- identifier: specifier,
75
+ identifier: target,
68
76
  context: referencingModule.context
69
77
  });
70
- imports.set(specifier, imported);
78
+ imports.set(target, imported);
71
79
  return imported;
72
80
  });
73
81
  await script.evaluate();
74
82
  if (instance) {
75
- const reports = await instance.execute(executeOptions);
83
+ const reports = await instance.execute({
84
+ ...executeOptions,
85
+ baseUrl: identifier
86
+ });
76
87
  switch(executeOptions.format){
77
88
  case 'json':
78
89
  {
package/build/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { createRequire, Module } from 'node:module';\nimport { SyntheticModule, createContext, SourceTextModule } from 'node:vm';\nimport { stat, readFile } from 'node:fs/promises';\nimport { transform } from '@swc/core';\nimport { Command, Option } from 'commander';\nimport { glob } from 'glob';\nimport { Benchmark, printTableReports, printJSONReports, printSimpleReports, DEFAULT_REPORT_TYPES, DEFAULT_WORKERS } from './index.js';\nimport { REPORT_TYPES } from './types.js';\n\nconst require = createRequire(import.meta.url);\nconst { name, description, version } = require('../package.json');\n\nconst commander = new Command();\n\nconst transpile = async (code: string): Promise<string> => {\n const output = await transform(code, {\n filename: 'benchmark.ts',\n jsc: {\n parser: {\n syntax: 'typescript',\n tsx: false,\n dynamicImport: true,\n },\n target: 'esnext',\n },\n module: {\n type: 'es6',\n },\n });\n return output.code;\n};\n\ncommander\n .name(name)\n .description(description)\n .version(version)\n .argument('<path>', 'glob pattern to find benchmarks')\n .addOption(new Option('-r, --report-types [reportTypes...]', 'statistic types to include in the report').choices(REPORT_TYPES).default(DEFAULT_REPORT_TYPES))\n .addOption(new Option('-w, --workers [workers]', 'number of concurent workers').default(DEFAULT_WORKERS).argParser(parseInt))\n .addOption(new Option('-f, --format [format]', 'output format').default('simple').choices(['simple', 'json', 'pjson', 'table']))\n .addOption(new Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseInt))\n .addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseInt))\n .addOption(new Option('--warmup-cycles [warmupCycles]', 'number of warmup cycles before measuring').argParser(parseInt))\n .addOption(new Option('--max-cycles [maxCycles]', 'maximum measurement cycles per feed').argParser(parseInt))\n .addOption(new Option('--min-cycles [minCycles]', 'minimum measurement cycles per feed').argParser(parseInt))\n .action(async (path, executeOptions) => {\n const files = await glob(path, { absolute: true, cwd: process.cwd() }).catch(() => []);\n for (const file of files) {\n const stats = await stat(file).catch(() => false as const);\n if (stats && stats.isFile()) {\n const content = await readFile(file, 'utf8');\n const code = await transpile(content);\n let instance: Benchmark<unknown> | undefined;\n const benchmark = (...args: Parameters<(typeof Benchmark)['create']>) => {\n if (instance) {\n throw new Error('Only one benchmark per file is supported');\n }\n instance = Benchmark.create(...args);\n return instance;\n };\n const script = new SourceTextModule(code, {\n context: createContext({ benchmark }),\n });\n const imports = new Map();\n await script.link(async (specifier: string, referencingModule) => {\n if (imports.has(specifier)) {\n return imports.get(specifier);\n }\n const mod = await import(Module.isBuiltin(specifier) ? specifier : require.resolve(specifier));\n const exportNames = Object.keys(mod);\n const imported = new SyntheticModule(\n exportNames,\n () => {\n exportNames.forEach((key) => imported.setExport(key, mod[key]));\n },\n { identifier: specifier, context: referencingModule.context },\n );\n\n imports.set(specifier, imported);\n return imported;\n });\n await script.evaluate();\n\n if (instance) {\n const reports = await instance.execute(executeOptions);\n switch (executeOptions.format) {\n case 'json':\n {\n printJSONReports(reports);\n }\n break;\n case 'pjson':\n {\n printJSONReports(reports, 2);\n }\n break;\n case 'table':\n {\n printTableReports(reports);\n }\n break;\n default:\n printSimpleReports(reports);\n }\n }\n }\n }\n });\n\ncommander.parse(process.argv);\n"],"names":["createRequire","Module","SyntheticModule","createContext","SourceTextModule","stat","readFile","transform","Command","Option","glob","Benchmark","printTableReports","printJSONReports","printSimpleReports","DEFAULT_REPORT_TYPES","DEFAULT_WORKERS","REPORT_TYPES","require","url","name","description","version","commander","transpile","code","output","filename","jsc","parser","syntax","tsx","dynamicImport","target","module","type","argument","addOption","choices","default","argParser","parseInt","action","path","executeOptions","files","absolute","cwd","process","catch","file","stats","isFile","content","instance","benchmark","args","Error","create","script","context","imports","Map","link","specifier","referencingModule","has","get","mod","isBuiltin","resolve","exportNames","Object","keys","imported","forEach","key","setExport","identifier","set","evaluate","reports","execute","format","parse","argv"],"mappings":"AAAA,SAASA,aAAa,EAAEC,MAAM,QAAQ,cAAc;AACpD,SAASC,eAAe,EAAEC,aAAa,EAAEC,gBAAgB,QAAQ,UAAU;AAC3E,SAASC,IAAI,EAAEC,QAAQ,QAAQ,mBAAmB;AAClD,SAASC,SAAS,QAAQ,YAAY;AACtC,SAASC,OAAO,EAAEC,MAAM,QAAQ,YAAY;AAC5C,SAASC,IAAI,QAAQ,OAAO;AAC5B,SAASC,SAAS,EAAEC,iBAAiB,EAAEC,gBAAgB,EAAEC,kBAAkB,EAAEC,oBAAoB,EAAEC,eAAe,QAAQ,aAAa;AACvI,SAASC,YAAY,QAAQ,aAAa;AAE1C,MAAMC,UAAUlB,cAAc,YAAYmB,GAAG;AAC7C,MAAM,EAAEC,IAAI,EAAEC,WAAW,EAAEC,OAAO,EAAE,GAAGJ,QAAQ;AAE/C,MAAMK,YAAY,IAAIf;AAEtB,MAAMgB,YAAY,OAAOC;IACvB,MAAMC,SAAS,MAAMnB,UAAUkB,MAAM;QACnCE,UAAU;QACVC,KAAK;YACHC,QAAQ;gBACNC,QAAQ;gBACRC,KAAK;gBACLC,eAAe;YACjB;YACAC,QAAQ;QACV;QACAC,QAAQ;YACNC,MAAM;QACR;IACF;IACA,OAAOT,OAAOD,IAAI;AACpB;AAEAF,UACGH,IAAI,CAACA,MACLC,WAAW,CAACA,aACZC,OAAO,CAACA,SACRc,QAAQ,CAAC,UAAU,mCACnBC,SAAS,CAAC,IAAI5B,OAAO,uCAAuC,4CAA4C6B,OAAO,CAACrB,cAAcsB,OAAO,CAACxB,uBACtIsB,SAAS,CAAC,IAAI5B,OAAO,2BAA2B,+BAA+B8B,OAAO,CAACvB,iBAAiBwB,SAAS,CAACC,WAClHJ,SAAS,CAAC,IAAI5B,OAAO,yBAAyB,iBAAiB8B,OAAO,CAAC,UAAUD,OAAO,CAAC;IAAC;IAAU;IAAQ;IAAS;CAAQ,GAC7HD,SAAS,CAAC,IAAI5B,OAAO,kCAAkC,2CAA2C+B,SAAS,CAACC,WAC5GJ,SAAS,CAAC,IAAI5B,OAAO,kCAAkC,uDAAuD+B,SAAS,CAACC,WACxHJ,SAAS,CAAC,IAAI5B,OAAO,kCAAkC,4CAA4C+B,SAAS,CAACC,WAC7GJ,SAAS,CAAC,IAAI5B,OAAO,4BAA4B,uCAAuC+B,SAAS,CAACC,WAClGJ,SAAS,CAAC,IAAI5B,OAAO,4BAA4B,uCAAuC+B,SAAS,CAACC,WAClGC,MAAM,CAAC,OAAOC,MAAMC;IACnB,MAAMC,QAAQ,MAAMnC,KAAKiC,MAAM;QAAEG,UAAU;QAAMC,KAAKC,QAAQD,GAAG;IAAG,GAAGE,KAAK,CAAC,IAAM,EAAE;IACrF,KAAK,MAAMC,QAAQL,MAAO;QACxB,MAAMM,QAAQ,MAAM9C,KAAK6C,MAAMD,KAAK,CAAC,IAAM;QAC3C,IAAIE,SAASA,MAAMC,MAAM,IAAI;YAC3B,MAAMC,UAAU,MAAM/C,SAAS4C,MAAM;YACrC,MAAMzB,OAAO,MAAMD,UAAU6B;YAC7B,IAAIC;YACJ,MAAMC,YAAY,CAAC,GAAGC;gBACpB,IAAIF,UAAU;oBACZ,MAAM,IAAIG,MAAM;gBAClB;gBACAH,WAAW3C,UAAU+C,MAAM,IAAIF;gBAC/B,OAAOF;YACT;YACA,MAAMK,SAAS,IAAIvD,iBAAiBqB,MAAM;gBACxCmC,SAASzD,cAAc;oBAAEoD;gBAAU;YACrC;YACA,MAAMM,UAAU,IAAIC;YACpB,MAAMH,OAAOI,IAAI,CAAC,OAAOC,WAAmBC;gBAC1C,IAAIJ,QAAQK,GAAG,CAACF,YAAY;oBAC1B,OAAOH,QAAQM,GAAG,CAACH;gBACrB;gBACA,MAAMI,MAAM,MAAM,MAAM,CAACnE,OAAOoE,SAAS,CAACL,aAAaA,YAAY9C,QAAQoD,OAAO,CAACN;gBACnF,MAAMO,cAAcC,OAAOC,IAAI,CAACL;gBAChC,MAAMM,WAAW,IAAIxE,gBACnBqE,aACA;oBACEA,YAAYI,OAAO,CAAC,CAACC,MAAQF,SAASG,SAAS,CAACD,KAAKR,GAAG,CAACQ,IAAI;gBAC/D,GACA;oBAAEE,YAAYd;oBAAWJ,SAASK,kBAAkBL,OAAO;gBAAC;gBAG9DC,QAAQkB,GAAG,CAACf,WAAWU;gBACvB,OAAOA;YACT;YACA,MAAMf,OAAOqB,QAAQ;YAErB,IAAI1B,UAAU;gBACZ,MAAM2B,UAAU,MAAM3B,SAAS4B,OAAO,CAACtC;gBACvC,OAAQA,eAAeuC,MAAM;oBAC3B,KAAK;wBACH;4BACEtE,iBAAiBoE;wBACnB;wBACA;oBACF,KAAK;wBACH;4BACEpE,iBAAiBoE,SAAS;wBAC5B;wBACA;oBACF,KAAK;wBACH;4BACErE,kBAAkBqE;wBACpB;wBACA;oBACF;wBACEnE,mBAAmBmE;gBACvB;YACF;QACF;IACF;AACF;AAEF1D,UAAU6D,KAAK,CAACpC,QAAQqC,IAAI"}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { createRequire, Module } from 'node:module';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport { SyntheticModule, createContext, SourceTextModule } from 'node:vm';\nimport { stat, readFile } from 'node:fs/promises';\nimport { Command, Option } from 'commander';\nimport { glob } from 'glob';\nimport { Benchmark, printTableReports, printJSONReports, printSimpleReports, DEFAULT_REPORT_TYPES, DEFAULT_WORKERS } from './index.js';\nimport { transpile } from './utils.js';\nimport { REPORT_TYPES } from './types.js';\n\nconst require = createRequire(import.meta.url);\nconst { name, description, version } = require('../package.json');\n\nconst commander = new Command();\n\ncommander\n .name(name)\n .description(description)\n .version(version)\n .argument('<paths...>', 'glob pattern to find benchmarks')\n .addOption(new Option('-r, --report-types [reportTypes...]', 'statistic types to include in the report').choices(REPORT_TYPES).default(DEFAULT_REPORT_TYPES))\n .addOption(new Option('-w, --workers [workers]', 'number of concurent workers').default(DEFAULT_WORKERS).argParser(parseInt))\n .addOption(new Option('-f, --format [format]', 'output format').default('simple').choices(['simple', 'json', 'pjson', 'table']))\n .addOption(new Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseInt))\n .addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseInt))\n .addOption(new Option('--warmup-cycles [warmupCycles]', 'number of warmup cycles before measuring').argParser(parseInt))\n .addOption(new Option('--max-cycles [maxCycles]', 'maximum measurement cycles per feed').argParser(parseInt))\n .addOption(new Option('--min-cycles [minCycles]', 'minimum measurement cycles per feed').argParser(parseInt))\n .addOption(new Option('--no-gc-observer', 'disable GC overlap detection'))\n .action(async (patterns: string[], executeOptions) => {\n const files = new Set<string>();\n await Promise.all(\n patterns.map(async (pattern) => {\n const matches = await glob(pattern, { absolute: true, cwd: process.cwd() }).catch(() => []);\n matches.forEach((file) => files.add(file));\n }),\n );\n\n for (const file of files) {\n const stats = await stat(file).catch(() => false as const);\n if (stats && stats.isFile()) {\n const content = await readFile(file, 'utf8');\n const identifier = pathToFileURL(file).href;\n const code = await transpile(content);\n let instance: Benchmark<unknown> | undefined;\n const benchmark = (...args: Parameters<(typeof Benchmark)['create']>) => {\n if (instance) {\n throw new Error('Only one benchmark per file is supported');\n }\n instance = Benchmark.create(...args);\n return instance;\n };\n const script = new SourceTextModule(code, {\n identifier,\n context: createContext({\n benchmark,\n Buffer,\n console,\n }),\n initializeImportMeta(meta) {\n meta.url = identifier;\n },\n async importModuleDynamically(specifier, referencingModule) {\n if (Module.isBuiltin(specifier)) {\n return import(specifier);\n }\n const baseIdentifier = referencingModule.identifier ?? identifier;\n const resolveFrom = createRequire(fileURLToPath(baseIdentifier));\n const resolved = resolveFrom.resolve(specifier);\n return import(resolved);\n },\n });\n const imports = new Map<string, SyntheticModule>();\n await script.link(async (specifier: string, referencingModule) => {\n const baseIdentifier = referencingModule.identifier ?? identifier;\n const resolveFrom = createRequire(fileURLToPath(baseIdentifier));\n const target = Module.isBuiltin(specifier) ? specifier : resolveFrom.resolve(specifier);\n const cached = imports.get(target);\n if (cached) {\n return cached;\n }\n const mod = await import(target);\n const exportNames = Object.keys(mod);\n const imported = new SyntheticModule(\n exportNames,\n () => {\n exportNames.forEach((key) => imported.setExport(key, mod[key]));\n },\n { identifier: target, context: referencingModule.context },\n );\n\n imports.set(target, imported);\n return imported;\n });\n await script.evaluate();\n\n if (instance) {\n const reports = await instance.execute({\n ...executeOptions,\n baseUrl: identifier,\n });\n switch (executeOptions.format) {\n case 'json':\n {\n printJSONReports(reports);\n }\n break;\n case 'pjson':\n {\n printJSONReports(reports, 2);\n }\n break;\n case 'table':\n {\n printTableReports(reports);\n }\n break;\n default:\n printSimpleReports(reports);\n }\n }\n }\n }\n });\n\ncommander.parse(process.argv);\n"],"names":["createRequire","Module","fileURLToPath","pathToFileURL","SyntheticModule","createContext","SourceTextModule","stat","readFile","Command","Option","glob","Benchmark","printTableReports","printJSONReports","printSimpleReports","DEFAULT_REPORT_TYPES","DEFAULT_WORKERS","transpile","REPORT_TYPES","require","url","name","description","version","commander","argument","addOption","choices","default","argParser","parseInt","action","patterns","executeOptions","files","Set","Promise","all","map","pattern","matches","absolute","cwd","process","catch","forEach","file","add","stats","isFile","content","identifier","href","code","instance","benchmark","args","Error","create","script","context","Buffer","console","initializeImportMeta","meta","importModuleDynamically","specifier","referencingModule","isBuiltin","baseIdentifier","resolveFrom","resolved","resolve","imports","Map","link","target","cached","get","mod","exportNames","Object","keys","imported","key","setExport","set","evaluate","reports","execute","baseUrl","format","parse","argv"],"mappings":"AAAA,SAASA,aAAa,EAAEC,MAAM,QAAQ,cAAc;AACpD,SAASC,aAAa,EAAEC,aAAa,QAAQ,WAAW;AACxD,SAASC,eAAe,EAAEC,aAAa,EAAEC,gBAAgB,QAAQ,UAAU;AAC3E,SAASC,IAAI,EAAEC,QAAQ,QAAQ,mBAAmB;AAClD,SAASC,OAAO,EAAEC,MAAM,QAAQ,YAAY;AAC5C,SAASC,IAAI,QAAQ,OAAO;AAC5B,SAASC,SAAS,EAAEC,iBAAiB,EAAEC,gBAAgB,EAAEC,kBAAkB,EAAEC,oBAAoB,EAAEC,eAAe,QAAQ,aAAa;AACvI,SAASC,SAAS,QAAQ,aAAa;AACvC,SAASC,YAAY,QAAQ,aAAa;AAE1C,MAAMC,UAAUpB,cAAc,YAAYqB,GAAG;AAC7C,MAAM,EAAEC,IAAI,EAAEC,WAAW,EAAEC,OAAO,EAAE,GAAGJ,QAAQ;AAE/C,MAAMK,YAAY,IAAIhB;AAEtBgB,UACGH,IAAI,CAACA,MACLC,WAAW,CAACA,aACZC,OAAO,CAACA,SACRE,QAAQ,CAAC,cAAc,mCACvBC,SAAS,CAAC,IAAIjB,OAAO,uCAAuC,4CAA4CkB,OAAO,CAACT,cAAcU,OAAO,CAACb,uBACtIW,SAAS,CAAC,IAAIjB,OAAO,2BAA2B,+BAA+BmB,OAAO,CAACZ,iBAAiBa,SAAS,CAACC,WAClHJ,SAAS,CAAC,IAAIjB,OAAO,yBAAyB,iBAAiBmB,OAAO,CAAC,UAAUD,OAAO,CAAC;IAAC;IAAU;IAAQ;IAAS;CAAQ,GAC7HD,SAAS,CAAC,IAAIjB,OAAO,kCAAkC,2CAA2CoB,SAAS,CAACC,WAC5GJ,SAAS,CAAC,IAAIjB,OAAO,kCAAkC,uDAAuDoB,SAAS,CAACC,WACxHJ,SAAS,CAAC,IAAIjB,OAAO,kCAAkC,4CAA4CoB,SAAS,CAACC,WAC7GJ,SAAS,CAAC,IAAIjB,OAAO,4BAA4B,uCAAuCoB,SAAS,CAACC,WAClGJ,SAAS,CAAC,IAAIjB,OAAO,4BAA4B,uCAAuCoB,SAAS,CAACC,WAClGJ,SAAS,CAAC,IAAIjB,OAAO,oBAAoB,iCACzCsB,MAAM,CAAC,OAAOC,UAAoBC;IACjC,MAAMC,QAAQ,IAAIC;IAClB,MAAMC,QAAQC,GAAG,CACfL,SAASM,GAAG,CAAC,OAAOC;QAClB,MAAMC,UAAU,MAAM9B,KAAK6B,SAAS;YAAEE,UAAU;YAAMC,KAAKC,QAAQD,GAAG;QAAG,GAAGE,KAAK,CAAC,IAAM,EAAE;QAC1FJ,QAAQK,OAAO,CAAC,CAACC,OAASZ,MAAMa,GAAG,CAACD;IACtC;IAGF,KAAK,MAAMA,QAAQZ,MAAO;QACxB,MAAMc,QAAQ,MAAM1C,KAAKwC,MAAMF,KAAK,CAAC,IAAM;QAC3C,IAAII,SAASA,MAAMC,MAAM,IAAI;YAC3B,MAAMC,UAAU,MAAM3C,SAASuC,MAAM;YACrC,MAAMK,aAAajD,cAAc4C,MAAMM,IAAI;YAC3C,MAAMC,OAAO,MAAMpC,UAAUiC;YAC7B,IAAII;YACJ,MAAMC,YAAY,CAAC,GAAGC;gBACpB,IAAIF,UAAU;oBACZ,MAAM,IAAIG,MAAM;gBAClB;gBACAH,WAAW3C,UAAU+C,MAAM,IAAIF;gBAC/B,OAAOF;YACT;YACA,MAAMK,SAAS,IAAItD,iBAAiBgD,MAAM;gBACxCF;gBACAS,SAASxD,cAAc;oBACrBmD;oBACAM;oBACAC;gBACF;gBACAC,sBAAqBC,IAAI;oBACvBA,KAAK5C,GAAG,GAAG+B;gBACb;gBACA,MAAMc,yBAAwBC,SAAS,EAAEC,iBAAiB;oBACxD,IAAInE,OAAOoE,SAAS,CAACF,YAAY;wBAC/B,OAAO,MAAM,CAACA;oBAChB;oBACA,MAAMG,iBAAiBF,kBAAkBhB,UAAU,IAAIA;oBACvD,MAAMmB,cAAcvE,cAAcE,cAAcoE;oBAChD,MAAME,WAAWD,YAAYE,OAAO,CAACN;oBACrC,OAAO,MAAM,CAACK;gBAChB;YACF;YACA,MAAME,UAAU,IAAIC;YACpB,MAAMf,OAAOgB,IAAI,CAAC,OAAOT,WAAmBC;gBAC1C,MAAME,iBAAiBF,kBAAkBhB,UAAU,IAAIA;gBACvD,MAAMmB,cAAcvE,cAAcE,cAAcoE;gBAChD,MAAMO,SAAS5E,OAAOoE,SAAS,CAACF,aAAaA,YAAYI,YAAYE,OAAO,CAACN;gBAC7E,MAAMW,SAASJ,QAAQK,GAAG,CAACF;gBAC3B,IAAIC,QAAQ;oBACV,OAAOA;gBACT;gBACA,MAAME,MAAM,MAAM,MAAM,CAACH;gBACzB,MAAMI,cAAcC,OAAOC,IAAI,CAACH;gBAChC,MAAMI,WAAW,IAAIhF,gBACnB6E,aACA;oBACEA,YAAYnC,OAAO,CAAC,CAACuC,MAAQD,SAASE,SAAS,CAACD,KAAKL,GAAG,CAACK,IAAI;gBAC/D,GACA;oBAAEjC,YAAYyB;oBAAQhB,SAASO,kBAAkBP,OAAO;gBAAC;gBAG3Da,QAAQa,GAAG,CAACV,QAAQO;gBACpB,OAAOA;YACT;YACA,MAAMxB,OAAO4B,QAAQ;YAErB,IAAIjC,UAAU;gBACZ,MAAMkC,UAAU,MAAMlC,SAASmC,OAAO,CAAC;oBACrC,GAAGxD,cAAc;oBACjByD,SAASvC;gBACX;gBACA,OAAQlB,eAAe0D,MAAM;oBAC3B,KAAK;wBACH;4BACE9E,iBAAiB2E;wBACnB;wBACA;oBACF,KAAK;wBACH;4BACE3E,iBAAiB2E,SAAS;wBAC5B;wBACA;oBACF,KAAK;wBACH;4BACE5E,kBAAkB4E;wBACpB;wBACA;oBACF;wBACE1E,mBAAmB0E;gBACvB;YACF;QACF;IACF;AACF;AAEFhE,UAAUoE,KAAK,CAACjD,QAAQkD,IAAI"}
@@ -11,11 +11,12 @@ Object.defineProperty(exports, "createExecutor", {
11
11
  const _nodeworker_threads = require("node:worker_threads");
12
12
  const _nodeevents = require("node:events");
13
13
  const _async = require("async");
14
- const _typescjs = require("./types.cjs");
14
+ const _nodeurl = require("node:url");
15
15
  const _reportercjs = require("./reporter.cjs");
16
16
  const _utilscjs = require("./utils.cjs");
17
- const createExecutor = ({ workers, warmupCycles, maxCycles, minCycles, absThreshold, relThreshold, reportTypes })=>{
18
- const executor = (0, _async.queue)(async ({ setup, teardown, pre, run, post, data })=>{
17
+ const _typescjs = require("./types.cjs");
18
+ const createExecutor = ({ baseUrl = (0, _nodeurl.pathToFileURL)(process.cwd()).href, workers, warmupCycles, maxCycles, minCycles, absThreshold, relThreshold, gcObserver = true, reportTypes })=>{
19
+ const executor = (0, _async.queue)(async ({ baseUrl: runBaseUrl = baseUrl, setup, teardown, pre, run, post, data })=>{
19
20
  const setupCode = setup?.toString();
20
21
  const teardownCode = teardown?.toString();
21
22
  const preCode = pre?.toString();
@@ -25,6 +26,7 @@ const createExecutor = ({ workers, warmupCycles, maxCycles, minCycles, absThresh
25
26
  const durationsSAB = new SharedArrayBuffer(BigUint64Array.BYTES_PER_ELEMENT * maxCycles);
26
27
  const workerFile = new URL('./worker.js', require("url").pathToFileURL(__filename).toString());
27
28
  const workerData = {
29
+ baseUrl: runBaseUrl,
28
30
  setupCode,
29
31
  teardownCode,
30
32
  preCode,
@@ -35,6 +37,7 @@ const createExecutor = ({ workers, warmupCycles, maxCycles, minCycles, absThresh
35
37
  minCycles,
36
38
  absThreshold,
37
39
  relThreshold,
40
+ gcObserver,
38
41
  controlSAB,
39
42
  durationsSAB
40
43
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/executor.ts"],"sourcesContent":["import { Worker } from 'node:worker_threads';\nimport { once } from 'node:events';\nimport { queue } from 'async';\nimport { RunOptions, ReportOptions, WorkerOptions, BenchmarkOptions, Control, ReportType, ReportTypeList, CONTROL_SLOTS } from './types.js';\nimport { createReport, Report } from './reporter.js';\nimport { cmp } from './utils.js';\n\nexport type ExecutorReport<R extends ReportTypeList> = Record<R[number], Report> & { count: number };\n\nexport interface ExecutorOptions<R extends ReportTypeList> extends BenchmarkOptions, ReportOptions<R> {\n workers?: number;\n maxCycles?: number;\n}\n\nexport const createExecutor = <TContext, TInput, R extends ReportTypeList>({\n workers,\n warmupCycles,\n maxCycles,\n minCycles,\n absThreshold,\n relThreshold,\n reportTypes,\n}: Required<ExecutorOptions<R>>) => {\n const executor = queue<RunOptions<TContext, TInput>>(async ({ setup, teardown, pre, run, post, data }) => {\n const setupCode = setup?.toString();\n const teardownCode = teardown?.toString();\n const preCode = pre?.toString();\n const runCode = run.toString()!;\n const postCode = post?.toString();\n\n const controlSAB = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * CONTROL_SLOTS);\n const durationsSAB = new SharedArrayBuffer(BigUint64Array.BYTES_PER_ELEMENT * maxCycles);\n\n const workerFile = new URL('./worker.js', import.meta.url);\n const workerData: WorkerOptions = {\n setupCode,\n teardownCode,\n preCode,\n runCode,\n postCode,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n\n controlSAB,\n durationsSAB,\n };\n\n const worker = new Worker(workerFile, {\n workerData,\n });\n const [exitCode] = await once(worker, 'exit');\n if (exitCode !== 0) {\n throw new Error(`worker exited with code ${exitCode}`);\n }\n\n const control = new Int32Array(controlSAB);\n const count = control[Control.INDEX];\n const durations = new BigUint64Array(durationsSAB).slice(0, count).sort(cmp);\n\n const report = reportTypes.map<[string, unknown]>((type) => [type, createReport(durations, type)] as [ReportType, Report]).concat([['count', count]]);\n return Object.fromEntries(report);\n }, workers);\n\n executor.error((err) => {\n console.error(err);\n });\n\n return executor;\n};\n"],"names":["createExecutor","workers","warmupCycles","maxCycles","minCycles","absThreshold","relThreshold","reportTypes","executor","queue","setup","teardown","pre","run","post","data","setupCode","toString","teardownCode","preCode","runCode","postCode","controlSAB","SharedArrayBuffer","Int32Array","BYTES_PER_ELEMENT","CONTROL_SLOTS","durationsSAB","BigUint64Array","workerFile","URL","workerData","worker","Worker","exitCode","once","Error","control","count","Control","INDEX","durations","slice","sort","cmp","report","map","type","createReport","concat","Object","fromEntries","error","err","console"],"mappings":";;;;+BAcaA;;;eAAAA;;;oCAdU;4BACF;uBACC;0BACyG;6BAC1F;0BACjB;AASb,MAAMA,iBAAiB,CAA6C,EACzEC,OAAO,EACPC,YAAY,EACZC,SAAS,EACTC,SAAS,EACTC,YAAY,EACZC,YAAY,EACZC,WAAW,EACkB;IAC7B,MAAMC,WAAWC,IAAAA,YAAK,EAA+B,OAAO,EAAEC,KAAK,EAAEC,QAAQ,EAAEC,GAAG,EAAEC,GAAG,EAAEC,IAAI,EAAEC,IAAI,EAAE;QACnG,MAAMC,YAAYN,OAAOO;QACzB,MAAMC,eAAeP,UAAUM;QAC/B,MAAME,UAAUP,KAAKK;QACrB,MAAMG,UAAUP,IAAII,QAAQ;QAC5B,MAAMI,WAAWP,MAAMG;QAEvB,MAAMK,aAAa,IAAIC,kBAAkBC,WAAWC,iBAAiB,GAAGC,uBAAa;QACrF,MAAMC,eAAe,IAAIJ,kBAAkBK,eAAeH,iBAAiB,GAAGtB;QAE9E,MAAM0B,aAAa,IAAIC,IAAI,eAAe;QAC1C,MAAMC,aAA4B;YAChCf;YACAE;YACAC;YACAC;YACAC;YACAN;YAEAb;YACAE;YACAC;YACAC;YAEAgB;YACAK;QACF;QAEA,MAAMK,SAAS,IAAIC,0BAAM,CAACJ,YAAY;YACpCE;QACF;QACA,MAAM,CAACG,SAAS,GAAG,MAAMC,IAAAA,gBAAI,EAACH,QAAQ;QACtC,IAAIE,aAAa,GAAG;YAClB,MAAM,IAAIE,MAAM,CAAC,wBAAwB,EAAEF,UAAU;QACvD;QAEA,MAAMG,UAAU,IAAIb,WAAWF;QAC/B,MAAMgB,QAAQD,OAAO,CAACE,iBAAO,CAACC,KAAK,CAAC;QACpC,MAAMC,YAAY,IAAIb,eAAeD,cAAce,KAAK,CAAC,GAAGJ,OAAOK,IAAI,CAACC,aAAG;QAE3E,MAAMC,SAAStC,YAAYuC,GAAG,CAAoB,CAACC,OAAS;gBAACA;gBAAMC,IAAAA,yBAAY,EAACP,WAAWM;aAAM,EAA0BE,MAAM,CAAC;YAAC;gBAAC;gBAASX;aAAM;SAAC;QACpJ,OAAOY,OAAOC,WAAW,CAACN;IAC5B,GAAG5C;IAEHO,SAAS4C,KAAK,CAAC,CAACC;QACdC,QAAQF,KAAK,CAACC;IAChB;IAEA,OAAO7C;AACT"}
1
+ {"version":3,"sources":["../src/executor.ts"],"sourcesContent":["import { Worker } from 'node:worker_threads';\nimport { once } from 'node:events';\nimport { queue } from 'async';\nimport { pathToFileURL } from 'node:url';\nimport { createReport, Report } from './reporter.js';\nimport { cmp } from './utils.js';\nimport { RunOptions, ReportOptions, WorkerOptions, BenchmarkOptions, Control, ReportType, ReportTypeList, CONTROL_SLOTS } from './types.js';\n\nexport type ExecutorReport<R extends ReportTypeList> = Record<R[number], Report> & { count: number };\n\nexport interface ExecutorOptions<R extends ReportTypeList> extends BenchmarkOptions, ReportOptions<R> {\n baseUrl?: string;\n workers?: number;\n maxCycles?: number;\n}\n\nexport const createExecutor = <TContext, TInput, R extends ReportTypeList>({\n baseUrl = pathToFileURL(process.cwd()).href,\n workers,\n warmupCycles,\n maxCycles,\n minCycles,\n absThreshold,\n relThreshold,\n gcObserver = true,\n reportTypes,\n}: Required<ExecutorOptions<R>>) => {\n const executor = queue<RunOptions<TContext, TInput>>(async ({ baseUrl: runBaseUrl = baseUrl, setup, teardown, pre, run, post, data }) => {\n const setupCode = setup?.toString();\n const teardownCode = teardown?.toString();\n const preCode = pre?.toString();\n const runCode = run.toString()!;\n const postCode = post?.toString();\n\n const controlSAB = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * CONTROL_SLOTS);\n const durationsSAB = new SharedArrayBuffer(BigUint64Array.BYTES_PER_ELEMENT * maxCycles);\n\n const workerFile = new URL('./worker.js', import.meta.url);\n const workerData: WorkerOptions = {\n baseUrl: runBaseUrl,\n setupCode,\n teardownCode,\n preCode,\n runCode,\n postCode,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n gcObserver,\n\n controlSAB,\n durationsSAB,\n };\n\n const worker = new Worker(workerFile, {\n workerData,\n });\n const [exitCode] = await once(worker, 'exit');\n if (exitCode !== 0) {\n throw new Error(`worker exited with code ${exitCode}`);\n }\n\n const control = new Int32Array(controlSAB);\n const count = control[Control.INDEX];\n const durations = new BigUint64Array(durationsSAB).slice(0, count).sort(cmp);\n\n const report = reportTypes.map<[string, unknown]>((type) => [type, createReport(durations, type)] as [ReportType, Report]).concat([['count', count]]);\n return Object.fromEntries(report);\n }, workers);\n\n executor.error((err) => {\n console.error(err);\n });\n\n return executor;\n};\n"],"names":["createExecutor","baseUrl","pathToFileURL","process","cwd","href","workers","warmupCycles","maxCycles","minCycles","absThreshold","relThreshold","gcObserver","reportTypes","executor","queue","runBaseUrl","setup","teardown","pre","run","post","data","setupCode","toString","teardownCode","preCode","runCode","postCode","controlSAB","SharedArrayBuffer","Int32Array","BYTES_PER_ELEMENT","CONTROL_SLOTS","durationsSAB","BigUint64Array","workerFile","URL","workerData","worker","Worker","exitCode","once","Error","control","count","Control","INDEX","durations","slice","sort","cmp","report","map","type","createReport","concat","Object","fromEntries","error","err","console"],"mappings":";;;;+BAgBaA;;;eAAAA;;;oCAhBU;4BACF;uBACC;yBACQ;6BACO;0BACjB;0BAC2G;AAUxH,MAAMA,iBAAiB,CAA6C,EACzEC,UAAUC,IAAAA,sBAAa,EAACC,QAAQC,GAAG,IAAIC,IAAI,EAC3CC,OAAO,EACPC,YAAY,EACZC,SAAS,EACTC,SAAS,EACTC,YAAY,EACZC,YAAY,EACZC,aAAa,IAAI,EACjBC,WAAW,EACkB;IAC7B,MAAMC,WAAWC,IAAAA,YAAK,EAA+B,OAAO,EAAEd,SAASe,aAAaf,OAAO,EAAEgB,KAAK,EAAEC,QAAQ,EAAEC,GAAG,EAAEC,GAAG,EAAEC,IAAI,EAAEC,IAAI,EAAE;QAClI,MAAMC,YAAYN,OAAOO;QACzB,MAAMC,eAAeP,UAAUM;QAC/B,MAAME,UAAUP,KAAKK;QACrB,MAAMG,UAAUP,IAAII,QAAQ;QAC5B,MAAMI,WAAWP,MAAMG;QAEvB,MAAMK,aAAa,IAAIC,kBAAkBC,WAAWC,iBAAiB,GAAGC,uBAAa;QACrF,MAAMC,eAAe,IAAIJ,kBAAkBK,eAAeH,iBAAiB,GAAGxB;QAE9E,MAAM4B,aAAa,IAAIC,IAAI,eAAe;QAC1C,MAAMC,aAA4B;YAChCrC,SAASe;YACTO;YACAE;YACAC;YACAC;YACAC;YACAN;YAEAf;YACAE;YACAC;YACAC;YACAC;YAEAiB;YACAK;QACF;QAEA,MAAMK,SAAS,IAAIC,0BAAM,CAACJ,YAAY;YACpCE;QACF;QACA,MAAM,CAACG,SAAS,GAAG,MAAMC,IAAAA,gBAAI,EAACH,QAAQ;QACtC,IAAIE,aAAa,GAAG;YAClB,MAAM,IAAIE,MAAM,CAAC,wBAAwB,EAAEF,UAAU;QACvD;QAEA,MAAMG,UAAU,IAAIb,WAAWF;QAC/B,MAAMgB,QAAQD,OAAO,CAACE,iBAAO,CAACC,KAAK,CAAC;QACpC,MAAMC,YAAY,IAAIb,eAAeD,cAAce,KAAK,CAAC,GAAGJ,OAAOK,IAAI,CAACC,aAAG;QAE3E,MAAMC,SAASvC,YAAYwC,GAAG,CAAoB,CAACC,OAAS;gBAACA;gBAAMC,IAAAA,yBAAY,EAACP,WAAWM;aAAM,EAA0BE,MAAM,CAAC;YAAC;gBAAC;gBAASX;aAAM;SAAC;QACpJ,OAAOY,OAAOC,WAAW,CAACN;IAC5B,GAAG9C;IAEHQ,SAAS6C,KAAK,CAAC,CAACC;QACdC,QAAQF,KAAK,CAACC;IAChB;IAEA,OAAO9C;AACT"}
@@ -1,10 +1,11 @@
1
- import { RunOptions, ReportOptions, BenchmarkOptions, ReportTypeList } from './types.js';
2
1
  import { Report } from './reporter.js';
2
+ import { RunOptions, ReportOptions, BenchmarkOptions, ReportTypeList } from './types.js';
3
3
  export type ExecutorReport<R extends ReportTypeList> = Record<R[number], Report> & {
4
4
  count: number;
5
5
  };
6
6
  export interface ExecutorOptions<R extends ReportTypeList> extends BenchmarkOptions, ReportOptions<R> {
7
+ baseUrl?: string;
7
8
  workers?: number;
8
9
  maxCycles?: number;
9
10
  }
10
- export declare const createExecutor: <TContext, TInput, R extends ReportTypeList>({ workers, warmupCycles, maxCycles, minCycles, absThreshold, relThreshold, reportTypes, }: Required<ExecutorOptions<R>>) => import("async").QueueObject<RunOptions<TContext, TInput>>;
11
+ export declare const createExecutor: <TContext, TInput, R extends ReportTypeList>({ baseUrl, workers, warmupCycles, maxCycles, minCycles, absThreshold, relThreshold, gcObserver, reportTypes, }: Required<ExecutorOptions<R>>) => import("async").QueueObject<RunOptions<TContext, TInput>>;