overtake 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) 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 +226 -24
  22. package/build/runner.cjs.map +1 -1
  23. package/build/runner.d.ts +1 -1
  24. package/build/runner.js +226 -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 +95 -14
  35. package/build/worker.cjs.map +1 -1
  36. package/build/worker.d.ts +1 -1
  37. package/build/worker.js +54 -8
  38. package/build/worker.js.map +1 -1
  39. package/examples/accuracy.ts +54 -0
  40. package/examples/custom-reports.ts +0 -1
  41. package/examples/imports.ts +3 -7
  42. package/examples/quick-start.ts +2 -0
  43. package/package.json +10 -9
  44. package/src/cli.ts +42 -29
  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 +265 -23
  49. package/src/types.ts +4 -0
  50. package/src/utils.ts +20 -0
  51. package/src/worker.ts +59 -9
package/build/worker.cjs CHANGED
@@ -2,21 +2,102 @@
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 source = `
54
+ export const setup = ${serialize(setupCode)};
55
+ export const teardown = ${serialize(teardownCode)};
56
+ export const pre = ${serialize(preCode)};
57
+ export const run = ${serialize(runCode)};
58
+ export const post = ${serialize(postCode)};
59
+ `;
60
+ const context = (0, _nodevm.createContext)({
61
+ console,
62
+ Buffer
63
+ });
64
+ const imports = new Map();
65
+ const mod = new _nodevm.SourceTextModule(source, {
66
+ identifier: baseUrl,
67
+ context,
68
+ initializeImportMeta (meta) {
69
+ meta.url = baseUrl;
70
+ },
71
+ importModuleDynamically (specifier, referencingModule) {
72
+ const base = referencingModule.identifier ?? baseUrl;
73
+ const resolveFrom = (0, _nodemodule.createRequire)((0, _nodeurl.fileURLToPath)(base));
74
+ return Promise.resolve(resolveFrom.resolve(specifier)).then((p)=>/*#__PURE__*/ _interop_require_wildcard(require(p)));
75
+ }
76
+ });
77
+ await mod.link(async (specifier, referencingModule)=>{
78
+ const base = referencingModule.identifier ?? baseUrl;
79
+ const resolveFrom = (0, _nodemodule.createRequire)((0, _nodeurl.fileURLToPath)(base));
80
+ const target = resolveFrom.resolve(specifier);
81
+ const cached = imports.get(target);
82
+ if (cached) return cached;
83
+ const importedModule = await Promise.resolve(target).then((p)=>/*#__PURE__*/ _interop_require_wildcard(require(p)));
84
+ const exportNames = Object.keys(importedModule);
85
+ const imported = new _nodevm.SyntheticModule(exportNames, ()=>{
86
+ exportNames.forEach((key)=>imported.setExport(key, importedModule[key]));
87
+ }, {
88
+ identifier: target,
89
+ context: referencingModule.context
90
+ });
91
+ imports.set(target, imported);
92
+ return imported;
93
+ });
94
+ await mod.evaluate();
95
+ const { setup, teardown, pre, run, post } = mod.namespace;
96
+ if (!run) {
97
+ throw new Error('Benchmark run function is required');
98
+ }
99
+ process.exitCode = await (0, _runnercjs.benchmark)({
100
+ baseUrl,
20
101
  setup,
21
102
  teardown,
22
103
  pre,
@@ -27,9 +108,9 @@ const exitCode = await (0, _runnercjs.benchmark)({
27
108
  minCycles,
28
109
  absThreshold,
29
110
  relThreshold,
111
+ gcObserver,
30
112
  durationsSAB,
31
113
  controlSAB
32
114
  });
33
- process.exit(exitCode);
34
115
 
35
116
  //# 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 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 resolveFrom = createRequire(fileURLToPath(base));\n return import(resolveFrom.resolve(specifier));\n },\n});\n\nawait mod.link(async (specifier, referencingModule) => {\n const base = referencingModule.identifier ?? baseUrl;\n const resolveFrom = createRequire(fileURLToPath(base));\n const target = resolveFrom.resolve(specifier);\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","source","context","createContext","console","Buffer","imports","Map","mod","SourceTextModule","identifier","initializeImportMeta","meta","url","importModuleDynamically","specifier","referencingModule","base","resolveFrom","createRequire","fileURLToPath","resolve","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,SAAS,CAAC;qBACK,EAAEF,UAAUd,WAAW;wBACpB,EAAEc,UAAUb,cAAc;mBAC/B,EAAEa,UAAUZ,SAAS;mBACrB,EAAEY,UAAUX,SAAS;oBACpB,EAAEW,UAAUV,UAAU;EACxC,CAAC;AAEH,MAAMa,UAAUC,IAAAA,qBAAa,EAAC;IAAEC;IAASC;AAAO;AAChD,MAAMC,UAAU,IAAIC;AACpB,MAAMC,MAAM,IAAIC,wBAAgB,CAACR,QAAQ;IACvCS,YAAY1B;IACZkB;IACAS,sBAAqBC,IAAI;QACvBA,KAAKC,GAAG,GAAG7B;IACb;IACA8B,yBAAwBC,SAAS,EAAEC,iBAAiB;QAClD,MAAMC,OAAOD,kBAAkBN,UAAU,IAAI1B;QAC7C,MAAMkC,cAAcC,IAAAA,yBAAa,EAACC,IAAAA,sBAAa,EAACH;QAChD,OAAO,gBAAOC,YAAYG,OAAO,CAACN,8DAA3B;IACT;AACF;AAEA,MAAMP,IAAIc,IAAI,CAAC,OAAOP,WAAWC;IAC/B,MAAMC,OAAOD,kBAAkBN,UAAU,IAAI1B;IAC7C,MAAMkC,cAAcC,IAAAA,yBAAa,EAACC,IAAAA,sBAAa,EAACH;IAChD,MAAMM,SAASL,YAAYG,OAAO,CAACN;IACnC,MAAMS,SAASlB,QAAQmB,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;QAAEvB,YAAYa;QAAQrB,SAASc,kBAAkBd,OAAO;IAAC;IAE3DI,QAAQ6B,GAAG,CAACZ,QAAQO;IACpB,OAAOA;AACT;AAEA,MAAMtB,IAAI4B,QAAQ;AAClB,MAAM,EAAEC,KAAK,EAAEC,QAAQ,EAAEC,GAAG,EAAEC,GAAG,EAAEC,IAAI,EAAE,GAAGjC,IAAIkC,SAAS;AAEzD,IAAI,CAACF,KAAK;IACR,MAAM,IAAIG,MAAM;AAClB;AAEAC,QAAQC,QAAQ,GAAG,MAAMC,IAAAA,oBAAS,EAAC;IACjC9D;IACAqD;IACAC;IACAC;IACAC;IACAC;IACAnD;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,58 @@
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 source = `
9
+ export const setup = ${serialize(setupCode)};
10
+ export const teardown = ${serialize(teardownCode)};
11
+ export const pre = ${serialize(preCode)};
12
+ export const run = ${serialize(runCode)};
13
+ export const post = ${serialize(postCode)};
14
+ `;
15
+ const context = createContext({
16
+ console,
17
+ Buffer
18
+ });
19
+ const imports = new Map();
20
+ const mod = new SourceTextModule(source, {
21
+ identifier: baseUrl,
22
+ context,
23
+ initializeImportMeta (meta) {
24
+ meta.url = baseUrl;
25
+ },
26
+ importModuleDynamically (specifier, referencingModule) {
27
+ const base = referencingModule.identifier ?? baseUrl;
28
+ const resolveFrom = createRequire(fileURLToPath(base));
29
+ return import(resolveFrom.resolve(specifier));
30
+ }
31
+ });
32
+ await mod.link(async (specifier, referencingModule)=>{
33
+ const base = referencingModule.identifier ?? baseUrl;
34
+ const resolveFrom = createRequire(fileURLToPath(base));
35
+ const target = resolveFrom.resolve(specifier);
36
+ const cached = imports.get(target);
37
+ if (cached) return cached;
38
+ const importedModule = await import(target);
39
+ const exportNames = Object.keys(importedModule);
40
+ const imported = new SyntheticModule(exportNames, ()=>{
41
+ exportNames.forEach((key)=>imported.setExport(key, importedModule[key]));
42
+ }, {
43
+ identifier: target,
44
+ context: referencingModule.context
45
+ });
46
+ imports.set(target, imported);
47
+ return imported;
48
+ });
49
+ await mod.evaluate();
50
+ const { setup, teardown, pre, run, post } = mod.namespace;
51
+ if (!run) {
52
+ throw new Error('Benchmark run function is required');
53
+ }
54
+ process.exitCode = await benchmark({
55
+ baseUrl,
10
56
  setup,
11
57
  teardown,
12
58
  pre,
@@ -17,9 +63,9 @@ export const exitCode = await benchmark({
17
63
  minCycles,
18
64
  absThreshold,
19
65
  relThreshold,
66
+ gcObserver,
20
67
  durationsSAB,
21
68
  controlSAB
22
69
  });
23
- process.exit(exitCode);
24
70
 
25
71
  //# 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 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 resolveFrom = createRequire(fileURLToPath(base));\n return import(resolveFrom.resolve(specifier));\n },\n});\n\nawait mod.link(async (specifier, referencingModule) => {\n const base = referencingModule.identifier ?? baseUrl;\n const resolveFrom = createRequire(fileURLToPath(base));\n const target = resolveFrom.resolve(specifier);\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","source","context","console","Buffer","imports","Map","mod","identifier","initializeImportMeta","meta","url","importModuleDynamically","specifier","referencingModule","base","resolveFrom","resolve","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,SAAS,CAAC;qBACK,EAAEF,UAAUb,WAAW;wBACpB,EAAEa,UAAUZ,cAAc;mBAC/B,EAAEY,UAAUX,SAAS;mBACrB,EAAEW,UAAUV,SAAS;oBACpB,EAAEU,UAAUT,UAAU;EACxC,CAAC;AAEH,MAAMY,UAAUrB,cAAc;IAAEsB;IAASC;AAAO;AAChD,MAAMC,UAAU,IAAIC;AACpB,MAAMC,MAAM,IAAI5B,iBAAiBsB,QAAQ;IACvCO,YAAYvB;IACZiB;IACAO,sBAAqBC,IAAI;QACvBA,KAAKC,GAAG,GAAG1B;IACb;IACA2B,yBAAwBC,SAAS,EAAEC,iBAAiB;QAClD,MAAMC,OAAOD,kBAAkBN,UAAU,IAAIvB;QAC7C,MAAM+B,cAAclC,cAAcC,cAAcgC;QAChD,OAAO,MAAM,CAACC,YAAYC,OAAO,CAACJ;IACpC;AACF;AAEA,MAAMN,IAAIW,IAAI,CAAC,OAAOL,WAAWC;IAC/B,MAAMC,OAAOD,kBAAkBN,UAAU,IAAIvB;IAC7C,MAAM+B,cAAclC,cAAcC,cAAcgC;IAChD,MAAMI,SAASH,YAAYC,OAAO,CAACJ;IACnC,MAAMO,SAASf,QAAQgB,GAAG,CAACF;IAC3B,IAAIC,QAAQ,OAAOA;IAEnB,MAAME,iBAAiB,MAAM,MAAM,CAACH;IACpC,MAAMI,cAAcC,OAAOC,IAAI,CAACH;IAChC,MAAMI,WAAW,IAAI9C,gBACnB2C,aACA;QACEA,YAAYI,OAAO,CAAC,CAACC,MAAQF,SAASG,SAAS,CAACD,KAAKN,cAAc,CAACM,IAAI;IAC1E,GACA;QAAEpB,YAAYW;QAAQjB,SAASY,kBAAkBZ,OAAO;IAAC;IAE3DG,QAAQyB,GAAG,CAACX,QAAQO;IACpB,OAAOA;AACT;AAEA,MAAMnB,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,MAAMxD,UAAU;IACjCC;IACA+C;IACAC;IACAC;IACAC;IACAC;IACA7C;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.0",
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": "^24.10.1",
49
49
  "husky": "^9.1.7",
50
- "inop": "^0.7.9",
51
- "jest": "^30.0.5",
50
+ "inop": "^0.8.0",
51
+ "jest": "^30.2.0",
52
+ "overtake": "^1.0.5",
52
53
  "prettier": "^3.6.2",
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.2",
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,29 +13,11 @@ 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']))
@@ -43,12 +26,21 @@ commander
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
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { cpus } from 'node:os';
2
+ import { pathToFileURL } from 'node:url';
2
3
  import { createExecutor, ExecutorOptions, ExecutorReport } from './executor.js';
3
4
  import { MaybePromise, StepFn, SetupFn, TeardownFn, FeedFn, ReportType, ReportTypeList, DEFAULT_CYCLES } from './types.js';
4
5
 
@@ -141,20 +142,29 @@ export class Benchmark<TInput> {
141
142
  minCycles = 50,
142
143
  absThreshold = 1_000,
143
144
  relThreshold = 0.02,
145
+ gcObserver = true,
144
146
  reportTypes = DEFAULT_REPORT_TYPES as unknown as R,
147
+ baseUrl,
145
148
  }: ExecutorOptions<R>): Promise<TargetReport<R>[]> {
146
149
  if (this.#executed) {
147
150
  throw new Error("Benchmark is executed and can't be reused");
148
151
  }
149
152
  this.#executed = true;
150
153
 
154
+ const resolvedBaseUrl = baseUrl ?? pathToFileURL(process.cwd()).href;
155
+ if (!baseUrl) {
156
+ console.warn("Overtake: baseUrl not provided; defaulting to process.cwd(). Pass the benchmark's import.meta.url so relative imports resolve correctly.");
157
+ }
158
+
151
159
  const executor = createExecutor<unknown, TInput, R>({
160
+ baseUrl: resolvedBaseUrl,
152
161
  workers,
153
162
  warmupCycles,
154
163
  maxCycles,
155
164
  minCycles,
156
165
  absThreshold,
157
166
  relThreshold,
167
+ gcObserver,
158
168
  reportTypes,
159
169
  });
160
170
 
@@ -167,6 +177,7 @@ export class Benchmark<TInput> {
167
177
  const data = await feed.fn?.();
168
178
  executor
169
179
  .push<ExecutorReport<R>>({
180
+ baseUrl: resolvedBaseUrl,
170
181
  setup: target.setup,
171
182
  teardown: target.teardown,
172
183
  pre: measure.pre,