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
package/build/worker.cjs CHANGED
@@ -2,21 +2,111 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- Object.defineProperty(exports, "exitCode", {
6
- enumerable: true,
7
- get: function() {
8
- return exitCode;
9
- }
10
- });
11
5
  const _nodeworker_threads = require("node:worker_threads");
6
+ const _nodevm = require("node:vm");
7
+ const _nodemodule = require("node:module");
8
+ const _nodeurl = require("node:url");
12
9
  const _runnercjs = require("./runner.cjs");
13
- const { setupCode, teardownCode, preCode, runCode, postCode, data, warmupCycles, minCycles, absThreshold, relThreshold, durationsSAB, controlSAB } = _nodeworker_threads.workerData;
14
- const setup = setupCode && Function(`return ${setupCode};`)();
15
- const teardown = teardownCode && Function(`return ${teardownCode};`)();
16
- const pre = preCode && Function(`return ${preCode};`)();
17
- const run = runCode && Function(`return ${runCode};`)();
18
- const post = postCode && Function(`return ${postCode};`)();
19
- const exitCode = await (0, _runnercjs.benchmark)({
10
+ function _getRequireWildcardCache(nodeInterop) {
11
+ if (typeof WeakMap !== "function") return null;
12
+ var cacheBabelInterop = new WeakMap();
13
+ var cacheNodeInterop = new WeakMap();
14
+ return (_getRequireWildcardCache = function(nodeInterop) {
15
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
16
+ })(nodeInterop);
17
+ }
18
+ function _interop_require_wildcard(obj, nodeInterop) {
19
+ if (!nodeInterop && obj && obj.__esModule) {
20
+ return obj;
21
+ }
22
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
23
+ return {
24
+ default: obj
25
+ };
26
+ }
27
+ var cache = _getRequireWildcardCache(nodeInterop);
28
+ if (cache && cache.has(obj)) {
29
+ return cache.get(obj);
30
+ }
31
+ var newObj = {
32
+ __proto__: null
33
+ };
34
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
35
+ for(var key in obj){
36
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
37
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
38
+ if (desc && (desc.get || desc.set)) {
39
+ Object.defineProperty(newObj, key, desc);
40
+ } else {
41
+ newObj[key] = obj[key];
42
+ }
43
+ }
44
+ }
45
+ newObj.default = obj;
46
+ if (cache) {
47
+ cache.set(obj, newObj);
48
+ }
49
+ return newObj;
50
+ }
51
+ const { baseUrl, setupCode, teardownCode, preCode, runCode, postCode, data, warmupCycles, minCycles, absThreshold, relThreshold, gcObserver = true, durationsSAB, controlSAB } = _nodeworker_threads.workerData;
52
+ const serialize = (code)=>code ? code : '() => {}';
53
+ const isCjs = typeof require !== 'undefined';
54
+ const resolveSpecifier = (specifier, parent)=>{
55
+ if (!isCjs) {
56
+ try {
57
+ return require.resolve(specifier, parent);
58
+ } catch {}
59
+ }
60
+ const resolveFrom = (0, _nodemodule.createRequire)((0, _nodeurl.fileURLToPath)(parent));
61
+ return resolveFrom.resolve(specifier);
62
+ };
63
+ const source = `
64
+ export const setup = ${serialize(setupCode)};
65
+ export const teardown = ${serialize(teardownCode)};
66
+ export const pre = ${serialize(preCode)};
67
+ export const run = ${serialize(runCode)};
68
+ export const post = ${serialize(postCode)};
69
+ `;
70
+ const context = (0, _nodevm.createContext)({
71
+ console,
72
+ Buffer
73
+ });
74
+ const imports = new Map();
75
+ const mod = new _nodevm.SourceTextModule(source, {
76
+ identifier: baseUrl,
77
+ context,
78
+ initializeImportMeta (meta) {
79
+ meta.url = baseUrl;
80
+ },
81
+ importModuleDynamically (specifier, referencingModule) {
82
+ const base = referencingModule.identifier ?? baseUrl;
83
+ const resolved = resolveSpecifier(specifier, base);
84
+ return Promise.resolve(resolved).then((p)=>/*#__PURE__*/ _interop_require_wildcard(require(p)));
85
+ }
86
+ });
87
+ await mod.link(async (specifier, referencingModule)=>{
88
+ const base = referencingModule.identifier ?? baseUrl;
89
+ const target = resolveSpecifier(specifier, base);
90
+ const cached = imports.get(target);
91
+ if (cached) return cached;
92
+ const importedModule = await Promise.resolve(target).then((p)=>/*#__PURE__*/ _interop_require_wildcard(require(p)));
93
+ const exportNames = Object.keys(importedModule);
94
+ const imported = new _nodevm.SyntheticModule(exportNames, ()=>{
95
+ exportNames.forEach((key)=>imported.setExport(key, importedModule[key]));
96
+ }, {
97
+ identifier: target,
98
+ context: referencingModule.context
99
+ });
100
+ imports.set(target, imported);
101
+ return imported;
102
+ });
103
+ await mod.evaluate();
104
+ const { setup, teardown, pre, run, post } = mod.namespace;
105
+ if (!run) {
106
+ throw new Error('Benchmark run function is required');
107
+ }
108
+ process.exitCode = await (0, _runnercjs.benchmark)({
109
+ baseUrl,
20
110
  setup,
21
111
  teardown,
22
112
  pre,
@@ -27,9 +117,9 @@ const exitCode = await (0, _runnercjs.benchmark)({
27
117
  minCycles,
28
118
  absThreshold,
29
119
  relThreshold,
120
+ gcObserver,
30
121
  durationsSAB,
31
122
  controlSAB
32
123
  });
33
- process.exit(exitCode);
34
124
 
35
125
  //# sourceMappingURL=worker.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/worker.ts"],"sourcesContent":["import { workerData } from 'node:worker_threads';\nimport { benchmark } from './runner.js';\nimport { SetupFn, TeardownFn, StepFn, WorkerOptions } from './types.js';\n\nconst {\n setupCode,\n teardownCode,\n preCode,\n runCode,\n postCode,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n\n durationsSAB,\n controlSAB,\n}: WorkerOptions = workerData;\n\nconst setup: SetupFn<unknown> = setupCode && Function(`return ${setupCode};`)();\nconst teardown: TeardownFn<unknown> = teardownCode && Function(`return ${teardownCode};`)();\n\nconst pre: StepFn<unknown, unknown> = preCode && Function(`return ${preCode};`)();\nconst run: StepFn<unknown, unknown> = runCode && Function(`return ${runCode};`)();\nconst post: StepFn<unknown, unknown> = postCode && Function(`return ${postCode};`)();\n\nexport const exitCode = await benchmark({\n setup,\n teardown,\n pre,\n run,\n post,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n\n durationsSAB,\n controlSAB,\n});\n\nprocess.exit(exitCode);\n"],"names":["exitCode","setupCode","teardownCode","preCode","runCode","postCode","data","warmupCycles","minCycles","absThreshold","relThreshold","durationsSAB","controlSAB","workerData","setup","Function","teardown","pre","run","post","benchmark","process","exit"],"mappings":";;;;+BA4BaA;;;eAAAA;;;oCA5Bc;2BACD;AAG1B,MAAM,EACJC,SAAS,EACTC,YAAY,EACZC,OAAO,EACPC,OAAO,EACPC,QAAQ,EACRC,IAAI,EAEJC,YAAY,EACZC,SAAS,EACTC,YAAY,EACZC,YAAY,EAEZC,YAAY,EACZC,UAAU,EACX,GAAkBC,8BAAU;AAE7B,MAAMC,QAA0Bb,aAAac,SAAS,CAAC,OAAO,EAAEd,UAAU,CAAC,CAAC;AAC5E,MAAMe,WAAgCd,gBAAgBa,SAAS,CAAC,OAAO,EAAEb,aAAa,CAAC,CAAC;AAExF,MAAMe,MAAgCd,WAAWY,SAAS,CAAC,OAAO,EAAEZ,QAAQ,CAAC,CAAC;AAC9E,MAAMe,MAAgCd,WAAWW,SAAS,CAAC,OAAO,EAAEX,QAAQ,CAAC,CAAC;AAC9E,MAAMe,OAAiCd,YAAYU,SAAS,CAAC,OAAO,EAAEV,SAAS,CAAC,CAAC;AAE1E,MAAML,WAAW,MAAMoB,IAAAA,oBAAS,EAAC;IACtCN;IACAE;IACAC;IACAC;IACAC;IACAb;IAEAC;IACAC;IACAC;IACAC;IAEAC;IACAC;AACF;AAEAS,QAAQC,IAAI,CAACtB"}
1
+ {"version":3,"sources":["../src/worker.ts"],"sourcesContent":["import { workerData } from 'node:worker_threads';\nimport { SourceTextModule, SyntheticModule, createContext } from 'node:vm';\nimport { createRequire } from 'node:module';\nimport { fileURLToPath } from 'node:url';\nimport { benchmark } from './runner.js';\nimport { WorkerOptions } from './types.js';\n\nconst {\n baseUrl,\n setupCode,\n teardownCode,\n preCode,\n runCode,\n postCode,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n gcObserver = true,\n\n durationsSAB,\n controlSAB,\n}: WorkerOptions = workerData;\n\nconst serialize = (code?: string) => (code ? code : '() => {}');\n\nconst isCjs = typeof require !== 'undefined';\n\nconst resolveSpecifier = (specifier: string, parent: string) => {\n if (!isCjs) {\n try {\n return import.meta.resolve(specifier, parent);\n } catch {\n // fall through to CommonJS resolution\n }\n }\n const resolveFrom = createRequire(fileURLToPath(parent));\n return resolveFrom.resolve(specifier);\n};\n\nconst source = `\nexport const setup = ${serialize(setupCode)};\nexport const teardown = ${serialize(teardownCode)};\nexport const pre = ${serialize(preCode)};\nexport const run = ${serialize(runCode)};\nexport const post = ${serialize(postCode)};\n `;\n\nconst context = createContext({ console, Buffer });\nconst imports = new Map<string, SyntheticModule>();\nconst mod = new SourceTextModule(source, {\n identifier: baseUrl,\n context,\n initializeImportMeta(meta) {\n meta.url = baseUrl;\n },\n importModuleDynamically(specifier, referencingModule) {\n const base = referencingModule.identifier ?? baseUrl;\n const resolved = resolveSpecifier(specifier, base);\n return import(resolved);\n },\n});\n\nawait mod.link(async (specifier, referencingModule) => {\n const base = referencingModule.identifier ?? baseUrl;\n const target = resolveSpecifier(specifier, base);\n const cached = imports.get(target);\n if (cached) return cached;\n\n const importedModule = await import(target);\n const exportNames = Object.keys(importedModule);\n const imported = new SyntheticModule(\n exportNames,\n () => {\n exportNames.forEach((key) => imported.setExport(key, importedModule[key]));\n },\n { identifier: target, context: referencingModule.context },\n );\n imports.set(target, imported);\n return imported;\n});\n\nawait mod.evaluate();\nconst { setup, teardown, pre, run, post } = mod.namespace as any;\n\nif (!run) {\n throw new Error('Benchmark run function is required');\n}\n\nprocess.exitCode = await benchmark({\n baseUrl,\n setup,\n teardown,\n pre,\n run,\n post,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n gcObserver,\n\n durationsSAB,\n controlSAB,\n});\n"],"names":["baseUrl","setupCode","teardownCode","preCode","runCode","postCode","data","warmupCycles","minCycles","absThreshold","relThreshold","gcObserver","durationsSAB","controlSAB","workerData","serialize","code","isCjs","require","resolveSpecifier","specifier","parent","resolve","resolveFrom","createRequire","fileURLToPath","source","context","createContext","console","Buffer","imports","Map","mod","SourceTextModule","identifier","initializeImportMeta","meta","url","importModuleDynamically","referencingModule","base","resolved","link","target","cached","get","importedModule","exportNames","Object","keys","imported","SyntheticModule","forEach","key","setExport","set","evaluate","setup","teardown","pre","run","post","namespace","Error","process","exitCode","benchmark"],"mappings":";;;;oCAA2B;wBACsC;4BACnC;yBACA;2BACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAG1B,MAAM,EACJA,OAAO,EACPC,SAAS,EACTC,YAAY,EACZC,OAAO,EACPC,OAAO,EACPC,QAAQ,EACRC,IAAI,EAEJC,YAAY,EACZC,SAAS,EACTC,YAAY,EACZC,YAAY,EACZC,aAAa,IAAI,EAEjBC,YAAY,EACZC,UAAU,EACX,GAAkBC,8BAAU;AAE7B,MAAMC,YAAY,CAACC,OAAmBA,OAAOA,OAAO;AAEpD,MAAMC,QAAQ,OAAOC,YAAY;AAEjC,MAAMC,mBAAmB,CAACC,WAAmBC;IAC3C,IAAI,CAACJ,OAAO;QACV,IAAI;YACF,OAAO,QAAYK,OAAO,CAACF,WAAWC;QACxC,EAAE,OAAM,CAER;IACF;IACA,MAAME,cAAcC,IAAAA,yBAAa,EAACC,IAAAA,sBAAa,EAACJ;IAChD,OAAOE,YAAYD,OAAO,CAACF;AAC7B;AAEA,MAAMM,SAAS,CAAC;qBACK,EAAEX,UAAUd,WAAW;wBACpB,EAAEc,UAAUb,cAAc;mBAC/B,EAAEa,UAAUZ,SAAS;mBACrB,EAAEY,UAAUX,SAAS;oBACpB,EAAEW,UAAUV,UAAU;EACxC,CAAC;AAEH,MAAMsB,UAAUC,IAAAA,qBAAa,EAAC;IAAEC;IAASC;AAAO;AAChD,MAAMC,UAAU,IAAIC;AACpB,MAAMC,MAAM,IAAIC,wBAAgB,CAACR,QAAQ;IACvCS,YAAYnC;IACZ2B;IACAS,sBAAqBC,IAAI;QACvBA,KAAKC,GAAG,GAAGtC;IACb;IACAuC,yBAAwBnB,SAAS,EAAEoB,iBAAiB;QAClD,MAAMC,OAAOD,kBAAkBL,UAAU,IAAInC;QAC7C,MAAM0C,WAAWvB,iBAAiBC,WAAWqB;QAC7C,OAAO,gBAAOC,4DAAP;IACT;AACF;AAEA,MAAMT,IAAIU,IAAI,CAAC,OAAOvB,WAAWoB;IAC/B,MAAMC,OAAOD,kBAAkBL,UAAU,IAAInC;IAC7C,MAAM4C,SAASzB,iBAAiBC,WAAWqB;IAC3C,MAAMI,SAASd,QAAQe,GAAG,CAACF;IAC3B,IAAIC,QAAQ,OAAOA;IAEnB,MAAME,iBAAiB,MAAM,gBAAOH,0DAAP;IAC7B,MAAMI,cAAcC,OAAOC,IAAI,CAACH;IAChC,MAAMI,WAAW,IAAIC,uBAAe,CAClCJ,aACA;QACEA,YAAYK,OAAO,CAAC,CAACC,MAAQH,SAASI,SAAS,CAACD,KAAKP,cAAc,CAACO,IAAI;IAC1E,GACA;QAAEnB,YAAYS;QAAQjB,SAASa,kBAAkBb,OAAO;IAAC;IAE3DI,QAAQyB,GAAG,CAACZ,QAAQO;IACpB,OAAOA;AACT;AAEA,MAAMlB,IAAIwB,QAAQ;AAClB,MAAM,EAAEC,KAAK,EAAEC,QAAQ,EAAEC,GAAG,EAAEC,GAAG,EAAEC,IAAI,EAAE,GAAG7B,IAAI8B,SAAS;AAEzD,IAAI,CAACF,KAAK;IACR,MAAM,IAAIG,MAAM;AAClB;AAEAC,QAAQC,QAAQ,GAAG,MAAMC,IAAAA,oBAAS,EAAC;IACjCnE;IACA0D;IACAC;IACAC;IACAC;IACAC;IACAxD;IAEAC;IACAC;IACAC;IACAC;IACAC;IAEAC;IACAC;AACF"}
package/build/worker.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const exitCode: number;
1
+ export {};
package/build/worker.js CHANGED
@@ -1,12 +1,67 @@
1
1
  import { workerData } from 'node:worker_threads';
2
+ import { SourceTextModule, SyntheticModule, createContext } from 'node:vm';
3
+ import { createRequire } from 'node:module';
4
+ import { fileURLToPath } from 'node:url';
2
5
  import { benchmark } from "./runner.js";
3
- const { setupCode, teardownCode, preCode, runCode, postCode, data, warmupCycles, minCycles, absThreshold, relThreshold, durationsSAB, controlSAB } = workerData;
4
- const setup = setupCode && Function(`return ${setupCode};`)();
5
- const teardown = teardownCode && Function(`return ${teardownCode};`)();
6
- const pre = preCode && Function(`return ${preCode};`)();
7
- const run = runCode && Function(`return ${runCode};`)();
8
- const post = postCode && Function(`return ${postCode};`)();
9
- export const exitCode = await benchmark({
6
+ const { baseUrl, setupCode, teardownCode, preCode, runCode, postCode, data, warmupCycles, minCycles, absThreshold, relThreshold, gcObserver = true, durationsSAB, controlSAB } = workerData;
7
+ const serialize = (code)=>code ? code : '() => {}';
8
+ const isCjs = typeof require !== 'undefined';
9
+ const resolveSpecifier = (specifier, parent)=>{
10
+ if (!isCjs) {
11
+ try {
12
+ return import.meta.resolve(specifier, parent);
13
+ } catch {}
14
+ }
15
+ const resolveFrom = createRequire(fileURLToPath(parent));
16
+ return resolveFrom.resolve(specifier);
17
+ };
18
+ const source = `
19
+ export const setup = ${serialize(setupCode)};
20
+ export const teardown = ${serialize(teardownCode)};
21
+ export const pre = ${serialize(preCode)};
22
+ export const run = ${serialize(runCode)};
23
+ export const post = ${serialize(postCode)};
24
+ `;
25
+ const context = createContext({
26
+ console,
27
+ Buffer
28
+ });
29
+ const imports = new Map();
30
+ const mod = new SourceTextModule(source, {
31
+ identifier: baseUrl,
32
+ context,
33
+ initializeImportMeta (meta) {
34
+ meta.url = baseUrl;
35
+ },
36
+ importModuleDynamically (specifier, referencingModule) {
37
+ const base = referencingModule.identifier ?? baseUrl;
38
+ const resolved = resolveSpecifier(specifier, base);
39
+ return import(resolved);
40
+ }
41
+ });
42
+ await mod.link(async (specifier, referencingModule)=>{
43
+ const base = referencingModule.identifier ?? baseUrl;
44
+ const target = resolveSpecifier(specifier, base);
45
+ const cached = imports.get(target);
46
+ if (cached) return cached;
47
+ const importedModule = await import(target);
48
+ const exportNames = Object.keys(importedModule);
49
+ const imported = new SyntheticModule(exportNames, ()=>{
50
+ exportNames.forEach((key)=>imported.setExport(key, importedModule[key]));
51
+ }, {
52
+ identifier: target,
53
+ context: referencingModule.context
54
+ });
55
+ imports.set(target, imported);
56
+ return imported;
57
+ });
58
+ await mod.evaluate();
59
+ const { setup, teardown, pre, run, post } = mod.namespace;
60
+ if (!run) {
61
+ throw new Error('Benchmark run function is required');
62
+ }
63
+ process.exitCode = await benchmark({
64
+ baseUrl,
10
65
  setup,
11
66
  teardown,
12
67
  pre,
@@ -17,9 +72,9 @@ export const exitCode = await benchmark({
17
72
  minCycles,
18
73
  absThreshold,
19
74
  relThreshold,
75
+ gcObserver,
20
76
  durationsSAB,
21
77
  controlSAB
22
78
  });
23
- process.exit(exitCode);
24
79
 
25
80
  //# sourceMappingURL=worker.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/worker.ts"],"sourcesContent":["import { workerData } from 'node:worker_threads';\nimport { benchmark } from './runner.js';\nimport { SetupFn, TeardownFn, StepFn, WorkerOptions } from './types.js';\n\nconst {\n setupCode,\n teardownCode,\n preCode,\n runCode,\n postCode,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n\n durationsSAB,\n controlSAB,\n}: WorkerOptions = workerData;\n\nconst setup: SetupFn<unknown> = setupCode && Function(`return ${setupCode};`)();\nconst teardown: TeardownFn<unknown> = teardownCode && Function(`return ${teardownCode};`)();\n\nconst pre: StepFn<unknown, unknown> = preCode && Function(`return ${preCode};`)();\nconst run: StepFn<unknown, unknown> = runCode && Function(`return ${runCode};`)();\nconst post: StepFn<unknown, unknown> = postCode && Function(`return ${postCode};`)();\n\nexport const exitCode = await benchmark({\n setup,\n teardown,\n pre,\n run,\n post,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n\n durationsSAB,\n controlSAB,\n});\n\nprocess.exit(exitCode);\n"],"names":["workerData","benchmark","setupCode","teardownCode","preCode","runCode","postCode","data","warmupCycles","minCycles","absThreshold","relThreshold","durationsSAB","controlSAB","setup","Function","teardown","pre","run","post","exitCode","process","exit"],"mappings":"AAAA,SAASA,UAAU,QAAQ,sBAAsB;AACjD,SAASC,SAAS,QAAQ,cAAc;AAGxC,MAAM,EACJC,SAAS,EACTC,YAAY,EACZC,OAAO,EACPC,OAAO,EACPC,QAAQ,EACRC,IAAI,EAEJC,YAAY,EACZC,SAAS,EACTC,YAAY,EACZC,YAAY,EAEZC,YAAY,EACZC,UAAU,EACX,GAAkBb;AAEnB,MAAMc,QAA0BZ,aAAaa,SAAS,CAAC,OAAO,EAAEb,UAAU,CAAC,CAAC;AAC5E,MAAMc,WAAgCb,gBAAgBY,SAAS,CAAC,OAAO,EAAEZ,aAAa,CAAC,CAAC;AAExF,MAAMc,MAAgCb,WAAWW,SAAS,CAAC,OAAO,EAAEX,QAAQ,CAAC,CAAC;AAC9E,MAAMc,MAAgCb,WAAWU,SAAS,CAAC,OAAO,EAAEV,QAAQ,CAAC,CAAC;AAC9E,MAAMc,OAAiCb,YAAYS,SAAS,CAAC,OAAO,EAAET,SAAS,CAAC,CAAC;AAEjF,OAAO,MAAMc,WAAW,MAAMnB,UAAU;IACtCa;IACAE;IACAC;IACAC;IACAC;IACAZ;IAEAC;IACAC;IACAC;IACAC;IAEAC;IACAC;AACF,GAAG;AAEHQ,QAAQC,IAAI,CAACF"}
1
+ {"version":3,"sources":["../src/worker.ts"],"sourcesContent":["import { workerData } from 'node:worker_threads';\nimport { SourceTextModule, SyntheticModule, createContext } from 'node:vm';\nimport { createRequire } from 'node:module';\nimport { fileURLToPath } from 'node:url';\nimport { benchmark } from './runner.js';\nimport { WorkerOptions } from './types.js';\n\nconst {\n baseUrl,\n setupCode,\n teardownCode,\n preCode,\n runCode,\n postCode,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n gcObserver = true,\n\n durationsSAB,\n controlSAB,\n}: WorkerOptions = workerData;\n\nconst serialize = (code?: string) => (code ? code : '() => {}');\n\nconst isCjs = typeof require !== 'undefined';\n\nconst resolveSpecifier = (specifier: string, parent: string) => {\n if (!isCjs) {\n try {\n return import.meta.resolve(specifier, parent);\n } catch {\n // fall through to CommonJS resolution\n }\n }\n const resolveFrom = createRequire(fileURLToPath(parent));\n return resolveFrom.resolve(specifier);\n};\n\nconst source = `\nexport const setup = ${serialize(setupCode)};\nexport const teardown = ${serialize(teardownCode)};\nexport const pre = ${serialize(preCode)};\nexport const run = ${serialize(runCode)};\nexport const post = ${serialize(postCode)};\n `;\n\nconst context = createContext({ console, Buffer });\nconst imports = new Map<string, SyntheticModule>();\nconst mod = new SourceTextModule(source, {\n identifier: baseUrl,\n context,\n initializeImportMeta(meta) {\n meta.url = baseUrl;\n },\n importModuleDynamically(specifier, referencingModule) {\n const base = referencingModule.identifier ?? baseUrl;\n const resolved = resolveSpecifier(specifier, base);\n return import(resolved);\n },\n});\n\nawait mod.link(async (specifier, referencingModule) => {\n const base = referencingModule.identifier ?? baseUrl;\n const target = resolveSpecifier(specifier, base);\n const cached = imports.get(target);\n if (cached) return cached;\n\n const importedModule = await import(target);\n const exportNames = Object.keys(importedModule);\n const imported = new SyntheticModule(\n exportNames,\n () => {\n exportNames.forEach((key) => imported.setExport(key, importedModule[key]));\n },\n { identifier: target, context: referencingModule.context },\n );\n imports.set(target, imported);\n return imported;\n});\n\nawait mod.evaluate();\nconst { setup, teardown, pre, run, post } = mod.namespace as any;\n\nif (!run) {\n throw new Error('Benchmark run function is required');\n}\n\nprocess.exitCode = await benchmark({\n baseUrl,\n setup,\n teardown,\n pre,\n run,\n post,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n gcObserver,\n\n durationsSAB,\n controlSAB,\n});\n"],"names":["workerData","SourceTextModule","SyntheticModule","createContext","createRequire","fileURLToPath","benchmark","baseUrl","setupCode","teardownCode","preCode","runCode","postCode","data","warmupCycles","minCycles","absThreshold","relThreshold","gcObserver","durationsSAB","controlSAB","serialize","code","isCjs","require","resolveSpecifier","specifier","parent","resolve","resolveFrom","source","context","console","Buffer","imports","Map","mod","identifier","initializeImportMeta","meta","url","importModuleDynamically","referencingModule","base","resolved","link","target","cached","get","importedModule","exportNames","Object","keys","imported","forEach","key","setExport","set","evaluate","setup","teardown","pre","run","post","namespace","Error","process","exitCode"],"mappings":"AAAA,SAASA,UAAU,QAAQ,sBAAsB;AACjD,SAASC,gBAAgB,EAAEC,eAAe,EAAEC,aAAa,QAAQ,UAAU;AAC3E,SAASC,aAAa,QAAQ,cAAc;AAC5C,SAASC,aAAa,QAAQ,WAAW;AACzC,SAASC,SAAS,QAAQ,cAAc;AAGxC,MAAM,EACJC,OAAO,EACPC,SAAS,EACTC,YAAY,EACZC,OAAO,EACPC,OAAO,EACPC,QAAQ,EACRC,IAAI,EAEJC,YAAY,EACZC,SAAS,EACTC,YAAY,EACZC,YAAY,EACZC,aAAa,IAAI,EAEjBC,YAAY,EACZC,UAAU,EACX,GAAkBpB;AAEnB,MAAMqB,YAAY,CAACC,OAAmBA,OAAOA,OAAO;AAEpD,MAAMC,QAAQ,OAAOC,YAAY;AAEjC,MAAMC,mBAAmB,CAACC,WAAmBC;IAC3C,IAAI,CAACJ,OAAO;QACV,IAAI;YACF,OAAO,YAAYK,OAAO,CAACF,WAAWC;QACxC,EAAE,OAAM,CAER;IACF;IACA,MAAME,cAAczB,cAAcC,cAAcsB;IAChD,OAAOE,YAAYD,OAAO,CAACF;AAC7B;AAEA,MAAMI,SAAS,CAAC;qBACK,EAAET,UAAUb,WAAW;wBACpB,EAAEa,UAAUZ,cAAc;mBAC/B,EAAEY,UAAUX,SAAS;mBACrB,EAAEW,UAAUV,SAAS;oBACpB,EAAEU,UAAUT,UAAU;EACxC,CAAC;AAEH,MAAMmB,UAAU5B,cAAc;IAAE6B;IAASC;AAAO;AAChD,MAAMC,UAAU,IAAIC;AACpB,MAAMC,MAAM,IAAInC,iBAAiB6B,QAAQ;IACvCO,YAAY9B;IACZwB;IACAO,sBAAqBC,IAAI;QACvBA,KAAKC,GAAG,GAAGjC;IACb;IACAkC,yBAAwBf,SAAS,EAAEgB,iBAAiB;QAClD,MAAMC,OAAOD,kBAAkBL,UAAU,IAAI9B;QAC7C,MAAMqC,WAAWnB,iBAAiBC,WAAWiB;QAC7C,OAAO,MAAM,CAACC;IAChB;AACF;AAEA,MAAMR,IAAIS,IAAI,CAAC,OAAOnB,WAAWgB;IAC/B,MAAMC,OAAOD,kBAAkBL,UAAU,IAAI9B;IAC7C,MAAMuC,SAASrB,iBAAiBC,WAAWiB;IAC3C,MAAMI,SAASb,QAAQc,GAAG,CAACF;IAC3B,IAAIC,QAAQ,OAAOA;IAEnB,MAAME,iBAAiB,MAAM,MAAM,CAACH;IACpC,MAAMI,cAAcC,OAAOC,IAAI,CAACH;IAChC,MAAMI,WAAW,IAAInD,gBACnBgD,aACA;QACEA,YAAYI,OAAO,CAAC,CAACC,MAAQF,SAASG,SAAS,CAACD,KAAKN,cAAc,CAACM,IAAI;IAC1E,GACA;QAAElB,YAAYS;QAAQf,SAASW,kBAAkBX,OAAO;IAAC;IAE3DG,QAAQuB,GAAG,CAACX,QAAQO;IACpB,OAAOA;AACT;AAEA,MAAMjB,IAAIsB,QAAQ;AAClB,MAAM,EAAEC,KAAK,EAAEC,QAAQ,EAAEC,GAAG,EAAEC,GAAG,EAAEC,IAAI,EAAE,GAAG3B,IAAI4B,SAAS;AAEzD,IAAI,CAACF,KAAK;IACR,MAAM,IAAIG,MAAM;AAClB;AAEAC,QAAQC,QAAQ,GAAG,MAAM7D,UAAU;IACjCC;IACAoD;IACAC;IACAC;IACAC;IACAC;IACAlD;IAEAC;IACAC;IACAC;IACAC;IACAC;IAEAC;IACAC;AACF"}
@@ -0,0 +1,54 @@
1
+ import 'overtake';
2
+
3
+ const baseline = benchmark('accuracy tuning feed', () => {
4
+ const bytes = 131_072;
5
+ const uints = new Uint32Array(bytes / 4);
6
+ for (let i = 0; i < uints.length; i++) {
7
+ uints[i] = (i * 31) ^ 0x9e3779b1;
8
+ }
9
+ const src = Buffer.alloc(bytes, 7);
10
+ const dst = Buffer.allocUnsafe(bytes);
11
+
12
+ return { uints, src, dst };
13
+ });
14
+
15
+ baseline
16
+ .target('deterministic math', () => {
17
+ return { scratch: 0 };
18
+ })
19
+ .measure('sum uint32 array', (ctx, { uints }) => {
20
+ let acc = ctx.scratch;
21
+ for (let round = 0; round < 8; round++) {
22
+ for (let i = 0; i < uints.length; i++) {
23
+ acc += uints[i];
24
+ }
25
+ }
26
+ ctx.scratch = acc;
27
+ });
28
+
29
+ baseline.target('buffer reuse copy').measure('copy preallocated buffer', (_, { src, dst }) => {
30
+ for (let round = 0; round < 8; round++) {
31
+ dst.set(src);
32
+ }
33
+ });
34
+
35
+ baseline
36
+ .target('buffer reuse xor')
37
+ .measure('xor in place', (_, { src, dst }) => {
38
+ for (let round = 0; round < 4; round++) {
39
+ for (let i = 0; i < src.length; i++) {
40
+ dst[i] ^= src[i];
41
+ }
42
+ }
43
+ })
44
+ .pre((_, { dst }) => {
45
+ dst.fill(0);
46
+ });
47
+
48
+ baseline.target('steady loop baseline').measure('counter increment', () => {
49
+ let x = 0;
50
+ for (let i = 0; i < 200_000; i++) {
51
+ x += (i & 1023) >>> 0;
52
+ }
53
+ return x;
54
+ });
@@ -1,7 +1,6 @@
1
1
  // Demonstrates custom report types and statistics
2
2
  // CLI mode: npx overtake examples/custom-reports.ts -f table -r ops mean median p95 p99
3
3
  // Programmatic mode: node examples/custom-reports.js
4
-
5
4
  import { Benchmark, printTableReports } from '../build/index.js';
6
5
 
7
6
  const performanceSuite = new Benchmark('10K numbers', () => Array.from({ length: 10_000 }, () => Math.random() * 1000));
@@ -1,5 +1,6 @@
1
1
  // Demonstrates correct import patterns for worker context
2
2
  // Run: npx overtake examples/imports.ts
3
+ import 'overtake';
3
4
 
4
5
  const importExamples = benchmark('test data', () => Buffer.from('hello world'));
5
6
 
@@ -13,14 +14,9 @@ importExamples
13
14
  createHash('sha256').update(buffer).digest('hex');
14
15
  });
15
16
 
16
- // IMPORTANT: Local files need absolute paths
17
- // Relative imports resolve from node_modules/overtake/build/, not your project
17
+ // Relative imports resolve relative to benchmark file
18
18
  const localFilesTarget = importExamples.target('local files', async () => {
19
- const { join } = await import('node:path');
20
-
21
- // Build absolute path to your local module
22
- const localModulePath = join(process.cwd(), './build/types.js');
23
- const { DEFAULT_CYCLES, Control } = await import(localModulePath);
19
+ const { DEFAULT_CYCLES, Control } = await import('../build/types.js');
24
20
 
25
21
  return {
26
22
  DEFAULT_CYCLES,
@@ -1,5 +1,7 @@
1
1
  // Minimal example - comparing array sum algorithms
2
2
  // Run: npx overtake examples/quick-start.ts
3
+ // Import overtake to support global types
4
+ import 'overtake';
3
5
 
4
6
  const sumBenchmark = benchmark('1M numbers', () => Array.from({ length: 1_000_000 }, (_, index) => index));
5
7
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overtake",
3
- "version": "1.0.5",
3
+ "version": "1.1.1",
4
4
  "description": "NodeJS performance benchmark",
5
5
  "type": "module",
6
6
  "types": "build/index.d.ts",
@@ -41,23 +41,24 @@
41
41
  },
42
42
  "homepage": "https://github.com/3axap4eHko/overtake#readme",
43
43
  "devDependencies": {
44
- "@jest/globals": "^30.0.5",
44
+ "@jest/globals": "^30.2.0",
45
45
  "@swc/jest": "^0.2.39",
46
46
  "@types/async": "^3.2.25",
47
47
  "@types/jest": "^30.0.0",
48
- "@types/node": "^24.3.0",
48
+ "@types/node": "^25.0.3",
49
49
  "husky": "^9.1.7",
50
- "inop": "^0.7.9",
51
- "jest": "^30.0.5",
52
- "prettier": "^3.6.2",
50
+ "inop": "^0.8.3",
51
+ "jest": "^30.2.0",
52
+ "overtake": "^1.1.0",
53
+ "prettier": "^3.7.4",
53
54
  "pretty-quick": "^4.2.2",
54
- "typescript": "^5.9.2"
55
+ "typescript": "^5.9.3"
55
56
  },
56
57
  "dependencies": {
57
- "@swc/core": "^1.13.5",
58
+ "@swc/core": "^1.15.7",
58
59
  "async": "^3.2.6",
59
- "commander": "^14.0.0",
60
- "glob": "^11.0.3"
60
+ "commander": "^14.0.2",
61
+ "glob": "^13.0.0"
61
62
  },
62
63
  "scripts": {
63
64
  "build": "rm -rf build && inop src build -i __tests__ -i *.tmp.ts && tsc --declaration --emitDeclarationOnly",
package/src/cli.ts CHANGED
@@ -1,10 +1,11 @@
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
 
10
11
  const require = createRequire(import.meta.url);
@@ -12,43 +13,34 @@ const { name, description, version } = require('../package.json');
12
13
 
13
14
  const commander = new Command();
14
15
 
15
- const transpile = async (code: string): Promise<string> => {
16
- const output = await transform(code, {
17
- filename: 'benchmark.ts',
18
- jsc: {
19
- parser: {
20
- syntax: 'typescript',
21
- tsx: false,
22
- dynamicImport: true,
23
- },
24
- target: 'esnext',
25
- },
26
- module: {
27
- type: 'es6',
28
- },
29
- });
30
- return output.code;
31
- };
32
-
33
16
  commander
34
17
  .name(name)
35
18
  .description(description)
36
19
  .version(version)
37
- .argument('<path>', 'glob pattern to find benchmarks')
20
+ .argument('<paths...>', 'glob pattern to find benchmarks')
38
21
  .addOption(new Option('-r, --report-types [reportTypes...]', 'statistic types to include in the report').choices(REPORT_TYPES).default(DEFAULT_REPORT_TYPES))
39
22
  .addOption(new Option('-w, --workers [workers]', 'number of concurent workers').default(DEFAULT_WORKERS).argParser(parseInt))
40
23
  .addOption(new Option('-f, --format [format]', 'output format').default('simple').choices(['simple', 'json', 'pjson', 'table']))
41
- .addOption(new Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseInt))
42
- .addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseInt))
24
+ .addOption(new Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseFloat))
25
+ .addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseFloat))
43
26
  .addOption(new Option('--warmup-cycles [warmupCycles]', 'number of warmup cycles before measuring').argParser(parseInt))
44
27
  .addOption(new Option('--max-cycles [maxCycles]', 'maximum measurement cycles per feed').argParser(parseInt))
45
28
  .addOption(new Option('--min-cycles [minCycles]', 'minimum measurement cycles per feed').argParser(parseInt))
46
- .action(async (path, executeOptions) => {
47
- const files = await glob(path, { absolute: true, cwd: process.cwd() }).catch(() => []);
29
+ .addOption(new Option('--no-gc-observer', 'disable GC overlap detection'))
30
+ .action(async (patterns: string[], executeOptions) => {
31
+ const files = new Set<string>();
32
+ await Promise.all(
33
+ patterns.map(async (pattern) => {
34
+ const matches = await glob(pattern, { absolute: true, cwd: process.cwd() }).catch(() => []);
35
+ matches.forEach((file) => files.add(file));
36
+ }),
37
+ );
38
+
48
39
  for (const file of files) {
49
40
  const stats = await stat(file).catch(() => false as const);
50
41
  if (stats && stats.isFile()) {
51
42
  const content = await readFile(file, 'utf8');
43
+ const identifier = pathToFileURL(file).href;
52
44
  const code = await transpile(content);
53
45
  let instance: Benchmark<unknown> | undefined;
54
46
  const benchmark = (...args: Parameters<(typeof Benchmark)['create']>) => {
@@ -59,33 +51,54 @@ commander
59
51
  return instance;
60
52
  };
61
53
  const script = new SourceTextModule(code, {
54
+ identifier,
62
55
  context: createContext({
63
56
  benchmark,
64
57
  Buffer,
58
+ console,
65
59
  }),
60
+ initializeImportMeta(meta) {
61
+ meta.url = identifier;
62
+ },
63
+ async importModuleDynamically(specifier, referencingModule) {
64
+ if (Module.isBuiltin(specifier)) {
65
+ return import(specifier);
66
+ }
67
+ const baseIdentifier = referencingModule.identifier ?? identifier;
68
+ const resolveFrom = createRequire(fileURLToPath(baseIdentifier));
69
+ const resolved = resolveFrom.resolve(specifier);
70
+ return import(resolved);
71
+ },
66
72
  });
67
- const imports = new Map();
73
+ const imports = new Map<string, SyntheticModule>();
68
74
  await script.link(async (specifier: string, referencingModule) => {
69
- if (imports.has(specifier)) {
70
- return imports.get(specifier);
75
+ const baseIdentifier = referencingModule.identifier ?? identifier;
76
+ const resolveFrom = createRequire(fileURLToPath(baseIdentifier));
77
+ const target = Module.isBuiltin(specifier) ? specifier : resolveFrom.resolve(specifier);
78
+ const cached = imports.get(target);
79
+ if (cached) {
80
+ return cached;
71
81
  }
72
- const mod = await import(Module.isBuiltin(specifier) ? specifier : require.resolve(specifier));
82
+ const mod = await import(target);
73
83
  const exportNames = Object.keys(mod);
74
84
  const imported = new SyntheticModule(
75
85
  exportNames,
76
86
  () => {
77
87
  exportNames.forEach((key) => imported.setExport(key, mod[key]));
78
88
  },
79
- { identifier: specifier, context: referencingModule.context },
89
+ { identifier: target, context: referencingModule.context },
80
90
  );
81
91
 
82
- imports.set(specifier, imported);
92
+ imports.set(target, imported);
83
93
  return imported;
84
94
  });
85
95
  await script.evaluate();
86
96
 
87
97
  if (instance) {
88
- const reports = await instance.execute(executeOptions);
98
+ const reports = await instance.execute({
99
+ ...executeOptions,
100
+ baseUrl: identifier,
101
+ });
89
102
  switch (executeOptions.format) {
90
103
  case 'json':
91
104
  {
package/src/executor.ts CHANGED
@@ -1,27 +1,31 @@
1
1
  import { Worker } from 'node:worker_threads';
2
2
  import { once } from 'node:events';
3
3
  import { queue } from 'async';
4
- import { RunOptions, ReportOptions, WorkerOptions, BenchmarkOptions, Control, ReportType, ReportTypeList, CONTROL_SLOTS } from './types.js';
4
+ import { pathToFileURL } from 'node:url';
5
5
  import { createReport, Report } from './reporter.js';
6
6
  import { cmp } from './utils.js';
7
+ import { RunOptions, ReportOptions, WorkerOptions, BenchmarkOptions, Control, ReportType, ReportTypeList, CONTROL_SLOTS } from './types.js';
7
8
 
8
9
  export type ExecutorReport<R extends ReportTypeList> = Record<R[number], Report> & { count: number };
9
10
 
10
11
  export interface ExecutorOptions<R extends ReportTypeList> extends BenchmarkOptions, ReportOptions<R> {
12
+ baseUrl?: string;
11
13
  workers?: number;
12
14
  maxCycles?: number;
13
15
  }
14
16
 
15
17
  export const createExecutor = <TContext, TInput, R extends ReportTypeList>({
18
+ baseUrl = pathToFileURL(process.cwd()).href,
16
19
  workers,
17
20
  warmupCycles,
18
21
  maxCycles,
19
22
  minCycles,
20
23
  absThreshold,
21
24
  relThreshold,
25
+ gcObserver = true,
22
26
  reportTypes,
23
27
  }: Required<ExecutorOptions<R>>) => {
24
- const executor = queue<RunOptions<TContext, TInput>>(async ({ setup, teardown, pre, run, post, data }) => {
28
+ const executor = queue<RunOptions<TContext, TInput>>(async ({ baseUrl: runBaseUrl = baseUrl, setup, teardown, pre, run, post, data }) => {
25
29
  const setupCode = setup?.toString();
26
30
  const teardownCode = teardown?.toString();
27
31
  const preCode = pre?.toString();
@@ -33,6 +37,7 @@ export const createExecutor = <TContext, TInput, R extends ReportTypeList>({
33
37
 
34
38
  const workerFile = new URL('./worker.js', import.meta.url);
35
39
  const workerData: WorkerOptions = {
40
+ baseUrl: runBaseUrl,
36
41
  setupCode,
37
42
  teardownCode,
38
43
  preCode,
@@ -44,6 +49,7 @@ export const createExecutor = <TContext, TInput, R extends ReportTypeList>({
44
49
  minCycles,
45
50
  absThreshold,
46
51
  relThreshold,
52
+ gcObserver,
47
53
 
48
54
  controlSAB,
49
55
  durationsSAB,
@@ -0,0 +1,23 @@
1
+ export interface GCMarker {
2
+ ref: WeakRef<object>;
3
+ token: object;
4
+ }
5
+
6
+ export class GCWatcher {
7
+ #registry = new FinalizationRegistry(() => {});
8
+
9
+ start(): GCMarker {
10
+ const token = {};
11
+ const ref = new WeakRef(token);
12
+ this.#registry.register(token, null, token);
13
+ return { ref, token };
14
+ }
15
+
16
+ seen(marker: GCMarker): boolean {
17
+ const collected = marker.ref.deref() === undefined;
18
+ if (!collected) {
19
+ this.#registry.unregister(marker.token);
20
+ }
21
+ return collected;
22
+ }
23
+ }