overtake 1.3.2 → 2.0.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 +12 -15
  2. package/bin/overtake.js +1 -1
  3. package/build/executor.d.ts +8 -3
  4. package/build/index.d.ts +10 -11
  5. package/build/reporter.d.ts +10 -2
  6. package/build/runner.d.ts +1 -1
  7. package/build/types.d.ts +7 -7
  8. package/build/utils.d.ts +3 -17
  9. package/package.json +8 -26
  10. package/src/__tests__/assert-no-closure.ts +135 -0
  11. package/src/__tests__/benchmark-execute.ts +48 -0
  12. package/src/cli.ts +139 -144
  13. package/src/executor.ts +59 -24
  14. package/src/index.ts +135 -68
  15. package/src/reporter.ts +77 -125
  16. package/src/runner.ts +28 -25
  17. package/src/types.ts +9 -9
  18. package/src/utils.ts +62 -46
  19. package/src/worker.ts +13 -12
  20. package/tsconfig.json +3 -1
  21. package/build/cli.cjs +0 -179
  22. package/build/cli.cjs.map +0 -1
  23. package/build/cli.js +0 -134
  24. package/build/cli.js.map +0 -1
  25. package/build/executor.cjs +0 -116
  26. package/build/executor.cjs.map +0 -1
  27. package/build/executor.js +0 -106
  28. package/build/executor.js.map +0 -1
  29. package/build/gc-watcher.cjs +0 -30
  30. package/build/gc-watcher.cjs.map +0 -1
  31. package/build/gc-watcher.js +0 -20
  32. package/build/gc-watcher.js.map +0 -1
  33. package/build/index.cjs +0 -400
  34. package/build/index.cjs.map +0 -1
  35. package/build/index.js +0 -335
  36. package/build/index.js.map +0 -1
  37. package/build/reporter.cjs +0 -364
  38. package/build/reporter.cjs.map +0 -1
  39. package/build/reporter.js +0 -346
  40. package/build/reporter.js.map +0 -1
  41. package/build/runner.cjs +0 -528
  42. package/build/runner.cjs.map +0 -1
  43. package/build/runner.js +0 -518
  44. package/build/runner.js.map +0 -1
  45. package/build/types.cjs +0 -66
  46. package/build/types.cjs.map +0 -1
  47. package/build/types.js +0 -33
  48. package/build/types.js.map +0 -1
  49. package/build/utils.cjs +0 -121
  50. package/build/utils.cjs.map +0 -1
  51. package/build/utils.js +0 -85
  52. package/build/utils.js.map +0 -1
  53. package/build/worker.cjs +0 -158
  54. package/build/worker.cjs.map +0 -1
  55. package/build/worker.js +0 -113
  56. package/build/worker.js.map +0 -1
package/src/runner.ts CHANGED
@@ -1,13 +1,10 @@
1
1
  import { performance, PerformanceObserver } from 'node:perf_hooks';
2
- import { Options, Control, DURATION_SCALE } from './types.js';
3
- import { GCWatcher } from './gc-watcher.js';
4
- import { StepFn } from './types.js';
5
-
6
- const COMPLETE_VALUE = 100_00;
2
+ import { type Options, Control, DURATION_SCALE, COMPLETE_VALUE, type StepFn } from './types.ts';
3
+ import { GCWatcher } from './gc-watcher.ts';
7
4
 
8
5
  const hr = process.hrtime.bigint.bind(process.hrtime);
9
6
 
10
- const sink = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
7
+ const sink = new Int32Array(1);
11
8
  const consume = (value: unknown) => {
12
9
  let payload = 0;
13
10
  switch (typeof value) {
@@ -32,7 +29,7 @@ const consume = (value: unknown) => {
32
29
  default:
33
30
  payload = -1;
34
31
  }
35
- Atomics.xor(sink, 0, payload);
32
+ sink[0] ^= payload;
36
33
  };
37
34
 
38
35
  const runSync = (run: Function, overhead: bigint) => {
@@ -518,13 +515,23 @@ export const benchmark = async <TContext, TInput>({
518
515
  }
519
516
 
520
517
  let i = 0;
521
- let mean = 0n;
522
- let m2 = 0n;
518
+ const WELFORD_SCALE = 1_000_000n;
519
+ let meanS = 0n;
520
+ let m2S = 0n;
523
521
  const outlierWindow: number[] = [];
524
522
  let skipped = 0;
525
523
  const maxSkipped = maxCycles * 10;
526
524
  let disableFiltering = false;
527
525
 
526
+ const absThScaled = BigInt(Math.round(absThreshold)) * WELFORD_SCALE;
527
+ const absThSq = absThScaled * absThScaled;
528
+ const REL_PRECISION = 1_000_000n;
529
+ const relThBigint = BigInt(Math.round(relThreshold * Number(REL_PRECISION)));
530
+ const relThSq = relThBigint * relThBigint;
531
+ const relPrecSq = REL_PRECISION * REL_PRECISION;
532
+ const Z95_SQ_NUM = 38416n;
533
+ const Z95_SQ_DENOM = 10000n;
534
+
528
535
  while (true) {
529
536
  if (i >= maxCycles) break;
530
537
  if (!disableFiltering && skipped >= maxSkipped) {
@@ -583,26 +590,28 @@ export const benchmark = async <TContext, TInput>({
583
590
  }
584
591
 
585
592
  const durationNumber = Number(sampleDuration);
586
- pushWindow(outlierWindow, durationNumber, OUTLIER_WINDOW);
587
593
  if (!disableFiltering) {
588
594
  const { median, iqr } = medianAndIqr(outlierWindow);
595
+ pushWindow(outlierWindow, durationNumber, OUTLIER_WINDOW);
589
596
  const maxAllowed = median + OUTLIER_IQR_MULTIPLIER * iqr || Number.POSITIVE_INFINITY;
590
597
  if (outlierWindow.length >= 8 && durationNumber > maxAllowed && durationNumber - median > OUTLIER_ABS_THRESHOLD) {
591
598
  skipped++;
592
599
  continue;
593
600
  }
594
601
 
595
- const meanNumber = Number(mean);
602
+ const meanNumber = Number(meanS / WELFORD_SCALE);
596
603
  if (i >= 8 && meanNumber > 0 && durationNumber > OUTLIER_MULTIPLIER * meanNumber && durationNumber - meanNumber > OUTLIER_ABS_THRESHOLD) {
597
604
  skipped++;
598
605
  continue;
599
606
  }
607
+ } else {
608
+ pushWindow(outlierWindow, durationNumber, OUTLIER_WINDOW);
600
609
  }
601
610
 
602
611
  durations[i++] = sampleDuration;
603
- const delta = sampleDuration - mean;
604
- mean += delta / BigInt(i);
605
- m2 += delta * (sampleDuration - mean);
612
+ const deltaS = sampleDuration * WELFORD_SCALE - meanS;
613
+ meanS += deltaS / BigInt(i);
614
+ m2S += deltaS * (sampleDuration * WELFORD_SCALE - meanS);
606
615
 
607
616
  const progress = (i / maxCycles) * COMPLETE_VALUE;
608
617
  if (i % PROGRESS_STRIDE === 0) {
@@ -610,17 +619,11 @@ export const benchmark = async <TContext, TInput>({
610
619
  }
611
620
 
612
621
  if (i >= minCycles) {
613
- const variance = Number(m2) / (i - 1);
614
- const stddev = Math.sqrt(variance);
615
- if (stddev <= Number(absThreshold)) {
616
- break;
617
- }
618
-
619
- const meanNum = Number(mean);
620
- const cov = stddev / (meanNum || 1);
621
- if (cov <= relThreshold) {
622
- break;
623
- }
622
+ if (m2S <= absThSq * BigInt(i - 1)) break;
623
+ // RME convergence: Z95 * sem/mean <= relThreshold
624
+ // Z95^2 * m2S / (n*(n-1)*meanS^2) <= relThreshold^2
625
+ const ni = BigInt(i);
626
+ if (meanS !== 0n && Z95_SQ_NUM * m2S * relPrecSq <= relThSq * ni * (ni - 1n) * meanS * meanS * Z95_SQ_DENOM) break;
624
627
  }
625
628
  }
626
629
 
package/src/types.ts CHANGED
@@ -99,15 +99,15 @@ export interface Options<TContext, TInput> extends RunOptions<TContext, TInput>,
99
99
  controlSAB: SharedArrayBuffer;
100
100
  }
101
101
 
102
- export enum Control {
103
- INDEX,
104
- PROGRESS,
105
- COMPLETE,
106
- HEAP_USED,
107
- }
108
-
109
- export const CONTROL_SLOTS = Object.values(Control).length / 2;
110
- export const DEFAULT_CYCLES = 1_000;
102
+ export const Control = {
103
+ INDEX: 0,
104
+ PROGRESS: 1,
105
+ COMPLETE: 2,
106
+ HEAP_USED: 3,
107
+ } as const;
108
+
109
+ export const CONTROL_SLOTS = Object.keys(Control).length;
110
+ export const DEFAULT_CYCLES = 10_000;
111
111
  export const Z95 = 1.96;
112
112
  export const DURATION_SCALE = 1000n;
113
113
  export const COMPLETE_VALUE = 100_00;
package/src/utils.ts CHANGED
@@ -1,11 +1,31 @@
1
- import { transform } from '@swc/core';
1
+ import { parseSync } from '@swc/core';
2
2
 
3
- export const abs = (value: bigint) => {
4
- if (value < 0n) {
5
- return -value;
3
+ async function resolve(s: string, c: unknown, n: (...args: unknown[]) => unknown) {
4
+ try {
5
+ return await n(s, c);
6
+ } catch (e) {
7
+ if (s.endsWith('.js'))
8
+ try {
9
+ return await n(s.slice(0, -3) + '.ts', c);
10
+ } catch {}
11
+ throw e;
6
12
  }
7
- return value;
13
+ }
14
+
15
+ export const resolveHookUrl = 'data:text/javascript,' + encodeURIComponent(`export ${resolve.toString()}`);
16
+
17
+ export const isqrt = (n: bigint): bigint => {
18
+ if (n < 0n) throw new RangeError('Square root of negative');
19
+ if (n < 2n) return n;
20
+ let x = n;
21
+ let y = (x + 1n) >> 1n;
22
+ while (y < x) {
23
+ x = y;
24
+ y = (x + n / x) >> 1n;
25
+ }
26
+ return x;
8
27
  };
28
+
9
29
  export const cmp = (a: bigint | number, b: bigint | number): number => {
10
30
  if (a > b) {
11
31
  return 1;
@@ -23,10 +43,6 @@ export const max = (a: bigint, b: bigint) => {
23
43
  return b;
24
44
  };
25
45
 
26
- export const divMod = (a: bigint, b: bigint) => {
27
- return { quotient: a / b, remainder: a % b };
28
- };
29
-
30
46
  export function div(a: bigint, b: bigint, decimals: number = 2): string {
31
47
  if (b === 0n) throw new RangeError('Division by zero');
32
48
  const scale = 10n ** BigInt(decimals);
@@ -41,45 +57,45 @@ export function divs(a: bigint, b: bigint, scale: bigint): bigint {
41
57
  return (a * scale) / b;
42
58
  }
43
59
 
44
- export class ScaledBigInt {
45
- constructor(
46
- public value: bigint,
47
- public scale: bigint,
48
- ) {}
49
- add(value: bigint) {
50
- this.value += value * this.scale;
51
- }
52
- sub(value: bigint) {
53
- this.value -= value * this.scale;
54
- }
55
- div(value: bigint) {
56
- this.value /= value;
57
- }
58
- mul(value: bigint) {
59
- this.value *= value;
60
+ const KNOWN_GLOBALS = new Set(Object.getOwnPropertyNames(globalThis));
61
+ KNOWN_GLOBALS.add('arguments');
62
+
63
+ function collectUnresolved(node: unknown, result: Set<string>) {
64
+ if (!node || typeof node !== 'object') return;
65
+ if (Array.isArray(node)) {
66
+ for (const item of node) collectUnresolved(item, result);
67
+ return;
60
68
  }
61
- unscale() {
62
- return this.value / this.scale;
69
+ const obj = node as Record<string, unknown>;
70
+ if (obj.type === 'Identifier' && obj.ctxt === 1 && typeof obj.value === 'string') {
71
+ result.add(obj.value);
63
72
  }
64
- number() {
65
- return Number(div(this.value, this.scale));
73
+ for (const key of Object.keys(obj)) {
74
+ if (key === 'span') continue;
75
+ collectUnresolved(obj[key], result);
66
76
  }
67
77
  }
68
78
 
69
- export const transpile = async (code: string): Promise<string> => {
70
- const output = await transform(code, {
71
- filename: 'benchmark.ts',
72
- jsc: {
73
- parser: {
74
- syntax: 'typescript',
75
- tsx: false,
76
- dynamicImport: true,
77
- },
78
- target: 'esnext',
79
- },
80
- module: {
81
- type: 'es6',
82
- },
83
- });
84
- return output.code;
85
- };
79
+ export function assertNoClosure(code: string, name: string): void {
80
+ let ast;
81
+ try {
82
+ ast = parseSync(`var __fn = ${code}`, { syntax: 'ecmascript', target: 'esnext' });
83
+ } catch {
84
+ return;
85
+ }
86
+ const unresolved = new Set<string>();
87
+ collectUnresolved(ast, unresolved);
88
+ for (const g of KNOWN_GLOBALS) unresolved.delete(g);
89
+ if (unresolved.size === 0) return;
90
+
91
+ const vars = [...unresolved].join(', ');
92
+ throw new Error(
93
+ `Benchmark "${name}" function references outer-scope variables: ${vars}\n\n` +
94
+ `Benchmark functions are serialized with .toString() and executed in an isolated\n` +
95
+ `worker thread. Closed-over variables from the original module scope are not\n` +
96
+ `available in the worker and will cause a ReferenceError at runtime.\n\n` +
97
+ `To fix this, move the referenced values into:\n` +
98
+ ` - "setup" function (returned value becomes the first argument of run/pre/post)\n` +
99
+ ` - "data" option (passed as the second argument of run/pre/post)`,
100
+ );
101
+ }
package/src/worker.ts CHANGED
@@ -1,10 +1,13 @@
1
1
  import { workerData } from 'node:worker_threads';
2
- import { SourceTextModule, SyntheticModule, createContext } from 'node:vm';
3
- import { createRequire } from 'node:module';
2
+ import { SourceTextModule, SyntheticModule } from 'node:vm';
3
+ import { createRequire, register } from 'node:module';
4
4
  import { isAbsolute } from 'node:path';
5
5
  import { fileURLToPath, pathToFileURL } from 'node:url';
6
- import { benchmark } from './runner.js';
7
- import { WorkerOptions } from './types.js';
6
+ import { benchmark } from './runner.ts';
7
+ import { type WorkerOptions } from './types.ts';
8
+ import { resolveHookUrl } from './utils.ts';
9
+
10
+ register(resolveHookUrl);
8
11
 
9
12
  const {
10
13
  benchmarkUrl,
@@ -41,7 +44,11 @@ const resolveSpecifier = (specifier: string) => {
41
44
  if (isAbsolute(specifier)) {
42
45
  return pathToFileURL(specifier).href;
43
46
  }
44
- return requireFrom.resolve(specifier);
47
+ try {
48
+ return requireFrom.resolve(specifier);
49
+ } catch {
50
+ return specifier;
51
+ }
45
52
  };
46
53
 
47
54
  const source = `
@@ -52,11 +59,6 @@ export const run = ${serialize(runCode)};
52
59
  export const post = ${serialize(postCode)};
53
60
  `;
54
61
 
55
- const globals = Object.create(null);
56
- for (const k of Object.getOwnPropertyNames(globalThis)) {
57
- globals[k] = (globalThis as any)[k];
58
- }
59
- const context = createContext(globals);
60
62
  const imports = new Map<string, SyntheticModule>();
61
63
 
62
64
  const createSyntheticModule = (moduleExports: unknown, exportNames: string[], identifier: string) => {
@@ -71,7 +73,7 @@ const createSyntheticModule = (moduleExports: unknown, exportNames: string[], id
71
73
  mod.setExport(name, (moduleExports as Record<string, unknown>)[name]);
72
74
  }
73
75
  },
74
- { identifier, context },
76
+ { identifier },
75
77
  );
76
78
  return mod;
77
79
  };
@@ -111,7 +113,6 @@ const loadDynamicModule = async (target: string) => {
111
113
  };
112
114
  const mod = new SourceTextModule(source, {
113
115
  identifier: resolvedBenchmarkUrl,
114
- context,
115
116
  initializeImportMeta(meta) {
116
117
  meta.url = resolvedBenchmarkUrl;
117
118
  },
package/tsconfig.json CHANGED
@@ -8,10 +8,12 @@
8
8
  "resolveJsonModule": true,
9
9
  "allowJs": true,
10
10
  "moduleResolution": "NodeNext",
11
+ "allowImportingTsExtensions": true,
12
+ "verbatimModuleSyntax": true,
11
13
  "lib": ["ESNext"],
12
14
  "rootDir": "src",
13
15
  "outDir": "build"
14
16
  },
15
17
  "include": ["src/**/*.ts"],
16
- "exclude": ["node_modules", "**/*.tmp.ts"]
18
+ "exclude": ["node_modules", "**/__tests__", "**/*.tmp.ts"]
17
19
  }
package/build/cli.cjs DELETED
@@ -1,179 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- const _nodemodule = require("node:module");
6
- const _nodeurl = require("node:url");
7
- const _nodevm = require("node:vm");
8
- const _promises = require("node:fs/promises");
9
- const _commander = require("commander");
10
- const _glob = require("glob");
11
- const _indexcjs = require("./index.cjs");
12
- const _utilscjs = require("./utils.cjs");
13
- const _typescjs = require("./types.cjs");
14
- function _getRequireWildcardCache(nodeInterop) {
15
- if (typeof WeakMap !== "function") return null;
16
- var cacheBabelInterop = new WeakMap();
17
- var cacheNodeInterop = new WeakMap();
18
- return (_getRequireWildcardCache = function(nodeInterop) {
19
- return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
20
- })(nodeInterop);
21
- }
22
- function _interop_require_wildcard(obj, nodeInterop) {
23
- if (!nodeInterop && obj && obj.__esModule) {
24
- return obj;
25
- }
26
- if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
27
- return {
28
- default: obj
29
- };
30
- }
31
- var cache = _getRequireWildcardCache(nodeInterop);
32
- if (cache && cache.has(obj)) {
33
- return cache.get(obj);
34
- }
35
- var newObj = {
36
- __proto__: null
37
- };
38
- var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
39
- for(var key in obj){
40
- if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
41
- var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
42
- if (desc && (desc.get || desc.set)) {
43
- Object.defineProperty(newObj, key, desc);
44
- } else {
45
- newObj[key] = obj[key];
46
- }
47
- }
48
- }
49
- newObj.default = obj;
50
- if (cache) {
51
- cache.set(obj, newObj);
52
- }
53
- return newObj;
54
- }
55
- const require1 = (0, _nodemodule.createRequire)(require("url").pathToFileURL(__filename).toString());
56
- const { name, description, version } = require1('../package.json');
57
- const BENCHMARK_URL = Symbol.for('overtake.benchmarkUrl');
58
- const commander = new _commander.Command();
59
- 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([
60
- 'simple',
61
- 'json',
62
- 'pjson',
63
- 'table',
64
- 'markdown',
65
- 'histogram'
66
- ])).addOption(new _commander.Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseFloat)).addOption(new _commander.Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseFloat)).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')).addOption(new _commander.Option('--progress', 'show progress bar during benchmark execution')).addOption(new _commander.Option('--save-baseline <file>', 'save benchmark results to baseline file')).addOption(new _commander.Option('--compare-baseline <file>', 'compare results against baseline file')).action(async (patterns, executeOptions)=>{
67
- let baseline = null;
68
- if (executeOptions.compareBaseline) {
69
- try {
70
- const content = await (0, _promises.readFile)(executeOptions.compareBaseline, 'utf8');
71
- baseline = JSON.parse(content);
72
- } catch {
73
- console.error(`Warning: Could not load baseline file: ${executeOptions.compareBaseline}`);
74
- }
75
- }
76
- const files = new Set();
77
- await Promise.all(patterns.map(async (pattern)=>{
78
- const matches = await (0, _glob.glob)(pattern, {
79
- absolute: true,
80
- cwd: process.cwd()
81
- }).catch(()=>[]);
82
- matches.forEach((file)=>files.add(file));
83
- }));
84
- for (const file of files){
85
- const stats = await (0, _promises.stat)(file).catch(()=>false);
86
- if (stats && stats.isFile()) {
87
- const content = await (0, _promises.readFile)(file, 'utf8');
88
- const identifier = (0, _nodeurl.pathToFileURL)(file).href;
89
- const code = await (0, _utilscjs.transpile)(content);
90
- let instance;
91
- const benchmark = (...args)=>{
92
- if (instance) {
93
- throw new Error('Only one benchmark per file is supported');
94
- }
95
- instance = _indexcjs.Benchmark.create(...args);
96
- return instance;
97
- };
98
- const globals = Object.create(null);
99
- for (const k of Object.getOwnPropertyNames(globalThis)){
100
- globals[k] = globalThis[k];
101
- }
102
- globals.benchmark = benchmark;
103
- const script = new _nodevm.SourceTextModule(code, {
104
- identifier,
105
- context: (0, _nodevm.createContext)(globals),
106
- initializeImportMeta (meta) {
107
- meta.url = identifier;
108
- },
109
- async importModuleDynamically (specifier, referencingModule) {
110
- if (_nodemodule.Module.isBuiltin(specifier)) {
111
- return Promise.resolve(specifier).then((p)=>/*#__PURE__*/ _interop_require_wildcard(require(p)));
112
- }
113
- const baseIdentifier = referencingModule.identifier ?? identifier;
114
- const resolveFrom = (0, _nodemodule.createRequire)((0, _nodeurl.fileURLToPath)(baseIdentifier));
115
- const resolved = resolveFrom.resolve(specifier);
116
- return Promise.resolve(resolved).then((p)=>/*#__PURE__*/ _interop_require_wildcard(require(p)));
117
- }
118
- });
119
- const imports = new Map();
120
- await script.link(async (specifier, referencingModule)=>{
121
- const baseIdentifier = referencingModule.identifier ?? identifier;
122
- const resolveFrom = (0, _nodemodule.createRequire)((0, _nodeurl.fileURLToPath)(baseIdentifier));
123
- const target = _nodemodule.Module.isBuiltin(specifier) ? specifier : resolveFrom.resolve(specifier);
124
- const cached = imports.get(target);
125
- if (cached) {
126
- return cached;
127
- }
128
- const mod = await Promise.resolve(target).then((p)=>/*#__PURE__*/ _interop_require_wildcard(require(p)));
129
- const exportNames = Object.keys(mod);
130
- const imported = new _nodevm.SyntheticModule(exportNames, ()=>{
131
- exportNames.forEach((key)=>imported.setExport(key, mod[key]));
132
- }, {
133
- identifier: target,
134
- context: referencingModule.context
135
- });
136
- imports.set(target, imported);
137
- return imported;
138
- });
139
- await script.evaluate();
140
- if (instance) {
141
- const reports = await instance.execute({
142
- ...executeOptions,
143
- [BENCHMARK_URL]: identifier
144
- });
145
- if (executeOptions.saveBaseline) {
146
- const baselineData = (0, _indexcjs.reportsToBaseline)(reports);
147
- await (0, _promises.writeFile)(executeOptions.saveBaseline, JSON.stringify(baselineData, null, 2));
148
- console.log(`Baseline saved to: ${executeOptions.saveBaseline}`);
149
- }
150
- if (baseline) {
151
- (0, _indexcjs.printComparisonReports)(reports, baseline);
152
- } else {
153
- switch(executeOptions.format){
154
- case 'json':
155
- (0, _indexcjs.printJSONReports)(reports);
156
- break;
157
- case 'pjson':
158
- (0, _indexcjs.printJSONReports)(reports, 2);
159
- break;
160
- case 'table':
161
- (0, _indexcjs.printTableReports)(reports);
162
- break;
163
- case 'markdown':
164
- (0, _indexcjs.printMarkdownReports)(reports);
165
- break;
166
- case 'histogram':
167
- (0, _indexcjs.printHistogramReports)(reports);
168
- break;
169
- default:
170
- (0, _indexcjs.printSimpleReports)(reports);
171
- }
172
- }
173
- }
174
- }
175
- }
176
- });
177
- commander.parse(process.argv);
178
-
179
- //# sourceMappingURL=cli.cjs.map
package/build/cli.cjs.map DELETED
@@ -1 +0,0 @@
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, writeFile } from 'node:fs/promises';\nimport { Command, Option } from 'commander';\nimport { glob } from 'glob';\nimport {\n Benchmark,\n printTableReports,\n printJSONReports,\n printSimpleReports,\n printMarkdownReports,\n printHistogramReports,\n printComparisonReports,\n reportsToBaseline,\n BaselineData,\n DEFAULT_REPORT_TYPES,\n DEFAULT_WORKERS,\n} 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');\nconst BENCHMARK_URL = Symbol.for('overtake.benchmarkUrl');\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', 'markdown', 'histogram']))\n .addOption(new Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseFloat))\n .addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseFloat))\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 .addOption(new Option('--progress', 'show progress bar during benchmark execution'))\n .addOption(new Option('--save-baseline <file>', 'save benchmark results to baseline file'))\n .addOption(new Option('--compare-baseline <file>', 'compare results against baseline file'))\n .action(async (patterns: string[], executeOptions) => {\n let baseline: BaselineData | null = null;\n if (executeOptions.compareBaseline) {\n try {\n const content = await readFile(executeOptions.compareBaseline, 'utf8');\n baseline = JSON.parse(content) as BaselineData;\n } catch {\n console.error(`Warning: Could not load baseline file: ${executeOptions.compareBaseline}`);\n }\n }\n\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 globals = Object.create(null);\n for (const k of Object.getOwnPropertyNames(globalThis)) {\n globals[k] = (globalThis as any)[k];\n }\n globals.benchmark = benchmark;\n const script = new SourceTextModule(code, {\n identifier,\n context: createContext(globals),\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 [BENCHMARK_URL]: identifier,\n } as typeof executeOptions);\n\n if (executeOptions.saveBaseline) {\n const baselineData = reportsToBaseline(reports);\n await writeFile(executeOptions.saveBaseline, JSON.stringify(baselineData, null, 2));\n console.log(`Baseline saved to: ${executeOptions.saveBaseline}`);\n }\n\n if (baseline) {\n printComparisonReports(reports, baseline);\n } else {\n switch (executeOptions.format) {\n case 'json':\n printJSONReports(reports);\n break;\n case 'pjson':\n printJSONReports(reports, 2);\n break;\n case 'table':\n printTableReports(reports);\n break;\n case 'markdown':\n printMarkdownReports(reports);\n break;\n case 'histogram':\n printHistogramReports(reports);\n break;\n default:\n printSimpleReports(reports);\n }\n }\n }\n }\n }\n });\n\ncommander.parse(process.argv);\n"],"names":["require","createRequire","name","description","version","BENCHMARK_URL","Symbol","for","commander","Command","argument","addOption","Option","choices","REPORT_TYPES","default","DEFAULT_REPORT_TYPES","DEFAULT_WORKERS","argParser","parseInt","parseFloat","action","patterns","executeOptions","baseline","compareBaseline","content","readFile","JSON","parse","console","error","files","Set","Promise","all","map","pattern","matches","glob","absolute","cwd","process","catch","forEach","file","add","stats","stat","isFile","identifier","pathToFileURL","href","code","transpile","instance","benchmark","args","Error","Benchmark","create","globals","Object","k","getOwnPropertyNames","globalThis","script","SourceTextModule","context","createContext","initializeImportMeta","meta","url","importModuleDynamically","specifier","referencingModule","Module","isBuiltin","baseIdentifier","resolveFrom","fileURLToPath","resolved","resolve","imports","Map","link","target","cached","get","mod","exportNames","keys","imported","SyntheticModule","key","setExport","set","evaluate","reports","execute","saveBaseline","baselineData","reportsToBaseline","writeFile","stringify","log","printComparisonReports","format","printJSONReports","printTableReports","printMarkdownReports","printHistogramReports","printSimpleReports","argv"],"mappings":"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"}