overtake 1.3.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -15
- package/bin/overtake.js +1 -1
- package/build/executor.d.ts +8 -3
- package/build/index.d.ts +10 -11
- package/build/reporter.d.ts +10 -2
- package/build/runner.d.ts +1 -1
- package/build/types.d.ts +7 -7
- package/build/utils.d.ts +3 -17
- package/package.json +8 -26
- package/src/__tests__/assert-no-closure.ts +135 -0
- package/src/__tests__/benchmark-execute.ts +48 -0
- package/src/cli.ts +139 -144
- package/src/executor.ts +59 -24
- package/src/index.ts +135 -68
- package/src/reporter.ts +77 -125
- package/src/runner.ts +28 -25
- package/src/types.ts +9 -9
- package/src/utils.ts +62 -46
- package/src/worker.ts +13 -12
- package/tsconfig.json +3 -1
- package/build/cli.cjs +0 -179
- package/build/cli.cjs.map +0 -1
- package/build/cli.js +0 -134
- package/build/cli.js.map +0 -1
- package/build/executor.cjs +0 -116
- package/build/executor.cjs.map +0 -1
- package/build/executor.js +0 -106
- package/build/executor.js.map +0 -1
- package/build/gc-watcher.cjs +0 -30
- package/build/gc-watcher.cjs.map +0 -1
- package/build/gc-watcher.js +0 -20
- package/build/gc-watcher.js.map +0 -1
- package/build/index.cjs +0 -400
- package/build/index.cjs.map +0 -1
- package/build/index.js +0 -335
- package/build/index.js.map +0 -1
- package/build/reporter.cjs +0 -364
- package/build/reporter.cjs.map +0 -1
- package/build/reporter.js +0 -346
- package/build/reporter.js.map +0 -1
- package/build/runner.cjs +0 -528
- package/build/runner.cjs.map +0 -1
- package/build/runner.js +0 -518
- package/build/runner.js.map +0 -1
- package/build/types.cjs +0 -66
- package/build/types.cjs.map +0 -1
- package/build/types.js +0 -33
- package/build/types.js.map +0 -1
- package/build/utils.cjs +0 -121
- package/build/utils.cjs.map +0 -1
- package/build/utils.js +0 -85
- package/build/utils.js.map +0 -1
- package/build/worker.cjs +0 -158
- package/build/worker.cjs.map +0 -1
- package/build/worker.js +0 -113
- package/build/worker.js.map +0 -1
package/src/cli.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { createRequire,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { stat, readFile, writeFile } from 'node:fs/promises';
|
|
5
|
-
import { Command, Option } from 'commander';
|
|
6
|
-
import { glob } from 'glob';
|
|
1
|
+
import { createRequire, register } from 'node:module';
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { stat, readFile, writeFile, glob } from 'node:fs/promises';
|
|
7
5
|
import {
|
|
8
6
|
Benchmark,
|
|
9
7
|
printTableReports,
|
|
@@ -13,152 +11,149 @@ import {
|
|
|
13
11
|
printHistogramReports,
|
|
14
12
|
printComparisonReports,
|
|
15
13
|
reportsToBaseline,
|
|
16
|
-
BaselineData,
|
|
14
|
+
type BaselineData,
|
|
17
15
|
DEFAULT_REPORT_TYPES,
|
|
18
16
|
DEFAULT_WORKERS,
|
|
19
|
-
} from './index.
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
17
|
+
} from './index.ts';
|
|
18
|
+
import { REPORT_TYPES } from './types.ts';
|
|
19
|
+
import { resolveHookUrl } from './utils.ts';
|
|
20
|
+
|
|
21
|
+
register(resolveHookUrl);
|
|
22
22
|
|
|
23
23
|
const require = createRequire(import.meta.url);
|
|
24
|
-
const { name,
|
|
24
|
+
const { name, version, description } = require('../package.json');
|
|
25
25
|
const BENCHMARK_URL = Symbol.for('overtake.benchmarkUrl');
|
|
26
26
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
27
|
+
const FORMATS = ['simple', 'json', 'pjson', 'table', 'markdown', 'histogram'] as const;
|
|
28
|
+
|
|
29
|
+
const { values: opts, positionals: patterns } = parseArgs({
|
|
30
|
+
args: process.argv.slice(2),
|
|
31
|
+
allowPositionals: true,
|
|
32
|
+
options: {
|
|
33
|
+
'report-types': { type: 'string', short: 'r', multiple: true },
|
|
34
|
+
workers: { type: 'string', short: 'w' },
|
|
35
|
+
format: { type: 'string', short: 'f' },
|
|
36
|
+
'abs-threshold': { type: 'string' },
|
|
37
|
+
'rel-threshold': { type: 'string' },
|
|
38
|
+
'warmup-cycles': { type: 'string' },
|
|
39
|
+
'max-cycles': { type: 'string' },
|
|
40
|
+
'min-cycles': { type: 'string' },
|
|
41
|
+
'no-gc-observer': { type: 'boolean' },
|
|
42
|
+
progress: { type: 'boolean' },
|
|
43
|
+
'save-baseline': { type: 'string' },
|
|
44
|
+
'compare-baseline': { type: 'string' },
|
|
45
|
+
help: { type: 'boolean', short: 'h' },
|
|
46
|
+
version: { type: 'boolean', short: 'v' },
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (opts.version) {
|
|
51
|
+
console.log(version);
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (opts.help || patterns.length === 0) {
|
|
56
|
+
console.log(`${name} v${version} - ${description}
|
|
57
|
+
|
|
58
|
+
Usage: overtake [options] <paths...>
|
|
59
|
+
|
|
60
|
+
Options:
|
|
61
|
+
-r, --report-types <type> statistic type, repeat for multiple (-r ops -r p99)
|
|
62
|
+
-w, --workers <n> number of concurrent workers (default: ${DEFAULT_WORKERS})
|
|
63
|
+
-f, --format <format> output format: ${FORMATS.join(', ')} (default: simple)
|
|
64
|
+
--abs-threshold <ns> absolute error threshold in nanoseconds
|
|
65
|
+
--rel-threshold <frac> relative error threshold (0-1)
|
|
66
|
+
--warmup-cycles <n> warmup cycles before measuring
|
|
67
|
+
--max-cycles <n> maximum measurement cycles per feed
|
|
68
|
+
--min-cycles <n> minimum measurement cycles per feed
|
|
69
|
+
--no-gc-observer disable GC overlap detection
|
|
70
|
+
--progress show progress bar
|
|
71
|
+
--save-baseline <file> save results to baseline file
|
|
72
|
+
--compare-baseline <file> compare results against baseline file
|
|
73
|
+
-v, --version show version
|
|
74
|
+
-h, --help show this help`);
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const reportTypes = opts['report-types']?.length
|
|
79
|
+
? opts['report-types'].filter((t): t is (typeof REPORT_TYPES)[number] => REPORT_TYPES.includes(t as (typeof REPORT_TYPES)[number]))
|
|
80
|
+
: DEFAULT_REPORT_TYPES;
|
|
81
|
+
const format = opts.format && FORMATS.includes(opts.format as (typeof FORMATS)[number]) ? opts.format : 'simple';
|
|
82
|
+
|
|
83
|
+
const executeOptions = {
|
|
84
|
+
reportTypes,
|
|
85
|
+
workers: opts.workers ? parseInt(opts.workers) : DEFAULT_WORKERS,
|
|
86
|
+
absThreshold: opts['abs-threshold'] ? parseFloat(opts['abs-threshold']) : undefined,
|
|
87
|
+
relThreshold: opts['rel-threshold'] ? parseFloat(opts['rel-threshold']) : undefined,
|
|
88
|
+
warmupCycles: opts['warmup-cycles'] ? parseInt(opts['warmup-cycles']) : undefined,
|
|
89
|
+
maxCycles: opts['max-cycles'] ? parseInt(opts['max-cycles']) : undefined,
|
|
90
|
+
minCycles: opts['min-cycles'] ? parseInt(opts['min-cycles']) : undefined,
|
|
91
|
+
gcObserver: !opts['no-gc-observer'],
|
|
92
|
+
progress: opts.progress ?? false,
|
|
93
|
+
format,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
let baseline: BaselineData | null = null;
|
|
97
|
+
if (opts['compare-baseline']) {
|
|
98
|
+
try {
|
|
99
|
+
const content = await readFile(opts['compare-baseline'], 'utf8');
|
|
100
|
+
baseline = JSON.parse(content) as BaselineData;
|
|
101
|
+
} catch {
|
|
102
|
+
console.error(`Warning: Could not load baseline file: ${opts['compare-baseline']}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const files = new Set((await Promise.all(patterns.map((pattern) => Array.fromAsync(glob(pattern, { cwd: process.cwd() })).catch(() => [] as string[])))).flat());
|
|
107
|
+
|
|
108
|
+
for (const file of files) {
|
|
109
|
+
const stats = await stat(file).catch(() => false as const);
|
|
110
|
+
if (stats && stats.isFile()) {
|
|
111
|
+
const identifier = pathToFileURL(file).href;
|
|
112
|
+
let instance: Benchmark<unknown> | undefined;
|
|
113
|
+
(globalThis as any).benchmark = (...args: Parameters<(typeof Benchmark)['create']>) => {
|
|
114
|
+
if (instance) {
|
|
115
|
+
throw new Error('Only one benchmark per file is supported');
|
|
54
116
|
}
|
|
55
|
-
|
|
117
|
+
instance = Benchmark.create(...args);
|
|
118
|
+
return instance;
|
|
119
|
+
};
|
|
120
|
+
await import(identifier);
|
|
56
121
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
async importModuleDynamically(specifier, referencingModule) {
|
|
91
|
-
if (Module.isBuiltin(specifier)) {
|
|
92
|
-
return import(specifier);
|
|
93
|
-
}
|
|
94
|
-
const baseIdentifier = referencingModule.identifier ?? identifier;
|
|
95
|
-
const resolveFrom = createRequire(fileURLToPath(baseIdentifier));
|
|
96
|
-
const resolved = resolveFrom.resolve(specifier);
|
|
97
|
-
return import(resolved);
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
const imports = new Map<string, SyntheticModule>();
|
|
101
|
-
await script.link(async (specifier: string, referencingModule) => {
|
|
102
|
-
const baseIdentifier = referencingModule.identifier ?? identifier;
|
|
103
|
-
const resolveFrom = createRequire(fileURLToPath(baseIdentifier));
|
|
104
|
-
const target = Module.isBuiltin(specifier) ? specifier : resolveFrom.resolve(specifier);
|
|
105
|
-
const cached = imports.get(target);
|
|
106
|
-
if (cached) {
|
|
107
|
-
return cached;
|
|
108
|
-
}
|
|
109
|
-
const mod = await import(target);
|
|
110
|
-
const exportNames = Object.keys(mod);
|
|
111
|
-
const imported = new SyntheticModule(
|
|
112
|
-
exportNames,
|
|
113
|
-
() => {
|
|
114
|
-
exportNames.forEach((key) => imported.setExport(key, mod[key]));
|
|
115
|
-
},
|
|
116
|
-
{ identifier: target, context: referencingModule.context },
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
imports.set(target, imported);
|
|
120
|
-
return imported;
|
|
121
|
-
});
|
|
122
|
-
await script.evaluate();
|
|
123
|
-
|
|
124
|
-
if (instance) {
|
|
125
|
-
const reports = await instance.execute({
|
|
126
|
-
...executeOptions,
|
|
127
|
-
[BENCHMARK_URL]: identifier,
|
|
128
|
-
} as typeof executeOptions);
|
|
129
|
-
|
|
130
|
-
if (executeOptions.saveBaseline) {
|
|
131
|
-
const baselineData = reportsToBaseline(reports);
|
|
132
|
-
await writeFile(executeOptions.saveBaseline, JSON.stringify(baselineData, null, 2));
|
|
133
|
-
console.log(`Baseline saved to: ${executeOptions.saveBaseline}`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (baseline) {
|
|
137
|
-
printComparisonReports(reports, baseline);
|
|
138
|
-
} else {
|
|
139
|
-
switch (executeOptions.format) {
|
|
140
|
-
case 'json':
|
|
141
|
-
printJSONReports(reports);
|
|
142
|
-
break;
|
|
143
|
-
case 'pjson':
|
|
144
|
-
printJSONReports(reports, 2);
|
|
145
|
-
break;
|
|
146
|
-
case 'table':
|
|
147
|
-
printTableReports(reports);
|
|
148
|
-
break;
|
|
149
|
-
case 'markdown':
|
|
150
|
-
printMarkdownReports(reports);
|
|
151
|
-
break;
|
|
152
|
-
case 'histogram':
|
|
153
|
-
printHistogramReports(reports);
|
|
154
|
-
break;
|
|
155
|
-
default:
|
|
156
|
-
printSimpleReports(reports);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
122
|
+
if (instance) {
|
|
123
|
+
const reports = await instance.execute({
|
|
124
|
+
...executeOptions,
|
|
125
|
+
[BENCHMARK_URL]: identifier,
|
|
126
|
+
} as typeof executeOptions);
|
|
127
|
+
|
|
128
|
+
if (opts['save-baseline']) {
|
|
129
|
+
const baselineData = reportsToBaseline(reports);
|
|
130
|
+
await writeFile(opts['save-baseline'], JSON.stringify(baselineData, null, 2));
|
|
131
|
+
console.log(`Baseline saved to: ${opts['save-baseline']}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (baseline) {
|
|
135
|
+
printComparisonReports(reports, baseline);
|
|
136
|
+
} else {
|
|
137
|
+
switch (format) {
|
|
138
|
+
case 'json':
|
|
139
|
+
printJSONReports(reports);
|
|
140
|
+
break;
|
|
141
|
+
case 'pjson':
|
|
142
|
+
printJSONReports(reports, 2);
|
|
143
|
+
break;
|
|
144
|
+
case 'table':
|
|
145
|
+
printTableReports(reports);
|
|
146
|
+
break;
|
|
147
|
+
case 'markdown':
|
|
148
|
+
printMarkdownReports(reports);
|
|
149
|
+
break;
|
|
150
|
+
case 'histogram':
|
|
151
|
+
printHistogramReports(reports);
|
|
152
|
+
break;
|
|
153
|
+
default:
|
|
154
|
+
printSimpleReports(reports);
|
|
159
155
|
}
|
|
160
156
|
}
|
|
161
157
|
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
commander.parse(process.argv);
|
|
158
|
+
}
|
|
159
|
+
}
|
package/src/executor.ts
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import { Worker } from 'node:worker_threads';
|
|
2
2
|
import { once } from 'node:events';
|
|
3
|
-
import { queue } from 'async';
|
|
4
3
|
import { pathToFileURL } from 'node:url';
|
|
5
|
-
import { createReport, Report } from './reporter.
|
|
6
|
-
import { cmp } from './utils.
|
|
4
|
+
import { createReport, computeStats, Report } from './reporter.ts';
|
|
5
|
+
import { cmp, assertNoClosure } from './utils.ts';
|
|
7
6
|
import {
|
|
8
|
-
ExecutorRunOptions,
|
|
9
|
-
ReportOptions,
|
|
10
|
-
WorkerOptions,
|
|
11
|
-
BenchmarkOptions,
|
|
7
|
+
type ExecutorRunOptions,
|
|
8
|
+
type ReportOptions,
|
|
9
|
+
type WorkerOptions,
|
|
10
|
+
type BenchmarkOptions,
|
|
12
11
|
Control,
|
|
13
|
-
ReportType,
|
|
14
|
-
ReportTypeList,
|
|
12
|
+
type ReportType,
|
|
13
|
+
type ReportTypeList,
|
|
15
14
|
CONTROL_SLOTS,
|
|
16
15
|
COMPLETE_VALUE,
|
|
17
|
-
ProgressCallback,
|
|
18
|
-
} from './types.
|
|
16
|
+
type ProgressCallback,
|
|
17
|
+
} from './types.ts';
|
|
19
18
|
|
|
20
19
|
export type ExecutorReport<R extends ReportTypeList> = Record<R[number], Report> & {
|
|
21
20
|
count: number;
|
|
22
21
|
heapUsedKB: number;
|
|
23
22
|
dceWarning: boolean;
|
|
23
|
+
error?: string;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
export interface ExecutorOptions<R extends ReportTypeList> extends BenchmarkOptions, ReportOptions<R> {
|
|
@@ -32,22 +32,58 @@ export interface ExecutorOptions<R extends ReportTypeList> extends BenchmarkOpti
|
|
|
32
32
|
|
|
33
33
|
const BENCHMARK_URL = Symbol.for('overtake.benchmarkUrl');
|
|
34
34
|
|
|
35
|
-
export
|
|
35
|
+
export interface Executor<TContext, TInput> {
|
|
36
|
+
pushAsync<T>(task: ExecutorRunOptions<TContext, TInput>): Promise<T>;
|
|
37
|
+
kill(): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const createExecutor = <TContext, TInput, R extends ReportTypeList>(options: Required<ExecutorOptions<R>>): Executor<TContext, TInput> => {
|
|
36
41
|
const { workers, warmupCycles, maxCycles, minCycles, absThreshold, relThreshold, gcObserver = true, reportTypes, onProgress, progressInterval = 100 } = options;
|
|
37
42
|
const benchmarkUrl = (options as Record<symbol, unknown>)[BENCHMARK_URL];
|
|
38
43
|
const resolvedBenchmarkUrl = typeof benchmarkUrl === 'string' ? benchmarkUrl : pathToFileURL(process.cwd()).href;
|
|
39
44
|
|
|
40
|
-
const
|
|
45
|
+
const pending: { task: ExecutorRunOptions<TContext, TInput>; resolve: (v: unknown) => void; reject: (e: unknown) => void }[] = [];
|
|
46
|
+
let running = 0;
|
|
47
|
+
|
|
48
|
+
const schedule = async (task: ExecutorRunOptions<TContext, TInput>) => {
|
|
49
|
+
running++;
|
|
50
|
+
try {
|
|
51
|
+
return await runTask(task);
|
|
52
|
+
} finally {
|
|
53
|
+
running--;
|
|
54
|
+
if (pending.length > 0) {
|
|
55
|
+
const next = pending.shift()!;
|
|
56
|
+
schedule(next.task).then(next.resolve, next.reject);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const pushAsync = <T>(task: ExecutorRunOptions<TContext, TInput>): Promise<T> => {
|
|
62
|
+
if (running < workers) {
|
|
63
|
+
return schedule(task) as Promise<T>;
|
|
64
|
+
}
|
|
65
|
+
return new Promise<T>((resolve, reject) => {
|
|
66
|
+
pending.push({ task, resolve: resolve as (v: unknown) => void, reject });
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const runTask = async ({ id, setup, teardown, pre, run, post, data }: ExecutorRunOptions<TContext, TInput>) => {
|
|
41
71
|
const setupCode = setup?.toString();
|
|
42
72
|
const teardownCode = teardown?.toString();
|
|
43
73
|
const preCode = pre?.toString();
|
|
44
|
-
const runCode = run.toString()
|
|
74
|
+
const runCode = run.toString();
|
|
45
75
|
const postCode = post?.toString();
|
|
46
76
|
|
|
77
|
+
if (setupCode) assertNoClosure(setupCode, 'setup');
|
|
78
|
+
if (teardownCode) assertNoClosure(teardownCode, 'teardown');
|
|
79
|
+
if (preCode) assertNoClosure(preCode, 'pre');
|
|
80
|
+
assertNoClosure(runCode, 'run');
|
|
81
|
+
if (postCode) assertNoClosure(postCode, 'post');
|
|
82
|
+
|
|
47
83
|
const controlSAB = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * CONTROL_SLOTS);
|
|
48
84
|
const durationsSAB = new SharedArrayBuffer(BigUint64Array.BYTES_PER_ELEMENT * maxCycles);
|
|
49
85
|
|
|
50
|
-
const workerFile = new URL('./worker.
|
|
86
|
+
const workerFile = new URL('./worker.ts', import.meta.url);
|
|
51
87
|
const workerData: WorkerOptions = {
|
|
52
88
|
benchmarkUrl: resolvedBenchmarkUrl,
|
|
53
89
|
setupCode,
|
|
@@ -83,17 +119,18 @@ export const createExecutor = <TContext, TInput, R extends ReportTypeList>(optio
|
|
|
83
119
|
const WORKER_TIMEOUT_MS = 300_000;
|
|
84
120
|
const exitPromise = once(worker, 'exit');
|
|
85
121
|
const timeoutId = setTimeout(() => worker.terminate(), WORKER_TIMEOUT_MS);
|
|
122
|
+
let workerError: string | undefined;
|
|
86
123
|
try {
|
|
87
124
|
const [exitCode] = await exitPromise;
|
|
88
125
|
clearTimeout(timeoutId);
|
|
89
126
|
if (progressIntervalId) clearInterval(progressIntervalId);
|
|
90
127
|
if (exitCode !== 0) {
|
|
91
|
-
|
|
128
|
+
workerError = `worker exited with code ${exitCode}`;
|
|
92
129
|
}
|
|
93
130
|
} catch (err) {
|
|
94
131
|
clearTimeout(timeoutId);
|
|
95
132
|
if (progressIntervalId) clearInterval(progressIntervalId);
|
|
96
|
-
|
|
133
|
+
workerError = err instanceof Error ? err.message : String(err);
|
|
97
134
|
}
|
|
98
135
|
|
|
99
136
|
const count = control[Control.INDEX];
|
|
@@ -112,19 +149,17 @@ export const createExecutor = <TContext, TInput, R extends ReportTypeList>(optio
|
|
|
112
149
|
}
|
|
113
150
|
}
|
|
114
151
|
|
|
152
|
+
const stats = count > 0 ? computeStats(durations) : undefined;
|
|
115
153
|
const report = reportTypes
|
|
116
|
-
.map<[string, unknown]>((type) => [type, createReport(durations, type)] as [ReportType, Report])
|
|
154
|
+
.map<[string, unknown]>((type) => [type, createReport(durations, type, stats)] as [ReportType, Report])
|
|
117
155
|
.concat([
|
|
118
156
|
['count', count],
|
|
119
157
|
['heapUsedKB', heapUsedKB],
|
|
120
158
|
['dceWarning', dceWarning],
|
|
159
|
+
['error', workerError],
|
|
121
160
|
]);
|
|
122
161
|
return Object.fromEntries(report);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
executor.error((err) => {
|
|
126
|
-
console.error(err);
|
|
127
|
-
});
|
|
162
|
+
};
|
|
128
163
|
|
|
129
|
-
return
|
|
164
|
+
return { pushAsync, kill() {} };
|
|
130
165
|
};
|