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.
- package/README.md +25 -29
- package/build/cli.cjs +43 -33
- package/build/cli.cjs.map +1 -1
- package/build/cli.js +42 -32
- package/build/cli.js.map +1 -1
- package/build/executor.cjs +6 -3
- package/build/executor.cjs.map +1 -1
- package/build/executor.d.ts +3 -2
- package/build/executor.js +6 -3
- package/build/executor.js.map +1 -1
- package/build/gc-watcher.cjs +31 -0
- package/build/gc-watcher.cjs.map +1 -0
- package/build/gc-watcher.d.ts +9 -0
- package/build/gc-watcher.js +21 -0
- package/build/gc-watcher.js.map +1 -0
- package/build/index.cjs +9 -1
- package/build/index.cjs.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.js +9 -1
- package/build/index.js.map +1 -1
- package/build/runner.cjs +229 -24
- package/build/runner.cjs.map +1 -1
- package/build/runner.d.ts +1 -1
- package/build/runner.js +229 -24
- package/build/runner.js.map +1 -1
- package/build/types.cjs.map +1 -1
- package/build/types.d.ts +4 -0
- package/build/types.js.map +1 -1
- package/build/utils.cjs +21 -0
- package/build/utils.cjs.map +1 -1
- package/build/utils.d.ts +1 -0
- package/build/utils.js +18 -0
- package/build/utils.js.map +1 -1
- package/build/worker.cjs +104 -14
- package/build/worker.cjs.map +1 -1
- package/build/worker.d.ts +1 -1
- package/build/worker.js +63 -8
- package/build/worker.js.map +1 -1
- package/examples/accuracy.ts +54 -0
- package/examples/custom-reports.ts +0 -1
- package/examples/imports.ts +3 -7
- package/examples/quick-start.ts +2 -0
- package/package.json +11 -10
- package/src/cli.ts +44 -31
- package/src/executor.ts +8 -2
- package/src/gc-watcher.ts +23 -0
- package/src/index.ts +11 -0
- package/src/runner.ts +269 -23
- package/src/types.ts +4 -0
- package/src/utils.ts +20 -0
- package/src/worker.ts +72 -9
- package/build/queue.cjs +0 -48
- package/build/queue.cjs.map +0 -1
- package/build/queue.d.ts +0 -3
- package/build/queue.js +0 -38
- package/build/queue.js.map +0 -1
- 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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
package/build/worker.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/worker.ts"],"sourcesContent":["import { workerData } from 'node:worker_threads';\nimport { benchmark } from './runner.js';\nimport {
|
|
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
|
|
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
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
package/build/worker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/worker.ts"],"sourcesContent":["import { workerData } from 'node:worker_threads';\nimport { benchmark } from './runner.js';\nimport {
|
|
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));
|
package/examples/imports.ts
CHANGED
|
@@ -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
|
-
//
|
|
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 {
|
|
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,
|
package/examples/quick-start.ts
CHANGED
|
@@ -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.
|
|
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
|
|
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": "^
|
|
48
|
+
"@types/node": "^25.0.3",
|
|
49
49
|
"husky": "^9.1.7",
|
|
50
|
-
"inop": "^0.
|
|
51
|
-
"jest": "^30.0
|
|
52
|
-
"
|
|
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.
|
|
55
|
+
"typescript": "^5.9.3"
|
|
55
56
|
},
|
|
56
57
|
"dependencies": {
|
|
57
|
-
"@swc/core": "^1.
|
|
58
|
+
"@swc/core": "^1.15.7",
|
|
58
59
|
"async": "^3.2.6",
|
|
59
|
-
"commander": "^14.0.
|
|
60
|
-
"glob": "^
|
|
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('<
|
|
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(
|
|
42
|
-
.addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(
|
|
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
|
-
.
|
|
47
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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(
|
|
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:
|
|
89
|
+
{ identifier: target, context: referencingModule.context },
|
|
80
90
|
);
|
|
81
91
|
|
|
82
|
-
imports.set(
|
|
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(
|
|
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 {
|
|
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
|
+
}
|