overtake 1.0.5 → 1.1.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 (57) hide show
  1. package/README.md +25 -29
  2. package/build/cli.cjs +43 -33
  3. package/build/cli.cjs.map +1 -1
  4. package/build/cli.js +42 -32
  5. package/build/cli.js.map +1 -1
  6. package/build/executor.cjs +6 -3
  7. package/build/executor.cjs.map +1 -1
  8. package/build/executor.d.ts +3 -2
  9. package/build/executor.js +6 -3
  10. package/build/executor.js.map +1 -1
  11. package/build/gc-watcher.cjs +31 -0
  12. package/build/gc-watcher.cjs.map +1 -0
  13. package/build/gc-watcher.d.ts +9 -0
  14. package/build/gc-watcher.js +21 -0
  15. package/build/gc-watcher.js.map +1 -0
  16. package/build/index.cjs +9 -1
  17. package/build/index.cjs.map +1 -1
  18. package/build/index.d.ts +1 -1
  19. package/build/index.js +9 -1
  20. package/build/index.js.map +1 -1
  21. package/build/runner.cjs +229 -24
  22. package/build/runner.cjs.map +1 -1
  23. package/build/runner.d.ts +1 -1
  24. package/build/runner.js +229 -24
  25. package/build/runner.js.map +1 -1
  26. package/build/types.cjs.map +1 -1
  27. package/build/types.d.ts +4 -0
  28. package/build/types.js.map +1 -1
  29. package/build/utils.cjs +21 -0
  30. package/build/utils.cjs.map +1 -1
  31. package/build/utils.d.ts +1 -0
  32. package/build/utils.js +18 -0
  33. package/build/utils.js.map +1 -1
  34. package/build/worker.cjs +104 -14
  35. package/build/worker.cjs.map +1 -1
  36. package/build/worker.d.ts +1 -1
  37. package/build/worker.js +63 -8
  38. package/build/worker.js.map +1 -1
  39. package/examples/accuracy.ts +54 -0
  40. package/examples/custom-reports.ts +0 -1
  41. package/examples/imports.ts +3 -7
  42. package/examples/quick-start.ts +2 -0
  43. package/package.json +11 -10
  44. package/src/cli.ts +44 -31
  45. package/src/executor.ts +8 -2
  46. package/src/gc-watcher.ts +23 -0
  47. package/src/index.ts +11 -0
  48. package/src/runner.ts +269 -23
  49. package/src/types.ts +4 -0
  50. package/src/utils.ts +20 -0
  51. package/src/worker.ts +72 -9
  52. package/build/queue.cjs +0 -48
  53. package/build/queue.cjs.map +0 -1
  54. package/build/queue.d.ts +0 -3
  55. package/build/queue.js +0 -38
  56. package/build/queue.js.map +0 -1
  57. package/src/queue.ts +0 -42
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "GCWatcher", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return GCWatcher;
9
+ }
10
+ });
11
+ class GCWatcher {
12
+ #registry = new FinalizationRegistry(()=>{});
13
+ start() {
14
+ const token = {};
15
+ const ref = new WeakRef(token);
16
+ this.#registry.register(token, null, token);
17
+ return {
18
+ ref,
19
+ token
20
+ };
21
+ }
22
+ seen(marker) {
23
+ const collected = marker.ref.deref() === undefined;
24
+ if (!collected) {
25
+ this.#registry.unregister(marker.token);
26
+ }
27
+ return collected;
28
+ }
29
+ }
30
+
31
+ //# sourceMappingURL=gc-watcher.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/gc-watcher.ts"],"sourcesContent":["export interface GCMarker {\n ref: WeakRef<object>;\n token: object;\n}\n\nexport class GCWatcher {\n #registry = new FinalizationRegistry(() => {});\n\n start(): GCMarker {\n const token = {};\n const ref = new WeakRef(token);\n this.#registry.register(token, null, token);\n return { ref, token };\n }\n\n seen(marker: GCMarker): boolean {\n const collected = marker.ref.deref() === undefined;\n if (!collected) {\n this.#registry.unregister(marker.token);\n }\n return collected;\n }\n}\n"],"names":["GCWatcher","FinalizationRegistry","start","token","ref","WeakRef","register","seen","marker","collected","deref","undefined","unregister"],"mappings":";;;;+BAKaA;;;eAAAA;;;AAAN,MAAMA;IACX,CAAA,QAAS,GAAG,IAAIC,qBAAqB,KAAO,GAAG;IAE/CC,QAAkB;QAChB,MAAMC,QAAQ,CAAC;QACf,MAAMC,MAAM,IAAIC,QAAQF;QACxB,IAAI,CAAC,CAAA,QAAS,CAACG,QAAQ,CAACH,OAAO,MAAMA;QACrC,OAAO;YAAEC;YAAKD;QAAM;IACtB;IAEAI,KAAKC,MAAgB,EAAW;QAC9B,MAAMC,YAAYD,OAAOJ,GAAG,CAACM,KAAK,OAAOC;QACzC,IAAI,CAACF,WAAW;YACd,IAAI,CAAC,CAAA,QAAS,CAACG,UAAU,CAACJ,OAAOL,KAAK;QACxC;QACA,OAAOM;IACT;AACF"}
@@ -0,0 +1,9 @@
1
+ export interface GCMarker {
2
+ ref: WeakRef<object>;
3
+ token: object;
4
+ }
5
+ export declare class GCWatcher {
6
+ #private;
7
+ start(): GCMarker;
8
+ seen(marker: GCMarker): boolean;
9
+ }
@@ -0,0 +1,21 @@
1
+ export class GCWatcher {
2
+ #registry = new FinalizationRegistry(()=>{});
3
+ start() {
4
+ const token = {};
5
+ const ref = new WeakRef(token);
6
+ this.#registry.register(token, null, token);
7
+ return {
8
+ ref,
9
+ token
10
+ };
11
+ }
12
+ seen(marker) {
13
+ const collected = marker.ref.deref() === undefined;
14
+ if (!collected) {
15
+ this.#registry.unregister(marker.token);
16
+ }
17
+ return collected;
18
+ }
19
+ }
20
+
21
+ //# sourceMappingURL=gc-watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/gc-watcher.ts"],"sourcesContent":["export interface GCMarker {\n ref: WeakRef<object>;\n token: object;\n}\n\nexport class GCWatcher {\n #registry = new FinalizationRegistry(() => {});\n\n start(): GCMarker {\n const token = {};\n const ref = new WeakRef(token);\n this.#registry.register(token, null, token);\n return { ref, token };\n }\n\n seen(marker: GCMarker): boolean {\n const collected = marker.ref.deref() === undefined;\n if (!collected) {\n this.#registry.unregister(marker.token);\n }\n return collected;\n }\n}\n"],"names":["GCWatcher","FinalizationRegistry","start","token","ref","WeakRef","register","seen","marker","collected","deref","undefined","unregister"],"mappings":"AAKA,OAAO,MAAMA;IACX,CAAA,QAAS,GAAG,IAAIC,qBAAqB,KAAO,GAAG;IAE/CC,QAAkB;QAChB,MAAMC,QAAQ,CAAC;QACf,MAAMC,MAAM,IAAIC,QAAQF;QACxB,IAAI,CAAC,CAAA,QAAS,CAACG,QAAQ,CAACH,OAAO,MAAMA;QACrC,OAAO;YAAEC;YAAKD;QAAM;IACtB;IAEAI,KAAKC,MAAgB,EAAW;QAC9B,MAAMC,YAAYD,OAAOJ,GAAG,CAACM,KAAK,OAAOC;QACzC,IAAI,CAACF,WAAW;YACd,IAAI,CAAC,CAAA,QAAS,CAACG,UAAU,CAACJ,OAAOL,KAAK;QACxC;QACA,OAAOM;IACT;AACF"}
package/build/index.cjs CHANGED
@@ -47,6 +47,7 @@ _export(exports, {
47
47
  }
48
48
  });
49
49
  const _nodeos = require("node:os");
50
+ const _nodeurl = require("node:url");
50
51
  const _executorcjs = require("./executor.cjs");
51
52
  const _typescjs = require("./types.cjs");
52
53
  const DEFAULT_WORKERS = (0, _nodeos.cpus)().length;
@@ -139,18 +140,24 @@ class Benchmark {
139
140
  this.#targets.push(target);
140
141
  return new Target(target);
141
142
  }
142
- async execute({ workers = DEFAULT_WORKERS, warmupCycles = 20, maxCycles = _typescjs.DEFAULT_CYCLES, minCycles = 50, absThreshold = 1_000, relThreshold = 0.02, reportTypes = DEFAULT_REPORT_TYPES }) {
143
+ async execute({ workers = DEFAULT_WORKERS, warmupCycles = 20, maxCycles = _typescjs.DEFAULT_CYCLES, minCycles = 50, absThreshold = 1_000, relThreshold = 0.02, gcObserver = true, reportTypes = DEFAULT_REPORT_TYPES, baseUrl }) {
143
144
  if (this.#executed) {
144
145
  throw new Error("Benchmark is executed and can't be reused");
145
146
  }
146
147
  this.#executed = true;
148
+ const resolvedBaseUrl = baseUrl ?? (0, _nodeurl.pathToFileURL)(process.cwd()).href;
149
+ if (!baseUrl) {
150
+ console.warn("Overtake: baseUrl not provided; defaulting to process.cwd(). Pass the benchmark's import.meta.url so relative imports resolve correctly.");
151
+ }
147
152
  const executor = (0, _executorcjs.createExecutor)({
153
+ baseUrl: resolvedBaseUrl,
148
154
  workers,
149
155
  warmupCycles,
150
156
  maxCycles,
151
157
  minCycles,
152
158
  absThreshold,
153
159
  relThreshold,
160
+ gcObserver,
154
161
  reportTypes
155
162
  });
156
163
  const reports = [];
@@ -167,6 +174,7 @@ class Benchmark {
167
174
  for (const feed of this.#feeds){
168
175
  const data = await feed.fn?.();
169
176
  executor.push({
177
+ baseUrl: resolvedBaseUrl,
170
178
  setup: target.setup,
171
179
  teardown: target.teardown,
172
180
  pre: measure.pre,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { cpus } from 'node:os';\nimport { createExecutor, ExecutorOptions, ExecutorReport } from './executor.js';\nimport { MaybePromise, StepFn, SetupFn, TeardownFn, FeedFn, ReportType, ReportTypeList, DEFAULT_CYCLES } from './types.js';\n\ndeclare global {\n const benchmark: typeof Benchmark.create;\n}\n\nexport const DEFAULT_WORKERS = cpus().length;\n\nexport const AsyncFunction = (async () => {}).constructor;\n\nexport interface TargetReport<R extends ReportTypeList> {\n target: string;\n measures: MeasureReport<R>[];\n}\n\nexport interface MeasureReport<R extends ReportTypeList> {\n measure: string;\n feeds: FeedReport<R>[];\n}\n\nexport interface FeedReport<R extends ReportTypeList> {\n feed: string;\n data: ExecutorReport<R>;\n}\n\nexport const DEFAULT_REPORT_TYPES = ['ops'] as const;\nexport type DefaultReportTypes = (typeof DEFAULT_REPORT_TYPES)[number];\n\nexport class MeasureContext<TContext, TInput> {\n public pre?: StepFn<TContext, TInput>;\n public post?: StepFn<TContext, TInput>;\n\n constructor(\n public title: string,\n public run: StepFn<TContext, TInput>,\n ) {}\n}\n\nexport class Measure<TContext, TInput> {\n #ctx: MeasureContext<TContext, TInput>;\n\n constructor(ctx: MeasureContext<TContext, TInput>) {\n this.#ctx = ctx;\n }\n\n pre(fn: StepFn<TContext, TInput>): Measure<TContext, TInput> {\n this.#ctx.pre = fn;\n return this;\n }\n post(fn: StepFn<TContext, TInput>): Measure<TContext, TInput> {\n this.#ctx.post = fn;\n return this;\n }\n}\n\nexport class TargetContext<TContext, TInput> {\n public teardown?: TeardownFn<TContext>;\n public measures: MeasureContext<TContext, TInput>[] = [];\n\n constructor(\n readonly title: string,\n readonly setup?: SetupFn<MaybePromise<TContext>>,\n ) {}\n}\n\nexport class Target<TContext, TInput> {\n #ctx: TargetContext<TContext, TInput>;\n\n constructor(ctx: TargetContext<TContext, TInput>) {\n this.#ctx = ctx;\n }\n teardown(fn: TeardownFn<TContext>): Target<TContext, TInput> {\n this.#ctx.teardown = fn;\n\n return this;\n }\n measure(title: string, run: StepFn<TContext, TInput>): Measure<TContext, TInput> {\n const measure = new MeasureContext(title, run);\n this.#ctx.measures.push(measure);\n\n return new Measure(measure);\n }\n}\n\nexport class FeedContext<TInput> {\n constructor(\n readonly title: string,\n readonly fn?: FeedFn<TInput>,\n ) {}\n}\n\nexport class Benchmark<TInput> {\n #targets: TargetContext<unknown, TInput>[] = [];\n #feeds: FeedContext<TInput>[] = [];\n #executed = false;\n\n static create(title: string): Benchmark<void>;\n static create<I>(title: string, fn: FeedFn<I>): Benchmark<I>;\n static create<I>(title: string, fn?: FeedFn<I> | undefined): Benchmark<I> {\n if (fn) {\n return new Benchmark(title, fn);\n } else {\n return new Benchmark(title);\n }\n }\n\n constructor(title: string);\n constructor(title: string, fn: FeedFn<TInput>);\n constructor(title: string, fn?: FeedFn<TInput> | undefined) {\n if (fn) {\n this.feed(title, fn);\n } else {\n this.feed(title);\n }\n }\n\n feed(title: string): Benchmark<TInput | void>;\n feed<I>(title: string, fn: FeedFn<I>): Benchmark<TInput | I>;\n feed<I>(title: string, fn?: FeedFn<I> | undefined): Benchmark<TInput | I> {\n const self = this as Benchmark<TInput | I>;\n self.#feeds.push(fn ? new FeedContext(title, fn) : new FeedContext(title));\n\n return self;\n }\n\n target<TContext>(title: string): Target<void, TInput>;\n target<TContext>(title: string, setup: SetupFn<Awaited<TContext>>): Target<TContext, TInput>;\n target<TContext>(title: string, setup?: SetupFn<Awaited<TContext>> | undefined): Target<TContext, TInput> {\n const target = new TargetContext<TContext, TInput>(title, setup);\n this.#targets.push(target as TargetContext<unknown, TInput>);\n\n return new Target<TContext, TInput>(target);\n }\n\n async execute<R extends readonly ReportType[] = typeof DEFAULT_REPORT_TYPES>({\n workers = DEFAULT_WORKERS,\n warmupCycles = 20,\n maxCycles = DEFAULT_CYCLES,\n minCycles = 50,\n absThreshold = 1_000,\n relThreshold = 0.02,\n reportTypes = DEFAULT_REPORT_TYPES as unknown as R,\n }: ExecutorOptions<R>): Promise<TargetReport<R>[]> {\n if (this.#executed) {\n throw new Error(\"Benchmark is executed and can't be reused\");\n }\n this.#executed = true;\n\n const executor = createExecutor<unknown, TInput, R>({\n workers,\n warmupCycles,\n maxCycles,\n minCycles,\n absThreshold,\n relThreshold,\n reportTypes,\n });\n\n const reports: TargetReport<R>[] = [];\n for (const target of this.#targets) {\n const targetReport: TargetReport<R> = { target: target.title, measures: [] };\n for (const measure of target.measures) {\n const measureReport: MeasureReport<R> = { measure: measure.title, feeds: [] };\n for (const feed of this.#feeds) {\n const data = await feed.fn?.();\n executor\n .push<ExecutorReport<R>>({\n setup: target.setup,\n teardown: target.teardown,\n pre: measure.pre,\n run: measure.run,\n post: measure.post,\n data,\n })\n .then((data) => {\n measureReport.feeds.push({\n feed: feed.title,\n data,\n });\n });\n }\n targetReport.measures.push(measureReport);\n }\n reports.push(targetReport);\n }\n await executor.drain();\n executor.kill();\n\n return reports;\n }\n}\n\nexport const printSimpleReports = <R extends ReportTypeList>(reports: TargetReport<R>[]) => {\n for (const report of reports) {\n for (const { measure, feeds } of report.measures) {\n console.group('\\n', report.target, measure);\n for (const { feed, data } of feeds) {\n const output = Object.entries(data)\n .map(([key, report]) => `${key}: ${report.toString()}`)\n .join('; ');\n console.log(feed, output);\n }\n console.groupEnd();\n }\n }\n};\n\nexport const printTableReports = <R extends ReportTypeList>(reports: TargetReport<R>[]) => {\n for (const report of reports) {\n for (const { measure, feeds } of report.measures) {\n console.log('\\n', report.target, measure);\n const table: Record<string, unknown> = {};\n for (const { feed, data } of feeds) {\n table[feed] = Object.fromEntries(Object.entries(data).map(([key, report]) => [key, report.toString()]));\n }\n console.table(table);\n }\n }\n};\n\nexport const printJSONReports = <R extends ReportTypeList>(reports: TargetReport<R>[], padding?: number) => {\n const output = {} as Record<string, Record<string, Record<string, string>>>;\n for (const report of reports) {\n for (const { measure, feeds } of report.measures) {\n const row = {} as Record<string, Record<string, string>>;\n for (const { feed, data } of feeds) {\n row[feed] = Object.fromEntries(Object.entries(data).map(([key, report]) => [key, report.toString()]));\n }\n output[`${report.target} ${measure}`] = row;\n }\n }\n console.log(JSON.stringify(output, null, padding));\n};\n"],"names":["AsyncFunction","Benchmark","DEFAULT_REPORT_TYPES","DEFAULT_WORKERS","FeedContext","Measure","MeasureContext","Target","TargetContext","printJSONReports","printSimpleReports","printTableReports","cpus","length","pre","post","title","run","ctx","fn","teardown","measures","setup","measure","push","create","feed","self","target","execute","workers","warmupCycles","maxCycles","DEFAULT_CYCLES","minCycles","absThreshold","relThreshold","reportTypes","Error","executor","createExecutor","reports","targetReport","measureReport","feeds","data","then","drain","kill","report","console","group","output","Object","entries","map","key","toString","join","log","groupEnd","table","fromEntries","padding","row","JSON","stringify"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { cpus } from 'node:os';\nimport { pathToFileURL } from 'node:url';\nimport { createExecutor, ExecutorOptions, ExecutorReport } from './executor.js';\nimport { MaybePromise, StepFn, SetupFn, TeardownFn, FeedFn, ReportType, ReportTypeList, DEFAULT_CYCLES } from './types.js';\n\ndeclare global {\n const benchmark: typeof Benchmark.create;\n}\n\nexport const DEFAULT_WORKERS = cpus().length;\n\nexport const AsyncFunction = (async () => {}).constructor;\n\nexport interface TargetReport<R extends ReportTypeList> {\n target: string;\n measures: MeasureReport<R>[];\n}\n\nexport interface MeasureReport<R extends ReportTypeList> {\n measure: string;\n feeds: FeedReport<R>[];\n}\n\nexport interface FeedReport<R extends ReportTypeList> {\n feed: string;\n data: ExecutorReport<R>;\n}\n\nexport const DEFAULT_REPORT_TYPES = ['ops'] as const;\nexport type DefaultReportTypes = (typeof DEFAULT_REPORT_TYPES)[number];\n\nexport class MeasureContext<TContext, TInput> {\n public pre?: StepFn<TContext, TInput>;\n public post?: StepFn<TContext, TInput>;\n\n constructor(\n public title: string,\n public run: StepFn<TContext, TInput>,\n ) {}\n}\n\nexport class Measure<TContext, TInput> {\n #ctx: MeasureContext<TContext, TInput>;\n\n constructor(ctx: MeasureContext<TContext, TInput>) {\n this.#ctx = ctx;\n }\n\n pre(fn: StepFn<TContext, TInput>): Measure<TContext, TInput> {\n this.#ctx.pre = fn;\n return this;\n }\n post(fn: StepFn<TContext, TInput>): Measure<TContext, TInput> {\n this.#ctx.post = fn;\n return this;\n }\n}\n\nexport class TargetContext<TContext, TInput> {\n public teardown?: TeardownFn<TContext>;\n public measures: MeasureContext<TContext, TInput>[] = [];\n\n constructor(\n readonly title: string,\n readonly setup?: SetupFn<MaybePromise<TContext>>,\n ) {}\n}\n\nexport class Target<TContext, TInput> {\n #ctx: TargetContext<TContext, TInput>;\n\n constructor(ctx: TargetContext<TContext, TInput>) {\n this.#ctx = ctx;\n }\n teardown(fn: TeardownFn<TContext>): Target<TContext, TInput> {\n this.#ctx.teardown = fn;\n\n return this;\n }\n measure(title: string, run: StepFn<TContext, TInput>): Measure<TContext, TInput> {\n const measure = new MeasureContext(title, run);\n this.#ctx.measures.push(measure);\n\n return new Measure(measure);\n }\n}\n\nexport class FeedContext<TInput> {\n constructor(\n readonly title: string,\n readonly fn?: FeedFn<TInput>,\n ) {}\n}\n\nexport class Benchmark<TInput> {\n #targets: TargetContext<unknown, TInput>[] = [];\n #feeds: FeedContext<TInput>[] = [];\n #executed = false;\n\n static create(title: string): Benchmark<void>;\n static create<I>(title: string, fn: FeedFn<I>): Benchmark<I>;\n static create<I>(title: string, fn?: FeedFn<I> | undefined): Benchmark<I> {\n if (fn) {\n return new Benchmark(title, fn);\n } else {\n return new Benchmark(title);\n }\n }\n\n constructor(title: string);\n constructor(title: string, fn: FeedFn<TInput>);\n constructor(title: string, fn?: FeedFn<TInput> | undefined) {\n if (fn) {\n this.feed(title, fn);\n } else {\n this.feed(title);\n }\n }\n\n feed(title: string): Benchmark<TInput | void>;\n feed<I>(title: string, fn: FeedFn<I>): Benchmark<TInput | I>;\n feed<I>(title: string, fn?: FeedFn<I> | undefined): Benchmark<TInput | I> {\n const self = this as Benchmark<TInput | I>;\n self.#feeds.push(fn ? new FeedContext(title, fn) : new FeedContext(title));\n\n return self;\n }\n\n target<TContext>(title: string): Target<void, TInput>;\n target<TContext>(title: string, setup: SetupFn<Awaited<TContext>>): Target<TContext, TInput>;\n target<TContext>(title: string, setup?: SetupFn<Awaited<TContext>> | undefined): Target<TContext, TInput> {\n const target = new TargetContext<TContext, TInput>(title, setup);\n this.#targets.push(target as TargetContext<unknown, TInput>);\n\n return new Target<TContext, TInput>(target);\n }\n\n async execute<R extends readonly ReportType[] = typeof DEFAULT_REPORT_TYPES>({\n workers = DEFAULT_WORKERS,\n warmupCycles = 20,\n maxCycles = DEFAULT_CYCLES,\n minCycles = 50,\n absThreshold = 1_000,\n relThreshold = 0.02,\n gcObserver = true,\n reportTypes = DEFAULT_REPORT_TYPES as unknown as R,\n baseUrl,\n }: ExecutorOptions<R>): Promise<TargetReport<R>[]> {\n if (this.#executed) {\n throw new Error(\"Benchmark is executed and can't be reused\");\n }\n this.#executed = true;\n\n const resolvedBaseUrl = baseUrl ?? pathToFileURL(process.cwd()).href;\n if (!baseUrl) {\n console.warn(\"Overtake: baseUrl not provided; defaulting to process.cwd(). Pass the benchmark's import.meta.url so relative imports resolve correctly.\");\n }\n\n const executor = createExecutor<unknown, TInput, R>({\n baseUrl: resolvedBaseUrl,\n workers,\n warmupCycles,\n maxCycles,\n minCycles,\n absThreshold,\n relThreshold,\n gcObserver,\n reportTypes,\n });\n\n const reports: TargetReport<R>[] = [];\n for (const target of this.#targets) {\n const targetReport: TargetReport<R> = { target: target.title, measures: [] };\n for (const measure of target.measures) {\n const measureReport: MeasureReport<R> = { measure: measure.title, feeds: [] };\n for (const feed of this.#feeds) {\n const data = await feed.fn?.();\n executor\n .push<ExecutorReport<R>>({\n baseUrl: resolvedBaseUrl,\n setup: target.setup,\n teardown: target.teardown,\n pre: measure.pre,\n run: measure.run,\n post: measure.post,\n data,\n })\n .then((data) => {\n measureReport.feeds.push({\n feed: feed.title,\n data,\n });\n });\n }\n targetReport.measures.push(measureReport);\n }\n reports.push(targetReport);\n }\n await executor.drain();\n executor.kill();\n\n return reports;\n }\n}\n\nexport const printSimpleReports = <R extends ReportTypeList>(reports: TargetReport<R>[]) => {\n for (const report of reports) {\n for (const { measure, feeds } of report.measures) {\n console.group('\\n', report.target, measure);\n for (const { feed, data } of feeds) {\n const output = Object.entries(data)\n .map(([key, report]) => `${key}: ${report.toString()}`)\n .join('; ');\n console.log(feed, output);\n }\n console.groupEnd();\n }\n }\n};\n\nexport const printTableReports = <R extends ReportTypeList>(reports: TargetReport<R>[]) => {\n for (const report of reports) {\n for (const { measure, feeds } of report.measures) {\n console.log('\\n', report.target, measure);\n const table: Record<string, unknown> = {};\n for (const { feed, data } of feeds) {\n table[feed] = Object.fromEntries(Object.entries(data).map(([key, report]) => [key, report.toString()]));\n }\n console.table(table);\n }\n }\n};\n\nexport const printJSONReports = <R extends ReportTypeList>(reports: TargetReport<R>[], padding?: number) => {\n const output = {} as Record<string, Record<string, Record<string, string>>>;\n for (const report of reports) {\n for (const { measure, feeds } of report.measures) {\n const row = {} as Record<string, Record<string, string>>;\n for (const { feed, data } of feeds) {\n row[feed] = Object.fromEntries(Object.entries(data).map(([key, report]) => [key, report.toString()]));\n }\n output[`${report.target} ${measure}`] = row;\n }\n }\n console.log(JSON.stringify(output, null, padding));\n};\n"],"names":["AsyncFunction","Benchmark","DEFAULT_REPORT_TYPES","DEFAULT_WORKERS","FeedContext","Measure","MeasureContext","Target","TargetContext","printJSONReports","printSimpleReports","printTableReports","cpus","length","pre","post","title","run","ctx","fn","teardown","measures","setup","measure","push","create","feed","self","target","execute","workers","warmupCycles","maxCycles","DEFAULT_CYCLES","minCycles","absThreshold","relThreshold","gcObserver","reportTypes","baseUrl","Error","resolvedBaseUrl","pathToFileURL","process","cwd","href","console","warn","executor","createExecutor","reports","targetReport","measureReport","feeds","data","then","drain","kill","report","group","output","Object","entries","map","key","toString","join","log","groupEnd","table","fromEntries","padding","row","JSON","stringify"],"mappings":"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"}
package/build/index.d.ts CHANGED
@@ -60,7 +60,7 @@ export declare class Benchmark<TInput> {
60
60
  feed<I>(title: string, fn: FeedFn<I>): Benchmark<TInput | I>;
61
61
  target<TContext>(title: string): Target<void, TInput>;
62
62
  target<TContext>(title: string, setup: SetupFn<Awaited<TContext>>): Target<TContext, TInput>;
63
- execute<R extends readonly ReportType[] = typeof DEFAULT_REPORT_TYPES>({ workers, warmupCycles, maxCycles, minCycles, absThreshold, relThreshold, reportTypes, }: ExecutorOptions<R>): Promise<TargetReport<R>[]>;
63
+ execute<R extends readonly ReportType[] = typeof DEFAULT_REPORT_TYPES>({ workers, warmupCycles, maxCycles, minCycles, absThreshold, relThreshold, gcObserver, reportTypes, baseUrl, }: ExecutorOptions<R>): Promise<TargetReport<R>[]>;
64
64
  }
65
65
  export declare const printSimpleReports: <R extends ReportTypeList>(reports: TargetReport<R>[]) => void;
66
66
  export declare const printTableReports: <R extends ReportTypeList>(reports: TargetReport<R>[]) => void;
package/build/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { cpus } from 'node:os';
2
+ import { pathToFileURL } from 'node:url';
2
3
  import { createExecutor } from "./executor.js";
3
4
  import { DEFAULT_CYCLES } from "./types.js";
4
5
  export const DEFAULT_WORKERS = cpus().length;
@@ -91,18 +92,24 @@ export class Benchmark {
91
92
  this.#targets.push(target);
92
93
  return new Target(target);
93
94
  }
94
- async execute({ workers = DEFAULT_WORKERS, warmupCycles = 20, maxCycles = DEFAULT_CYCLES, minCycles = 50, absThreshold = 1_000, relThreshold = 0.02, reportTypes = DEFAULT_REPORT_TYPES }) {
95
+ async execute({ workers = DEFAULT_WORKERS, warmupCycles = 20, maxCycles = DEFAULT_CYCLES, minCycles = 50, absThreshold = 1_000, relThreshold = 0.02, gcObserver = true, reportTypes = DEFAULT_REPORT_TYPES, baseUrl }) {
95
96
  if (this.#executed) {
96
97
  throw new Error("Benchmark is executed and can't be reused");
97
98
  }
98
99
  this.#executed = true;
100
+ const resolvedBaseUrl = baseUrl ?? pathToFileURL(process.cwd()).href;
101
+ if (!baseUrl) {
102
+ console.warn("Overtake: baseUrl not provided; defaulting to process.cwd(). Pass the benchmark's import.meta.url so relative imports resolve correctly.");
103
+ }
99
104
  const executor = createExecutor({
105
+ baseUrl: resolvedBaseUrl,
100
106
  workers,
101
107
  warmupCycles,
102
108
  maxCycles,
103
109
  minCycles,
104
110
  absThreshold,
105
111
  relThreshold,
112
+ gcObserver,
106
113
  reportTypes
107
114
  });
108
115
  const reports = [];
@@ -119,6 +126,7 @@ export class Benchmark {
119
126
  for (const feed of this.#feeds){
120
127
  const data = await feed.fn?.();
121
128
  executor.push({
129
+ baseUrl: resolvedBaseUrl,
122
130
  setup: target.setup,
123
131
  teardown: target.teardown,
124
132
  pre: measure.pre,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { cpus } from 'node:os';\nimport { createExecutor, ExecutorOptions, ExecutorReport } from './executor.js';\nimport { MaybePromise, StepFn, SetupFn, TeardownFn, FeedFn, ReportType, ReportTypeList, DEFAULT_CYCLES } from './types.js';\n\ndeclare global {\n const benchmark: typeof Benchmark.create;\n}\n\nexport const DEFAULT_WORKERS = cpus().length;\n\nexport const AsyncFunction = (async () => {}).constructor;\n\nexport interface TargetReport<R extends ReportTypeList> {\n target: string;\n measures: MeasureReport<R>[];\n}\n\nexport interface MeasureReport<R extends ReportTypeList> {\n measure: string;\n feeds: FeedReport<R>[];\n}\n\nexport interface FeedReport<R extends ReportTypeList> {\n feed: string;\n data: ExecutorReport<R>;\n}\n\nexport const DEFAULT_REPORT_TYPES = ['ops'] as const;\nexport type DefaultReportTypes = (typeof DEFAULT_REPORT_TYPES)[number];\n\nexport class MeasureContext<TContext, TInput> {\n public pre?: StepFn<TContext, TInput>;\n public post?: StepFn<TContext, TInput>;\n\n constructor(\n public title: string,\n public run: StepFn<TContext, TInput>,\n ) {}\n}\n\nexport class Measure<TContext, TInput> {\n #ctx: MeasureContext<TContext, TInput>;\n\n constructor(ctx: MeasureContext<TContext, TInput>) {\n this.#ctx = ctx;\n }\n\n pre(fn: StepFn<TContext, TInput>): Measure<TContext, TInput> {\n this.#ctx.pre = fn;\n return this;\n }\n post(fn: StepFn<TContext, TInput>): Measure<TContext, TInput> {\n this.#ctx.post = fn;\n return this;\n }\n}\n\nexport class TargetContext<TContext, TInput> {\n public teardown?: TeardownFn<TContext>;\n public measures: MeasureContext<TContext, TInput>[] = [];\n\n constructor(\n readonly title: string,\n readonly setup?: SetupFn<MaybePromise<TContext>>,\n ) {}\n}\n\nexport class Target<TContext, TInput> {\n #ctx: TargetContext<TContext, TInput>;\n\n constructor(ctx: TargetContext<TContext, TInput>) {\n this.#ctx = ctx;\n }\n teardown(fn: TeardownFn<TContext>): Target<TContext, TInput> {\n this.#ctx.teardown = fn;\n\n return this;\n }\n measure(title: string, run: StepFn<TContext, TInput>): Measure<TContext, TInput> {\n const measure = new MeasureContext(title, run);\n this.#ctx.measures.push(measure);\n\n return new Measure(measure);\n }\n}\n\nexport class FeedContext<TInput> {\n constructor(\n readonly title: string,\n readonly fn?: FeedFn<TInput>,\n ) {}\n}\n\nexport class Benchmark<TInput> {\n #targets: TargetContext<unknown, TInput>[] = [];\n #feeds: FeedContext<TInput>[] = [];\n #executed = false;\n\n static create(title: string): Benchmark<void>;\n static create<I>(title: string, fn: FeedFn<I>): Benchmark<I>;\n static create<I>(title: string, fn?: FeedFn<I> | undefined): Benchmark<I> {\n if (fn) {\n return new Benchmark(title, fn);\n } else {\n return new Benchmark(title);\n }\n }\n\n constructor(title: string);\n constructor(title: string, fn: FeedFn<TInput>);\n constructor(title: string, fn?: FeedFn<TInput> | undefined) {\n if (fn) {\n this.feed(title, fn);\n } else {\n this.feed(title);\n }\n }\n\n feed(title: string): Benchmark<TInput | void>;\n feed<I>(title: string, fn: FeedFn<I>): Benchmark<TInput | I>;\n feed<I>(title: string, fn?: FeedFn<I> | undefined): Benchmark<TInput | I> {\n const self = this as Benchmark<TInput | I>;\n self.#feeds.push(fn ? new FeedContext(title, fn) : new FeedContext(title));\n\n return self;\n }\n\n target<TContext>(title: string): Target<void, TInput>;\n target<TContext>(title: string, setup: SetupFn<Awaited<TContext>>): Target<TContext, TInput>;\n target<TContext>(title: string, setup?: SetupFn<Awaited<TContext>> | undefined): Target<TContext, TInput> {\n const target = new TargetContext<TContext, TInput>(title, setup);\n this.#targets.push(target as TargetContext<unknown, TInput>);\n\n return new Target<TContext, TInput>(target);\n }\n\n async execute<R extends readonly ReportType[] = typeof DEFAULT_REPORT_TYPES>({\n workers = DEFAULT_WORKERS,\n warmupCycles = 20,\n maxCycles = DEFAULT_CYCLES,\n minCycles = 50,\n absThreshold = 1_000,\n relThreshold = 0.02,\n reportTypes = DEFAULT_REPORT_TYPES as unknown as R,\n }: ExecutorOptions<R>): Promise<TargetReport<R>[]> {\n if (this.#executed) {\n throw new Error(\"Benchmark is executed and can't be reused\");\n }\n this.#executed = true;\n\n const executor = createExecutor<unknown, TInput, R>({\n workers,\n warmupCycles,\n maxCycles,\n minCycles,\n absThreshold,\n relThreshold,\n reportTypes,\n });\n\n const reports: TargetReport<R>[] = [];\n for (const target of this.#targets) {\n const targetReport: TargetReport<R> = { target: target.title, measures: [] };\n for (const measure of target.measures) {\n const measureReport: MeasureReport<R> = { measure: measure.title, feeds: [] };\n for (const feed of this.#feeds) {\n const data = await feed.fn?.();\n executor\n .push<ExecutorReport<R>>({\n setup: target.setup,\n teardown: target.teardown,\n pre: measure.pre,\n run: measure.run,\n post: measure.post,\n data,\n })\n .then((data) => {\n measureReport.feeds.push({\n feed: feed.title,\n data,\n });\n });\n }\n targetReport.measures.push(measureReport);\n }\n reports.push(targetReport);\n }\n await executor.drain();\n executor.kill();\n\n return reports;\n }\n}\n\nexport const printSimpleReports = <R extends ReportTypeList>(reports: TargetReport<R>[]) => {\n for (const report of reports) {\n for (const { measure, feeds } of report.measures) {\n console.group('\\n', report.target, measure);\n for (const { feed, data } of feeds) {\n const output = Object.entries(data)\n .map(([key, report]) => `${key}: ${report.toString()}`)\n .join('; ');\n console.log(feed, output);\n }\n console.groupEnd();\n }\n }\n};\n\nexport const printTableReports = <R extends ReportTypeList>(reports: TargetReport<R>[]) => {\n for (const report of reports) {\n for (const { measure, feeds } of report.measures) {\n console.log('\\n', report.target, measure);\n const table: Record<string, unknown> = {};\n for (const { feed, data } of feeds) {\n table[feed] = Object.fromEntries(Object.entries(data).map(([key, report]) => [key, report.toString()]));\n }\n console.table(table);\n }\n }\n};\n\nexport const printJSONReports = <R extends ReportTypeList>(reports: TargetReport<R>[], padding?: number) => {\n const output = {} as Record<string, Record<string, Record<string, string>>>;\n for (const report of reports) {\n for (const { measure, feeds } of report.measures) {\n const row = {} as Record<string, Record<string, string>>;\n for (const { feed, data } of feeds) {\n row[feed] = Object.fromEntries(Object.entries(data).map(([key, report]) => [key, report.toString()]));\n }\n output[`${report.target} ${measure}`] = row;\n }\n }\n console.log(JSON.stringify(output, null, padding));\n};\n"],"names":["cpus","createExecutor","DEFAULT_CYCLES","DEFAULT_WORKERS","length","AsyncFunction","DEFAULT_REPORT_TYPES","MeasureContext","pre","post","title","run","Measure","ctx","fn","TargetContext","teardown","measures","setup","Target","measure","push","FeedContext","Benchmark","create","feed","self","target","execute","workers","warmupCycles","maxCycles","minCycles","absThreshold","relThreshold","reportTypes","Error","executor","reports","targetReport","measureReport","feeds","data","then","drain","kill","printSimpleReports","report","console","group","output","Object","entries","map","key","toString","join","log","groupEnd","printTableReports","table","fromEntries","printJSONReports","padding","row","JSON","stringify"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { cpus } from 'node:os';\nimport { pathToFileURL } from 'node:url';\nimport { createExecutor, ExecutorOptions, ExecutorReport } from './executor.js';\nimport { MaybePromise, StepFn, SetupFn, TeardownFn, FeedFn, ReportType, ReportTypeList, DEFAULT_CYCLES } from './types.js';\n\ndeclare global {\n const benchmark: typeof Benchmark.create;\n}\n\nexport const DEFAULT_WORKERS = cpus().length;\n\nexport const AsyncFunction = (async () => {}).constructor;\n\nexport interface TargetReport<R extends ReportTypeList> {\n target: string;\n measures: MeasureReport<R>[];\n}\n\nexport interface MeasureReport<R extends ReportTypeList> {\n measure: string;\n feeds: FeedReport<R>[];\n}\n\nexport interface FeedReport<R extends ReportTypeList> {\n feed: string;\n data: ExecutorReport<R>;\n}\n\nexport const DEFAULT_REPORT_TYPES = ['ops'] as const;\nexport type DefaultReportTypes = (typeof DEFAULT_REPORT_TYPES)[number];\n\nexport class MeasureContext<TContext, TInput> {\n public pre?: StepFn<TContext, TInput>;\n public post?: StepFn<TContext, TInput>;\n\n constructor(\n public title: string,\n public run: StepFn<TContext, TInput>,\n ) {}\n}\n\nexport class Measure<TContext, TInput> {\n #ctx: MeasureContext<TContext, TInput>;\n\n constructor(ctx: MeasureContext<TContext, TInput>) {\n this.#ctx = ctx;\n }\n\n pre(fn: StepFn<TContext, TInput>): Measure<TContext, TInput> {\n this.#ctx.pre = fn;\n return this;\n }\n post(fn: StepFn<TContext, TInput>): Measure<TContext, TInput> {\n this.#ctx.post = fn;\n return this;\n }\n}\n\nexport class TargetContext<TContext, TInput> {\n public teardown?: TeardownFn<TContext>;\n public measures: MeasureContext<TContext, TInput>[] = [];\n\n constructor(\n readonly title: string,\n readonly setup?: SetupFn<MaybePromise<TContext>>,\n ) {}\n}\n\nexport class Target<TContext, TInput> {\n #ctx: TargetContext<TContext, TInput>;\n\n constructor(ctx: TargetContext<TContext, TInput>) {\n this.#ctx = ctx;\n }\n teardown(fn: TeardownFn<TContext>): Target<TContext, TInput> {\n this.#ctx.teardown = fn;\n\n return this;\n }\n measure(title: string, run: StepFn<TContext, TInput>): Measure<TContext, TInput> {\n const measure = new MeasureContext(title, run);\n this.#ctx.measures.push(measure);\n\n return new Measure(measure);\n }\n}\n\nexport class FeedContext<TInput> {\n constructor(\n readonly title: string,\n readonly fn?: FeedFn<TInput>,\n ) {}\n}\n\nexport class Benchmark<TInput> {\n #targets: TargetContext<unknown, TInput>[] = [];\n #feeds: FeedContext<TInput>[] = [];\n #executed = false;\n\n static create(title: string): Benchmark<void>;\n static create<I>(title: string, fn: FeedFn<I>): Benchmark<I>;\n static create<I>(title: string, fn?: FeedFn<I> | undefined): Benchmark<I> {\n if (fn) {\n return new Benchmark(title, fn);\n } else {\n return new Benchmark(title);\n }\n }\n\n constructor(title: string);\n constructor(title: string, fn: FeedFn<TInput>);\n constructor(title: string, fn?: FeedFn<TInput> | undefined) {\n if (fn) {\n this.feed(title, fn);\n } else {\n this.feed(title);\n }\n }\n\n feed(title: string): Benchmark<TInput | void>;\n feed<I>(title: string, fn: FeedFn<I>): Benchmark<TInput | I>;\n feed<I>(title: string, fn?: FeedFn<I> | undefined): Benchmark<TInput | I> {\n const self = this as Benchmark<TInput | I>;\n self.#feeds.push(fn ? new FeedContext(title, fn) : new FeedContext(title));\n\n return self;\n }\n\n target<TContext>(title: string): Target<void, TInput>;\n target<TContext>(title: string, setup: SetupFn<Awaited<TContext>>): Target<TContext, TInput>;\n target<TContext>(title: string, setup?: SetupFn<Awaited<TContext>> | undefined): Target<TContext, TInput> {\n const target = new TargetContext<TContext, TInput>(title, setup);\n this.#targets.push(target as TargetContext<unknown, TInput>);\n\n return new Target<TContext, TInput>(target);\n }\n\n async execute<R extends readonly ReportType[] = typeof DEFAULT_REPORT_TYPES>({\n workers = DEFAULT_WORKERS,\n warmupCycles = 20,\n maxCycles = DEFAULT_CYCLES,\n minCycles = 50,\n absThreshold = 1_000,\n relThreshold = 0.02,\n gcObserver = true,\n reportTypes = DEFAULT_REPORT_TYPES as unknown as R,\n baseUrl,\n }: ExecutorOptions<R>): Promise<TargetReport<R>[]> {\n if (this.#executed) {\n throw new Error(\"Benchmark is executed and can't be reused\");\n }\n this.#executed = true;\n\n const resolvedBaseUrl = baseUrl ?? pathToFileURL(process.cwd()).href;\n if (!baseUrl) {\n console.warn(\"Overtake: baseUrl not provided; defaulting to process.cwd(). Pass the benchmark's import.meta.url so relative imports resolve correctly.\");\n }\n\n const executor = createExecutor<unknown, TInput, R>({\n baseUrl: resolvedBaseUrl,\n workers,\n warmupCycles,\n maxCycles,\n minCycles,\n absThreshold,\n relThreshold,\n gcObserver,\n reportTypes,\n });\n\n const reports: TargetReport<R>[] = [];\n for (const target of this.#targets) {\n const targetReport: TargetReport<R> = { target: target.title, measures: [] };\n for (const measure of target.measures) {\n const measureReport: MeasureReport<R> = { measure: measure.title, feeds: [] };\n for (const feed of this.#feeds) {\n const data = await feed.fn?.();\n executor\n .push<ExecutorReport<R>>({\n baseUrl: resolvedBaseUrl,\n setup: target.setup,\n teardown: target.teardown,\n pre: measure.pre,\n run: measure.run,\n post: measure.post,\n data,\n })\n .then((data) => {\n measureReport.feeds.push({\n feed: feed.title,\n data,\n });\n });\n }\n targetReport.measures.push(measureReport);\n }\n reports.push(targetReport);\n }\n await executor.drain();\n executor.kill();\n\n return reports;\n }\n}\n\nexport const printSimpleReports = <R extends ReportTypeList>(reports: TargetReport<R>[]) => {\n for (const report of reports) {\n for (const { measure, feeds } of report.measures) {\n console.group('\\n', report.target, measure);\n for (const { feed, data } of feeds) {\n const output = Object.entries(data)\n .map(([key, report]) => `${key}: ${report.toString()}`)\n .join('; ');\n console.log(feed, output);\n }\n console.groupEnd();\n }\n }\n};\n\nexport const printTableReports = <R extends ReportTypeList>(reports: TargetReport<R>[]) => {\n for (const report of reports) {\n for (const { measure, feeds } of report.measures) {\n console.log('\\n', report.target, measure);\n const table: Record<string, unknown> = {};\n for (const { feed, data } of feeds) {\n table[feed] = Object.fromEntries(Object.entries(data).map(([key, report]) => [key, report.toString()]));\n }\n console.table(table);\n }\n }\n};\n\nexport const printJSONReports = <R extends ReportTypeList>(reports: TargetReport<R>[], padding?: number) => {\n const output = {} as Record<string, Record<string, Record<string, string>>>;\n for (const report of reports) {\n for (const { measure, feeds } of report.measures) {\n const row = {} as Record<string, Record<string, string>>;\n for (const { feed, data } of feeds) {\n row[feed] = Object.fromEntries(Object.entries(data).map(([key, report]) => [key, report.toString()]));\n }\n output[`${report.target} ${measure}`] = row;\n }\n }\n console.log(JSON.stringify(output, null, padding));\n};\n"],"names":["cpus","pathToFileURL","createExecutor","DEFAULT_CYCLES","DEFAULT_WORKERS","length","AsyncFunction","DEFAULT_REPORT_TYPES","MeasureContext","pre","post","title","run","Measure","ctx","fn","TargetContext","teardown","measures","setup","Target","measure","push","FeedContext","Benchmark","create","feed","self","target","execute","workers","warmupCycles","maxCycles","minCycles","absThreshold","relThreshold","gcObserver","reportTypes","baseUrl","Error","resolvedBaseUrl","process","cwd","href","console","warn","executor","reports","targetReport","measureReport","feeds","data","then","drain","kill","printSimpleReports","report","group","output","Object","entries","map","key","toString","join","log","groupEnd","printTableReports","table","fromEntries","printJSONReports","padding","row","JSON","stringify"],"mappings":"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"}
package/build/runner.cjs CHANGED
@@ -8,23 +8,154 @@ Object.defineProperty(exports, "benchmark", {
8
8
  return benchmark;
9
9
  }
10
10
  });
11
+ const _nodeperf_hooks = require("node:perf_hooks");
11
12
  const _typescjs = require("./types.cjs");
13
+ const _gcwatchercjs = require("./gc-watcher.cjs");
12
14
  const COMPLETE_VALUE = 100_00;
15
+ const hr = process.hrtime.bigint.bind(process.hrtime);
13
16
  const runSync = (run)=>{
14
17
  return (...args)=>{
15
- const start = process.hrtime.bigint();
18
+ const start = hr();
16
19
  run(...args);
17
- return process.hrtime.bigint() - start;
20
+ return hr() - start;
18
21
  };
19
22
  };
20
23
  const runAsync = (run)=>{
21
24
  return async (...args)=>{
22
- const start = process.hrtime.bigint();
25
+ const start = hr();
23
26
  await run(...args);
24
- return process.hrtime.bigint() - start;
27
+ return hr() - start;
25
28
  };
26
29
  };
27
- const benchmark = async ({ setup, teardown, pre, run: runRaw, post, data, warmupCycles, minCycles, absThreshold, relThreshold, durationsSAB, controlSAB })=>{
30
+ const isThenable = (value)=>{
31
+ return value !== null && (typeof value === 'object' || typeof value === 'function') && typeof value.then === 'function';
32
+ };
33
+ const TARGET_SAMPLE_NS = 1_000_000n;
34
+ const MAX_BATCH = 1_048_576;
35
+ const PROGRESS_STRIDE = 16;
36
+ const GC_STRIDE = 32;
37
+ const OUTLIER_MULTIPLIER = 4;
38
+ const OUTLIER_IQR_MULTIPLIER = 3;
39
+ const OUTLIER_WINDOW = 64;
40
+ const collectSample = async (batchSize, run, pre, post, context, data)=>{
41
+ let sampleDuration = 0n;
42
+ for(let b = 0; b < batchSize; b++){
43
+ await pre?.(context, data);
44
+ sampleDuration += await run(context, data);
45
+ await post?.(context, data);
46
+ }
47
+ return sampleDuration / BigInt(batchSize);
48
+ };
49
+ const tuneParameters = async ({ initialBatch, run, pre, post, context, data, minCycles, relThreshold, maxCycles })=>{
50
+ let batchSize = initialBatch;
51
+ let bestCv = Number.POSITIVE_INFINITY;
52
+ let bestBatch = batchSize;
53
+ for(let attempt = 0; attempt < 3; attempt++){
54
+ const samples = [];
55
+ const sampleCount = Math.min(8, maxCycles);
56
+ for(let s = 0; s < sampleCount; s++){
57
+ const duration = await collectSample(batchSize, run, pre, post, context, data);
58
+ samples.push(Number(duration));
59
+ }
60
+ const mean = samples.reduce((acc, v)=>acc + v, 0) / samples.length;
61
+ const variance = samples.reduce((acc, v)=>acc + (v - mean) * (v - mean), 0) / Math.max(1, samples.length - 1);
62
+ const stddev = Math.sqrt(variance);
63
+ const cv = mean === 0 ? Number.POSITIVE_INFINITY : stddev / mean;
64
+ if (cv < bestCv) {
65
+ bestCv = cv;
66
+ bestBatch = batchSize;
67
+ }
68
+ if (cv <= relThreshold || batchSize >= MAX_BATCH) {
69
+ break;
70
+ }
71
+ batchSize = Math.min(MAX_BATCH, batchSize * 2);
72
+ }
73
+ const tunedRel = bestCv < relThreshold ? Math.max(bestCv * 1.5, relThreshold * 0.5) : relThreshold;
74
+ const tunedMin = Math.min(maxCycles, Math.max(minCycles, Math.ceil(minCycles * Math.max(1, bestCv / (relThreshold || 1e-6)))));
75
+ return {
76
+ batchSize: bestBatch,
77
+ relThreshold: tunedRel,
78
+ minCycles: tunedMin
79
+ };
80
+ };
81
+ const createGCTracker = ()=>{
82
+ if (process.env.OVERTAKE_GC_OBSERVER !== '1') {
83
+ return null;
84
+ }
85
+ if (typeof _nodeperf_hooks.PerformanceObserver === 'undefined') {
86
+ return null;
87
+ }
88
+ const events = [];
89
+ const observer = new _nodeperf_hooks.PerformanceObserver((list)=>{
90
+ for (const entry of list.getEntries()){
91
+ events.push({
92
+ start: entry.startTime,
93
+ end: entry.startTime + entry.duration
94
+ });
95
+ }
96
+ });
97
+ try {
98
+ observer.observe({
99
+ entryTypes: [
100
+ 'gc'
101
+ ]
102
+ });
103
+ } catch {
104
+ return null;
105
+ }
106
+ const overlaps = (start, end)=>{
107
+ let noisy = false;
108
+ for(let i = events.length - 1; i >= 0; i--){
109
+ const event = events[i];
110
+ if (event.end < start - 5_000) {
111
+ events.splice(i, 1);
112
+ continue;
113
+ }
114
+ if (event.start <= end && event.end >= start) {
115
+ noisy = true;
116
+ }
117
+ }
118
+ return noisy;
119
+ };
120
+ const dispose = ()=>observer.disconnect();
121
+ return {
122
+ overlaps,
123
+ dispose
124
+ };
125
+ };
126
+ const pushWindow = (arr, value, cap)=>{
127
+ if (arr.length === cap) {
128
+ arr.shift();
129
+ }
130
+ arr.push(value);
131
+ };
132
+ const medianAndIqr = (arr)=>{
133
+ if (arr.length === 0) return {
134
+ median: 0,
135
+ iqr: 0
136
+ };
137
+ const sorted = [
138
+ ...arr
139
+ ].sort((a, b)=>a - b);
140
+ const mid = Math.floor(sorted.length / 2);
141
+ const median = sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
142
+ const q1Idx = Math.floor(sorted.length * 0.25);
143
+ const q3Idx = Math.floor(sorted.length * 0.75);
144
+ const q1 = sorted[q1Idx];
145
+ const q3 = sorted[q3Idx];
146
+ return {
147
+ median,
148
+ iqr: q3 - q1
149
+ };
150
+ };
151
+ const windowCv = (arr)=>{
152
+ if (arr.length < 2) return Number.POSITIVE_INFINITY;
153
+ const mean = arr.reduce((a, v)=>a + v, 0) / arr.length;
154
+ const variance = arr.reduce((a, v)=>a + (v - mean) * (v - mean), 0) / (arr.length - 1);
155
+ const stddev = Math.sqrt(variance);
156
+ return mean === 0 ? Number.POSITIVE_INFINITY : stddev / mean;
157
+ };
158
+ const benchmark = async ({ setup, teardown, pre, run: runRaw, post, data, warmupCycles, minCycles, absThreshold, relThreshold, gcObserver = false, durationsSAB, controlSAB })=>{
28
159
  const durations = new BigUint64Array(durationsSAB);
29
160
  const control = new Int32Array(controlSAB);
30
161
  control[_typescjs.Control.INDEX] = 0;
@@ -32,40 +163,113 @@ const benchmark = async ({ setup, teardown, pre, run: runRaw, post, data, warmup
32
163
  control[_typescjs.Control.COMPLETE] = 255;
33
164
  const context = await setup?.();
34
165
  const maxCycles = durations.length;
166
+ const gcWatcher = gcObserver ? new _gcwatchercjs.GCWatcher() : null;
167
+ const gcTracker = gcObserver ? createGCTracker() : null;
35
168
  try {
36
169
  await pre?.(context, data);
37
- const result = runRaw(context, data);
170
+ const probeStart = hr();
171
+ const probeResult = runRaw(context, data);
172
+ const isAsync = isThenable(probeResult);
173
+ if (isAsync) {
174
+ await probeResult;
175
+ }
176
+ const durationProbe = hr() - probeStart;
38
177
  await post?.(context, data);
39
- global.gc?.();
40
- global.gc?.();
41
- const run = result instanceof Promise ? runAsync(runRaw) : runSync(runRaw);
42
- const start = Date.now();
43
- while(Date.now() - start < 1_000){
44
- Math.sqrt(Math.random());
178
+ const run = isAsync ? runAsync(runRaw) : runSync(runRaw);
179
+ const durationPerRun = durationProbe === 0n ? 1n : durationProbe;
180
+ const suggestedBatch = Number(TARGET_SAMPLE_NS / durationPerRun);
181
+ const initialBatchSize = Math.min(MAX_BATCH, Math.max(1, suggestedBatch));
182
+ const tuned = await tuneParameters({
183
+ initialBatch: initialBatchSize,
184
+ run,
185
+ pre,
186
+ post,
187
+ context,
188
+ data: data,
189
+ minCycles,
190
+ relThreshold,
191
+ maxCycles
192
+ });
193
+ let batchSize = tuned.batchSize;
194
+ minCycles = tuned.minCycles;
195
+ relThreshold = tuned.relThreshold;
196
+ const warmupStart = Date.now();
197
+ let warmupRemaining = warmupCycles;
198
+ const warmupWindow = [];
199
+ const warmupCap = Math.max(warmupCycles, Math.min(maxCycles, warmupCycles * 4 || 1000));
200
+ while(Date.now() - warmupStart < 1_000 && warmupRemaining > 0){
201
+ const start = hr();
202
+ await pre?.(context, data);
203
+ await run(context, data);
204
+ await post?.(context, data);
205
+ pushWindow(warmupWindow, Number(hr() - start), warmupCap);
206
+ warmupRemaining--;
45
207
  }
46
- for(let i = 0; i < warmupCycles; i++){
208
+ let warmupDone = 0;
209
+ while(warmupDone < warmupRemaining){
210
+ const start = hr();
47
211
  await pre?.(context, data);
48
212
  await run(context, data);
49
213
  await post?.(context, data);
50
- global.gc?.();
51
- global.gc?.();
214
+ pushWindow(warmupWindow, Number(hr() - start), warmupCap);
215
+ warmupDone++;
216
+ if (global.gc && warmupDone % GC_STRIDE === 0) {
217
+ global.gc();
218
+ }
219
+ }
220
+ while(warmupWindow.length >= 8 && warmupWindow.length < warmupCap){
221
+ const cv = windowCv(warmupWindow);
222
+ if (cv <= relThreshold * 2) {
223
+ break;
224
+ }
225
+ const start = hr();
226
+ await pre?.(context, data);
227
+ await run(context, data);
228
+ await post?.(context, data);
229
+ pushWindow(warmupWindow, Number(hr() - start), warmupCap);
52
230
  }
53
231
  let i = 0;
54
232
  let mean = 0n;
55
233
  let m2 = 0n;
234
+ const outlierWindow = [];
56
235
  while(true){
57
236
  if (i >= maxCycles) break;
58
- await pre?.(context, data);
59
- const duration = await run(context, data);
60
- await post?.(context, data);
61
- global.gc?.();
62
- global.gc?.();
63
- durations[i++] = duration;
64
- const delta = duration - mean;
237
+ const gcMarker = gcWatcher?.start();
238
+ const sampleStart = _nodeperf_hooks.performance.now();
239
+ let sampleDuration = 0n;
240
+ for(let b = 0; b < batchSize; b++){
241
+ await pre?.(context, data);
242
+ sampleDuration += await run(context, data);
243
+ await post?.(context, data);
244
+ if (global.gc && (i + b) % GC_STRIDE === 0) {
245
+ global.gc();
246
+ }
247
+ }
248
+ sampleDuration /= BigInt(batchSize);
249
+ const sampleEnd = _nodeperf_hooks.performance.now();
250
+ const gcNoise = (gcMarker ? gcWatcher.seen(gcMarker) : false) || (gcTracker?.overlaps(sampleStart, sampleEnd) ?? false);
251
+ if (gcNoise) {
252
+ continue;
253
+ }
254
+ const durationNumber = Number(sampleDuration);
255
+ pushWindow(outlierWindow, durationNumber, OUTLIER_WINDOW);
256
+ const { median, iqr } = medianAndIqr(outlierWindow);
257
+ const maxAllowed = median + OUTLIER_IQR_MULTIPLIER * iqr || Number.POSITIVE_INFINITY;
258
+ if (outlierWindow.length >= 8 && durationNumber > maxAllowed) {
259
+ continue;
260
+ }
261
+ const meanNumber = Number(mean);
262
+ if (i >= 8 && meanNumber > 0 && durationNumber > OUTLIER_MULTIPLIER * meanNumber) {
263
+ continue;
264
+ }
265
+ durations[i++] = sampleDuration;
266
+ const delta = sampleDuration - mean;
65
267
  mean += delta / BigInt(i);
66
- m2 += delta * (duration - mean);
268
+ m2 += delta * (sampleDuration - mean);
67
269
  const progress = Math.max(i / maxCycles) * COMPLETE_VALUE;
68
- control[_typescjs.Control.PROGRESS] = progress;
270
+ if (i % PROGRESS_STRIDE === 0) {
271
+ control[_typescjs.Control.PROGRESS] = progress;
272
+ }
69
273
  if (i >= minCycles) {
70
274
  const variance = Number(m2) / (i - 1);
71
275
  const stddev = Math.sqrt(variance);
@@ -85,6 +289,7 @@ const benchmark = async ({ setup, teardown, pre, run: runRaw, post, data, warmup
85
289
  console.error(e && typeof e === 'object' && 'stack' in e ? e.stack : e);
86
290
  control[_typescjs.Control.COMPLETE] = 1;
87
291
  } finally{
292
+ gcTracker?.dispose?.();
88
293
  try {
89
294
  await teardown?.(context);
90
295
  } catch (e) {