overtake 0.1.1 → 1.0.0-rc.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 +72 -79
- package/bin/overtake.js +2 -0
- package/build/__tests__/runner.d.ts +1 -0
- package/build/benchmark.cjs +237 -0
- package/build/benchmark.cjs.map +1 -0
- package/build/benchmark.d.ts +64 -0
- package/build/benchmark.js +189 -0
- package/build/benchmark.js.map +1 -0
- package/build/cli.cjs +149 -0
- package/build/cli.cjs.map +1 -0
- package/build/cli.d.ts +1 -0
- package/build/cli.js +104 -0
- package/build/cli.js.map +1 -0
- package/build/executor.cjs +68 -0
- package/build/executor.cjs.map +1 -0
- package/build/executor.d.ts +10 -0
- package/build/executor.js +58 -0
- package/build/executor.js.map +1 -0
- package/build/index.cjs +20 -0
- package/build/index.cjs.map +1 -0
- package/build/index.d.ts +5 -0
- package/build/index.js +3 -0
- package/build/index.js.map +1 -0
- package/build/queue.cjs +48 -0
- package/build/queue.cjs.map +1 -0
- package/build/queue.d.ts +3 -0
- package/build/queue.js +38 -0
- package/build/queue.js.map +1 -0
- package/build/reporter.cjs +175 -0
- package/build/reporter.cjs.map +1 -0
- package/build/reporter.d.ts +11 -0
- package/build/reporter.js +157 -0
- package/build/reporter.js.map +1 -0
- package/build/runner.cjs +92 -0
- package/build/runner.cjs.map +1 -0
- package/build/runner.d.ts +2 -0
- package/build/runner.js +82 -0
- package/build/runner.js.map +1 -0
- package/build/types.cjs +48 -0
- package/build/types.cjs.map +1 -0
- package/build/types.d.ts +59 -0
- package/build/types.js +21 -0
- package/build/types.js.map +1 -0
- package/build/utils.cjs +100 -0
- package/build/utils.cjs.map +1 -0
- package/build/utils.d.ts +20 -0
- package/build/utils.js +67 -0
- package/build/utils.js.map +1 -0
- package/build/worker.cjs +29 -0
- package/build/worker.cjs.map +1 -0
- package/build/worker.d.ts +1 -0
- package/build/worker.js +25 -0
- package/build/worker.js.map +1 -0
- package/package.json +25 -16
- package/src/__tests__/runner.ts +34 -0
- package/src/benchmark.ts +231 -0
- package/src/cli.ts +114 -0
- package/src/executor.ts +73 -0
- package/src/index.ts +6 -0
- package/src/queue.ts +42 -0
- package/src/reporter.ts +139 -0
- package/src/runner.ts +111 -0
- package/src/types.ts +72 -0
- package/src/utils.ts +65 -0
- package/src/worker.ts +46 -0
- package/tsconfig.json +17 -0
- package/cli.js +0 -70
- package/index.d.ts +0 -56
- package/index.js +0 -302
- package/runner.js +0 -3
package/src/reporter.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { div, max, divs } from './utils.js';
|
|
2
|
+
import { ReportType } from './types.js';
|
|
3
|
+
|
|
4
|
+
const units = [
|
|
5
|
+
{ unit: 'ns', factor: 1 },
|
|
6
|
+
{ unit: 'µs', factor: 1e3 },
|
|
7
|
+
{ unit: 'ms', factor: 1e6 },
|
|
8
|
+
{ unit: 's', factor: 1e9 },
|
|
9
|
+
{ unit: 'm', factor: 60 * 1e9 },
|
|
10
|
+
{ unit: 'h', factor: 3600 * 1e9 },
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
function smartFixed(n: number): string {
|
|
14
|
+
return n.toLocaleString(undefined, {
|
|
15
|
+
minimumFractionDigits: 0,
|
|
16
|
+
maximumFractionDigits: 2,
|
|
17
|
+
useGrouping: false,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export class Report {
|
|
21
|
+
constructor(
|
|
22
|
+
public readonly type: ReportType,
|
|
23
|
+
public readonly value: bigint,
|
|
24
|
+
public readonly uncertainty: number = 0,
|
|
25
|
+
public readonly scale: bigint = 1n,
|
|
26
|
+
) {}
|
|
27
|
+
valueOf() {
|
|
28
|
+
return Number(div(this.value, this.scale));
|
|
29
|
+
}
|
|
30
|
+
toString() {
|
|
31
|
+
const uncertainty = this.uncertainty ? ` ± ${smartFixed(this.uncertainty)}%` : '';
|
|
32
|
+
|
|
33
|
+
const value = this.valueOf();
|
|
34
|
+
if (this.type === 'ops') {
|
|
35
|
+
return `${smartFixed(value)} ops/s${uncertainty}`;
|
|
36
|
+
}
|
|
37
|
+
let display = value;
|
|
38
|
+
let unit = 'ns';
|
|
39
|
+
|
|
40
|
+
for (const { unit: u, factor } of units) {
|
|
41
|
+
const candidate = value / factor;
|
|
42
|
+
if (candidate < 1000) {
|
|
43
|
+
display = candidate;
|
|
44
|
+
unit = u;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return `${smartFixed(display)} ${unit}${uncertainty}`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const createReport = (durations: BigUint64Array, type: ReportType): Report => {
|
|
53
|
+
const n = durations.length;
|
|
54
|
+
if (n === 0) {
|
|
55
|
+
return new Report(type, 0n);
|
|
56
|
+
}
|
|
57
|
+
switch (type) {
|
|
58
|
+
case 'min': {
|
|
59
|
+
return new Report(type, durations[0]);
|
|
60
|
+
}
|
|
61
|
+
case 'max': {
|
|
62
|
+
return new Report(type, durations[n - 1]);
|
|
63
|
+
}
|
|
64
|
+
case 'median': {
|
|
65
|
+
const mid = Math.floor(n / 2);
|
|
66
|
+
const med = n % 2 === 0 ? (durations[mid - 1] + durations[mid]) / 2n : durations[mid];
|
|
67
|
+
return new Report(type, med);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
case 'mode': {
|
|
71
|
+
const freq = new Map<bigint, bigint>();
|
|
72
|
+
let maxCount = 0n;
|
|
73
|
+
let modeVal = durations[0];
|
|
74
|
+
for (const d of durations) {
|
|
75
|
+
const count = (freq.get(d) || 0n) + 1n;
|
|
76
|
+
freq.set(d, count);
|
|
77
|
+
if (count > maxCount) {
|
|
78
|
+
maxCount = count;
|
|
79
|
+
modeVal = d;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
let lower = modeVal;
|
|
83
|
+
let upper = modeVal;
|
|
84
|
+
const firstIdx = durations.indexOf(modeVal);
|
|
85
|
+
const lastIdx = durations.lastIndexOf(modeVal);
|
|
86
|
+
if (firstIdx > 0) lower = durations[firstIdx - 1];
|
|
87
|
+
if (lastIdx < n - 1) upper = durations[lastIdx + 1];
|
|
88
|
+
const gap = max(modeVal - lower, upper - modeVal);
|
|
89
|
+
const uncertainty = modeVal > 0 ? Number(((gap / 2n) * 100n) / modeVal) : 0;
|
|
90
|
+
return new Report(type, modeVal, uncertainty);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
case 'ops': {
|
|
94
|
+
let sum = 0n;
|
|
95
|
+
for (const duration of durations) {
|
|
96
|
+
sum += duration;
|
|
97
|
+
}
|
|
98
|
+
const avgNs = sum / BigInt(n);
|
|
99
|
+
const nsPerSec = 1_000_000_000n;
|
|
100
|
+
const raw = Number(nsPerSec) / Number(avgNs);
|
|
101
|
+
const extra = raw < 1 ? Math.ceil(-Math.log10(raw)) : 0;
|
|
102
|
+
|
|
103
|
+
const exp = raw > 100 ? 0 : 2 + extra;
|
|
104
|
+
|
|
105
|
+
const scale = 10n ** BigInt(exp);
|
|
106
|
+
|
|
107
|
+
const value = avgNs > 0n ? (nsPerSec * scale) / avgNs : 0n;
|
|
108
|
+
const deviation = durations[n - 1] - durations[0];
|
|
109
|
+
const uncertainty = avgNs > 0 ? Number(div(deviation * scale, 2n * avgNs)) : 0;
|
|
110
|
+
return new Report(type, value, uncertainty, scale);
|
|
111
|
+
}
|
|
112
|
+
case 'mean': {
|
|
113
|
+
let sum = 0n;
|
|
114
|
+
for (const duration of durations) {
|
|
115
|
+
sum += duration;
|
|
116
|
+
}
|
|
117
|
+
const value = divs(sum, BigInt(n), 1n);
|
|
118
|
+
return new Report(type, value);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
default: {
|
|
122
|
+
const p = Number(type.slice(1));
|
|
123
|
+
if (p === 0) {
|
|
124
|
+
return new Report(type, durations[0]);
|
|
125
|
+
}
|
|
126
|
+
if (p === 100) {
|
|
127
|
+
return new Report(type, durations[n - 1]);
|
|
128
|
+
}
|
|
129
|
+
const idx = Math.ceil((p / 100) * n) - 1;
|
|
130
|
+
const value = durations[Math.min(Math.max(idx, 0), n - 1)];
|
|
131
|
+
const prev = idx > 0 ? durations[idx - 1] : value;
|
|
132
|
+
const next = idx < n - 1 ? durations[idx + 1] : value;
|
|
133
|
+
const gap = max(value - prev, next - value);
|
|
134
|
+
const uncertainty = value > 0 ? Number(div(divs(gap, 2n, 100_00n), value)) / 100 : 0;
|
|
135
|
+
|
|
136
|
+
return new Report(type, value, uncertainty);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
package/src/runner.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Options, Control } from './types.js';
|
|
2
|
+
|
|
3
|
+
const COMPLETE_VALUE = 100_00;
|
|
4
|
+
|
|
5
|
+
const runSync = (run: Function) => {
|
|
6
|
+
return (...args: unknown[]) => {
|
|
7
|
+
const start = process.hrtime.bigint();
|
|
8
|
+
run(...args);
|
|
9
|
+
return process.hrtime.bigint() - start;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const runAsync = (run: Function) => {
|
|
14
|
+
return async (...args: unknown[]) => {
|
|
15
|
+
const start = process.hrtime.bigint();
|
|
16
|
+
await run(...args);
|
|
17
|
+
return process.hrtime.bigint() - start;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const benchmark = async <TContext, TInput>({
|
|
22
|
+
setup,
|
|
23
|
+
teardown,
|
|
24
|
+
pre,
|
|
25
|
+
run: runRaw,
|
|
26
|
+
post,
|
|
27
|
+
data,
|
|
28
|
+
|
|
29
|
+
warmupCycles,
|
|
30
|
+
minCycles,
|
|
31
|
+
absThreshold,
|
|
32
|
+
relThreshold,
|
|
33
|
+
|
|
34
|
+
durationsSAB,
|
|
35
|
+
controlSAB,
|
|
36
|
+
}: Required<Options<TContext, TInput>>) => {
|
|
37
|
+
const durations = new BigUint64Array(durationsSAB);
|
|
38
|
+
const control = new Int32Array(controlSAB);
|
|
39
|
+
|
|
40
|
+
control[Control.INDEX] = 0;
|
|
41
|
+
control[Control.PROGRESS] = 0;
|
|
42
|
+
control[Control.COMPLETE] = 255;
|
|
43
|
+
|
|
44
|
+
const context = (await setup?.()) as TContext;
|
|
45
|
+
const maxCycles = durations.length;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
await pre?.(context, data!);
|
|
49
|
+
const result = runRaw(context, data!);
|
|
50
|
+
await post?.(context, data!);
|
|
51
|
+
const run = result instanceof Promise ? runAsync(runRaw) : runSync(runRaw);
|
|
52
|
+
const start = Date.now();
|
|
53
|
+
while (Date.now() - start < 1_000) {
|
|
54
|
+
Math.sqrt(Math.random());
|
|
55
|
+
}
|
|
56
|
+
for (let i = 0; i < warmupCycles; i++) {
|
|
57
|
+
await pre?.(context, data!);
|
|
58
|
+
await run(context, data);
|
|
59
|
+
await post?.(context, data!);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let i = 0;
|
|
63
|
+
let mean = 0n;
|
|
64
|
+
let m2 = 0n;
|
|
65
|
+
|
|
66
|
+
while (true) {
|
|
67
|
+
if (i >= maxCycles) break;
|
|
68
|
+
|
|
69
|
+
await pre?.(context, data!);
|
|
70
|
+
const duration = await run(context, data);
|
|
71
|
+
await post?.(context, data!);
|
|
72
|
+
|
|
73
|
+
durations[i++] = duration;
|
|
74
|
+
const delta = duration - mean;
|
|
75
|
+
mean += delta / BigInt(i);
|
|
76
|
+
m2 += delta * (duration - mean);
|
|
77
|
+
|
|
78
|
+
const progress = Math.max(i / maxCycles) * COMPLETE_VALUE;
|
|
79
|
+
control[Control.PROGRESS] = progress;
|
|
80
|
+
|
|
81
|
+
if (i >= minCycles) {
|
|
82
|
+
const variance = Number(m2) / (i - 1);
|
|
83
|
+
const stddev = Math.sqrt(variance);
|
|
84
|
+
if (stddev <= Number(absThreshold)) {
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const meanNum = Number(mean);
|
|
89
|
+
const cov = stddev / (meanNum || 1);
|
|
90
|
+
if (cov <= relThreshold) {
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
control[Control.INDEX] = i;
|
|
97
|
+
control[Control.COMPLETE] = 0;
|
|
98
|
+
} catch (e) {
|
|
99
|
+
console.error(e && typeof e === 'object' && 'stack' in e ? e.stack : e);
|
|
100
|
+
control[Control.COMPLETE] = 1;
|
|
101
|
+
} finally {
|
|
102
|
+
try {
|
|
103
|
+
await teardown?.(context);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
control[Control.COMPLETE] = 2;
|
|
106
|
+
console.error(e && typeof e === 'object' && 'stack' in e ? e.stack : e);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return control[Control.COMPLETE];
|
|
111
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export type MaybePromise<T> = Promise<T> | PromiseLike<T> | T;
|
|
2
|
+
|
|
3
|
+
export interface SetupFn<TContext> {
|
|
4
|
+
(): MaybePromise<TContext>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface TeardownFn<TContext> {
|
|
8
|
+
(ctx: TContext): MaybePromise<void>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface StepFn<TContext, TInput> {
|
|
12
|
+
(ctx: TContext, input: TInput): MaybePromise<void>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface FeedFn<TInput> {
|
|
16
|
+
(): MaybePromise<TInput>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type _Sequence<To extends number, R extends unknown[]> = R['length'] extends To ? R[number] : _Sequence<To, [R['length'], ...R]>;
|
|
20
|
+
export type Sequence<To extends number> = number extends To ? number : _Sequence<To, []>;
|
|
21
|
+
export type Between<From extends number, To extends number> = Exclude<Sequence<To>, Sequence<From>>;
|
|
22
|
+
|
|
23
|
+
export type ReportType = 'ops' | 'min' | 'max' | 'mean' | 'median' | 'mode' | `p${Between<1, 100>}`;
|
|
24
|
+
export type ReportTypeList = readonly ReportType[];
|
|
25
|
+
export const REPORT_TYPES: ReportTypeList = Array.from({ length: 99 }, (_, idx) => `p${idx + 1}` as ReportType).concat(['ops', 'mean', 'min', 'max', 'median', 'mode']);
|
|
26
|
+
|
|
27
|
+
export interface ReportOptions<R extends ReportTypeList> {
|
|
28
|
+
reportTypes: R;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface BenchmarkOptions {
|
|
32
|
+
warmupCycles?: number;
|
|
33
|
+
minCycles?: number;
|
|
34
|
+
absThreshold?: number; // ns
|
|
35
|
+
relThreshold?: number; // %
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface RunOptions<TContext, TInput> {
|
|
39
|
+
setup?: SetupFn<TContext>;
|
|
40
|
+
teardown?: TeardownFn<TContext>;
|
|
41
|
+
pre?: StepFn<TContext, TInput>;
|
|
42
|
+
run: StepFn<TContext, TInput>;
|
|
43
|
+
post?: StepFn<TContext, TInput>;
|
|
44
|
+
data?: TInput;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface WorkerOptions extends Required<BenchmarkOptions> {
|
|
48
|
+
setupCode?: string;
|
|
49
|
+
teardownCode?: string;
|
|
50
|
+
preCode?: string;
|
|
51
|
+
runCode: string;
|
|
52
|
+
postCode?: string;
|
|
53
|
+
data?: unknown;
|
|
54
|
+
|
|
55
|
+
durationsSAB: SharedArrayBuffer;
|
|
56
|
+
controlSAB: SharedArrayBuffer;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface Options<TContext, TInput> extends RunOptions<TContext, TInput>, BenchmarkOptions {
|
|
60
|
+
durationsSAB: SharedArrayBuffer;
|
|
61
|
+
controlSAB: SharedArrayBuffer;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export enum Control {
|
|
65
|
+
INDEX,
|
|
66
|
+
PROGRESS,
|
|
67
|
+
COMPLETE,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const CONTROL_SLOTS = Object.values(Control).length / 2;
|
|
71
|
+
export const DEFAULT_CYCLES = 1_000;
|
|
72
|
+
export const Z95 = 1.96;
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export const abs = (value: bigint) => {
|
|
2
|
+
if (value < 0n) {
|
|
3
|
+
return -value;
|
|
4
|
+
}
|
|
5
|
+
return value;
|
|
6
|
+
};
|
|
7
|
+
export const cmp = (a: bigint | number, b: bigint | number): number => {
|
|
8
|
+
if (a > b) {
|
|
9
|
+
return 1;
|
|
10
|
+
}
|
|
11
|
+
if (a < b) {
|
|
12
|
+
return -1;
|
|
13
|
+
}
|
|
14
|
+
return 0;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const max = (a: bigint, b: bigint) => {
|
|
18
|
+
if (a > b) {
|
|
19
|
+
return a;
|
|
20
|
+
}
|
|
21
|
+
return b;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const divMod = (a: bigint, b: bigint) => {
|
|
25
|
+
return { quotient: a / b, remainder: a % b };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function div(a: bigint, b: bigint, decimals: number = 2): string {
|
|
29
|
+
if (b === 0n) throw new RangeError('Division by zero');
|
|
30
|
+
const scale = 10n ** BigInt(decimals);
|
|
31
|
+
const scaled = (a * scale) / b;
|
|
32
|
+
const intPart = scaled / scale;
|
|
33
|
+
const fracPart = scaled % scale;
|
|
34
|
+
return `${intPart}.${fracPart.toString().padStart(decimals, '0')}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function divs(a: bigint, b: bigint, scale: bigint): bigint {
|
|
38
|
+
if (b === 0n) throw new RangeError('Division by zero');
|
|
39
|
+
return (a * scale) / b;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class ScaledBigInt {
|
|
43
|
+
constructor(
|
|
44
|
+
public value: bigint,
|
|
45
|
+
public scale: bigint,
|
|
46
|
+
) {}
|
|
47
|
+
add(value: bigint) {
|
|
48
|
+
this.value += value * this.scale;
|
|
49
|
+
}
|
|
50
|
+
sub(value: bigint) {
|
|
51
|
+
this.value -= value * this.scale;
|
|
52
|
+
}
|
|
53
|
+
div(value: bigint) {
|
|
54
|
+
this.value /= value;
|
|
55
|
+
}
|
|
56
|
+
mul(value: bigint) {
|
|
57
|
+
this.value *= value;
|
|
58
|
+
}
|
|
59
|
+
unscale() {
|
|
60
|
+
return this.value / this.value;
|
|
61
|
+
}
|
|
62
|
+
number() {
|
|
63
|
+
return Number(div(this.value, this.scale));
|
|
64
|
+
}
|
|
65
|
+
}
|
package/src/worker.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { workerData } from 'node:worker_threads';
|
|
2
|
+
import { benchmark } from './runner.js';
|
|
3
|
+
import { SetupFn, TeardownFn, StepFn, WorkerOptions } from './types.js';
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
setupCode,
|
|
7
|
+
teardownCode,
|
|
8
|
+
preCode,
|
|
9
|
+
runCode,
|
|
10
|
+
postCode,
|
|
11
|
+
data,
|
|
12
|
+
|
|
13
|
+
warmupCycles,
|
|
14
|
+
minCycles,
|
|
15
|
+
absThreshold,
|
|
16
|
+
relThreshold,
|
|
17
|
+
|
|
18
|
+
durationsSAB,
|
|
19
|
+
controlSAB,
|
|
20
|
+
}: WorkerOptions = workerData;
|
|
21
|
+
|
|
22
|
+
const setup: SetupFn<unknown> = setupCode && Function(`return ${setupCode};`)();
|
|
23
|
+
const teardown: TeardownFn<unknown> = teardownCode && Function(`return ${teardownCode};`)();
|
|
24
|
+
|
|
25
|
+
const pre: StepFn<unknown, unknown> = preCode && Function(`return ${preCode};`)();
|
|
26
|
+
const run: StepFn<unknown, unknown> = runCode && Function(`return ${runCode};`)();
|
|
27
|
+
const post: StepFn<unknown, unknown> = postCode && Function(`return ${postCode};`)();
|
|
28
|
+
|
|
29
|
+
const exitCode = await benchmark({
|
|
30
|
+
setup,
|
|
31
|
+
teardown,
|
|
32
|
+
pre,
|
|
33
|
+
run,
|
|
34
|
+
post,
|
|
35
|
+
data,
|
|
36
|
+
|
|
37
|
+
warmupCycles,
|
|
38
|
+
minCycles,
|
|
39
|
+
absThreshold,
|
|
40
|
+
relThreshold,
|
|
41
|
+
|
|
42
|
+
durationsSAB,
|
|
43
|
+
controlSAB,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
process.exit(exitCode);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"strict": true,
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"module": "NodeNext",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"resolveJsonModule": true,
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
"moduleResolution": "NodeNext",
|
|
11
|
+
"lib": ["ESNext"],
|
|
12
|
+
"rootDir": "src",
|
|
13
|
+
"outDir": "build"
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*.ts"],
|
|
16
|
+
"exclude": ["node_modules", "**/*.tmp.ts"]
|
|
17
|
+
}
|
package/cli.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S node --no-warnings
|
|
2
|
-
|
|
3
|
-
import { Command, Option } from 'commander';
|
|
4
|
-
import Path from 'path';
|
|
5
|
-
import { glob } from 'glob';
|
|
6
|
-
import { load, createScript, benchmark, setup, teardown, measure, perform, run, defaultReporter, allowedFields } from './index.js';
|
|
7
|
-
import packageJson from './package.json' assert { type: 'json' };
|
|
8
|
-
|
|
9
|
-
const commands = new Command();
|
|
10
|
-
|
|
11
|
-
commands.name('overtake').description(packageJson.description).version(packageJson.version, '-v, --version');
|
|
12
|
-
|
|
13
|
-
commands
|
|
14
|
-
.argument('[files...]', 'File paths or path patterns to search benchmark scripts')
|
|
15
|
-
.option('-i, --inline [inline]', 'Inline benchmark.', (value, previous) => previous.concat([value]), [])
|
|
16
|
-
.option('-c, --count [count]', 'Perform count for inline benchmark.', (v) => parseInt(v))
|
|
17
|
-
.addOption(
|
|
18
|
-
new Option('-f, --fields [fields]', `Comma separated list of fields to report. Allowed values are: ${allowedFields}.`)
|
|
19
|
-
.default(['med', 'p95', 'p99', 'sum:total', 'count'])
|
|
20
|
-
.argParser((fields) =>
|
|
21
|
-
fields.split(',').filter((field) => {
|
|
22
|
-
if (!allowedFields.includes(field)) {
|
|
23
|
-
console.error(`Invalid field name: ${field}. Allowed values are: ${allowedFields.join(', ')}.`);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
return true;
|
|
27
|
-
}),
|
|
28
|
-
),
|
|
29
|
-
)
|
|
30
|
-
.action(async (patterns, { count = 1, inline, fields }) => {
|
|
31
|
-
Object.assign(globalThis, { benchmark, setup, teardown, measure, perform });
|
|
32
|
-
|
|
33
|
-
const foundFiles = await glob(patterns);
|
|
34
|
-
if (!foundFiles.length) {
|
|
35
|
-
console.error(`No files found with patterns ${patterns.join(', ')}`);
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
const files = [...new Set(foundFiles.map((filename) => Path.resolve(filename)).filter(Boolean))];
|
|
39
|
-
|
|
40
|
-
const scripts = [];
|
|
41
|
-
if (inline.length) {
|
|
42
|
-
const inlineScript = await createScript('', () => {
|
|
43
|
-
benchmark('', () => {
|
|
44
|
-
inline.forEach((code) => {
|
|
45
|
-
measure(code, `() => () => { ${code} }`);
|
|
46
|
-
});
|
|
47
|
-
perform('', count);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
scripts.push(inlineScript);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
for (const file of files) {
|
|
54
|
-
const filename = Path.resolve(file);
|
|
55
|
-
const script = await load(filename);
|
|
56
|
-
scripts.push(script);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
await run(scripts, defaultReporter, fields);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
commands.on('--help', () => {
|
|
63
|
-
console.log('');
|
|
64
|
-
console.log('Examples:');
|
|
65
|
-
console.log(' $ overtake **/__benchmarks__/*.js');
|
|
66
|
-
console.log(' $ overtake -i "class A{}" -i "function A(){}" -i "const A = () => {}" -c 1000000');
|
|
67
|
-
console.log(' $ overtake -v');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
commands.parse(process.argv);
|
package/index.d.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
declare global {
|
|
2
|
-
type CanBePromise<T> = Promise<T> | T;
|
|
3
|
-
|
|
4
|
-
interface Report {
|
|
5
|
-
type: string;
|
|
6
|
-
success: boolean;
|
|
7
|
-
count: number;
|
|
8
|
-
min: number;
|
|
9
|
-
max: number;
|
|
10
|
-
sum: number;
|
|
11
|
-
avg: number;
|
|
12
|
-
mode: number;
|
|
13
|
-
p1: number;
|
|
14
|
-
p5: number;
|
|
15
|
-
p20: number;
|
|
16
|
-
p33: number;
|
|
17
|
-
p50: number;
|
|
18
|
-
med: number;
|
|
19
|
-
p66: number;
|
|
20
|
-
p80: number;
|
|
21
|
-
p90: number;
|
|
22
|
-
p95: number;
|
|
23
|
-
p99: number;
|
|
24
|
-
setup: number;
|
|
25
|
-
init: number;
|
|
26
|
-
cycles: number;
|
|
27
|
-
teardown: number;
|
|
28
|
-
total: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
type MeasureInitResult = CanBePromise<() => void>;
|
|
32
|
-
|
|
33
|
-
function measure(title: string, init: () => MeasureInitResult): void;
|
|
34
|
-
function measure(title: string, init: (next: () => void) => MeasureInitResult): void;
|
|
35
|
-
function measure<C>(title: string, init: (context: C, next: () => void) => MeasureInitResult): void;
|
|
36
|
-
function measure<C, A>(title: string, init: (context: C, args: A, next: () => void) => MeasureInitResult): void;
|
|
37
|
-
|
|
38
|
-
function perform<A>(title: string, counter: number, args: A): void;
|
|
39
|
-
|
|
40
|
-
function setup<C>(init: () => CanBePromise<C>): void;
|
|
41
|
-
|
|
42
|
-
function teardown<C>(teardown: (context: C) => CanBePromise<void>): void;
|
|
43
|
-
|
|
44
|
-
interface Suite {
|
|
45
|
-
title: string;
|
|
46
|
-
setup: typeof setup;
|
|
47
|
-
teardown: typeof teardown;
|
|
48
|
-
measures: typeof measure[];
|
|
49
|
-
performs: typeof perform[];
|
|
50
|
-
init: () => void;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function benchmark(title: string, init: () => void): void;
|
|
54
|
-
|
|
55
|
-
function script(filename): Promise<Suite[]>;
|
|
56
|
-
}
|