modestbench 0.2.0 → 0.3.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/CHANGELOG.md +27 -0
- package/README.md +131 -34
- package/dist/cli/commands/analyze.cjs +60 -0
- package/dist/cli/commands/analyze.cjs.map +1 -0
- package/dist/cli/commands/analyze.d.cts +35 -0
- package/dist/cli/commands/analyze.d.cts.map +1 -0
- package/dist/cli/commands/analyze.d.ts +35 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -0
- package/dist/cli/commands/analyze.js +56 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/baseline.cjs +404 -0
- package/dist/cli/commands/baseline.cjs.map +1 -0
- package/dist/cli/commands/baseline.d.cts +72 -0
- package/dist/cli/commands/baseline.d.cts.map +1 -0
- package/dist/cli/commands/baseline.d.ts +72 -0
- package/dist/cli/commands/baseline.d.ts.map +1 -0
- package/dist/cli/commands/baseline.js +396 -0
- package/dist/cli/commands/baseline.js.map +1 -0
- package/dist/cli/commands/history.d.cts +1 -1
- package/dist/cli/commands/history.d.cts.map +1 -1
- package/dist/cli/commands/history.d.ts +1 -1
- package/dist/cli/commands/history.d.ts.map +1 -1
- package/dist/cli/commands/init.cjs +99 -166
- package/dist/cli/commands/init.cjs.map +1 -1
- package/dist/cli/commands/init.d.cts +4 -4
- package/dist/cli/commands/init.d.cts.map +1 -1
- package/dist/cli/commands/init.d.ts +4 -4
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +99 -166
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/run.cjs +146 -127
- package/dist/cli/commands/run.cjs.map +1 -1
- package/dist/cli/commands/run.d.cts +16 -3
- package/dist/cli/commands/run.d.cts.map +1 -1
- package/dist/cli/commands/run.d.ts +16 -3
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +145 -93
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/index.cjs +583 -394
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +4 -16
- package/dist/cli/index.d.cts.map +1 -1
- package/dist/cli/index.d.ts +4 -16
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +575 -386
- package/dist/cli/index.js.map +1 -1
- package/dist/config/budget-schema.cjs +172 -0
- package/dist/config/budget-schema.cjs.map +1 -0
- package/dist/config/budget-schema.d.cts +59 -0
- package/dist/config/budget-schema.d.cts.map +1 -0
- package/dist/config/budget-schema.d.ts +59 -0
- package/dist/config/budget-schema.d.ts.map +1 -0
- package/dist/config/budget-schema.js +166 -0
- package/dist/config/budget-schema.js.map +1 -0
- package/dist/config/schema.cjs +182 -2
- package/dist/config/schema.cjs.map +1 -1
- package/dist/config/schema.d.cts +122 -3
- package/dist/config/schema.d.cts.map +1 -1
- package/dist/config/schema.d.ts +122 -3
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +180 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/constants.cjs +45 -2
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +41 -0
- package/dist/constants.d.cts.map +1 -1
- package/dist/constants.d.ts +41 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +44 -1
- package/dist/constants.js.map +1 -1
- package/dist/core/engine.cjs +114 -23
- package/dist/core/engine.cjs.map +1 -1
- package/dist/core/engine.d.cts +7 -7
- package/dist/core/engine.d.cts.map +1 -1
- package/dist/core/engine.d.ts +7 -7
- package/dist/core/engine.d.ts.map +1 -1
- package/dist/core/engine.js +115 -24
- package/dist/core/engine.js.map +1 -1
- package/dist/core/engines/accurate-engine.cjs +171 -36
- package/dist/core/engines/accurate-engine.cjs.map +1 -1
- package/dist/core/engines/accurate-engine.d.cts +5 -0
- package/dist/core/engines/accurate-engine.d.cts.map +1 -1
- package/dist/core/engines/accurate-engine.d.ts +5 -0
- package/dist/core/engines/accurate-engine.d.ts.map +1 -1
- package/dist/core/engines/accurate-engine.js +171 -36
- package/dist/core/engines/accurate-engine.js.map +1 -1
- package/dist/core/engines/tinybench-engine.cjs +3 -2
- package/dist/core/engines/tinybench-engine.cjs.map +1 -1
- package/dist/core/engines/tinybench-engine.d.cts.map +1 -1
- package/dist/core/engines/tinybench-engine.d.ts.map +1 -1
- package/dist/core/engines/tinybench-engine.js +3 -2
- package/dist/core/engines/tinybench-engine.js.map +1 -1
- package/dist/core/output-path-resolver.cjs +8 -1
- package/dist/core/output-path-resolver.cjs.map +1 -1
- package/dist/core/output-path-resolver.d.cts.map +1 -1
- package/dist/core/output-path-resolver.d.ts.map +1 -1
- package/dist/core/output-path-resolver.js +9 -2
- package/dist/core/output-path-resolver.js.map +1 -1
- package/dist/errors/base.cjs +12 -3
- package/dist/errors/base.cjs.map +1 -1
- package/dist/errors/base.d.cts +7 -0
- package/dist/errors/base.d.cts.map +1 -1
- package/dist/errors/base.d.ts +7 -0
- package/dist/errors/base.d.ts.map +1 -1
- package/dist/errors/base.js +10 -2
- package/dist/errors/base.js.map +1 -1
- package/dist/errors/budget.cjs +37 -0
- package/dist/errors/budget.cjs.map +1 -0
- package/dist/errors/budget.d.cts +31 -0
- package/dist/errors/budget.d.cts.map +1 -0
- package/dist/errors/budget.d.ts +31 -0
- package/dist/errors/budget.d.ts.map +1 -0
- package/dist/errors/budget.js +33 -0
- package/dist/errors/budget.js.map +1 -0
- package/dist/errors/index.cjs +4 -1
- package/dist/errors/index.cjs.map +1 -1
- package/dist/errors/index.d.cts +1 -0
- package/dist/errors/index.d.cts.map +1 -1
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +2 -0
- package/dist/errors/index.js.map +1 -1
- package/dist/index.cjs +13 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/reporters/csv.cjs +37 -17
- package/dist/reporters/csv.cjs.map +1 -1
- package/dist/reporters/csv.d.cts +3 -6
- package/dist/reporters/csv.d.cts.map +1 -1
- package/dist/reporters/csv.d.ts +3 -6
- package/dist/reporters/csv.d.ts.map +1 -1
- package/dist/reporters/csv.js +37 -17
- package/dist/reporters/csv.js.map +1 -1
- package/dist/reporters/human.cjs +290 -67
- package/dist/reporters/human.cjs.map +1 -1
- package/dist/reporters/human.d.cts +25 -13
- package/dist/reporters/human.d.cts.map +1 -1
- package/dist/reporters/human.d.ts +25 -13
- package/dist/reporters/human.d.ts.map +1 -1
- package/dist/reporters/human.js +290 -67
- package/dist/reporters/human.js.map +1 -1
- package/dist/reporters/json.cjs +23 -48
- package/dist/reporters/json.cjs.map +1 -1
- package/dist/reporters/json.d.cts +2 -28
- package/dist/reporters/json.d.cts.map +1 -1
- package/dist/reporters/json.d.ts +2 -28
- package/dist/reporters/json.d.ts.map +1 -1
- package/dist/reporters/json.js +25 -50
- package/dist/reporters/json.js.map +1 -1
- package/dist/reporters/profile-human.cjs +154 -0
- package/dist/reporters/profile-human.cjs.map +1 -0
- package/dist/reporters/profile-human.d.cts +44 -0
- package/dist/reporters/profile-human.d.cts.map +1 -0
- package/dist/reporters/profile-human.d.ts +44 -0
- package/dist/reporters/profile-human.d.ts.map +1 -0
- package/dist/reporters/profile-human.js +147 -0
- package/dist/reporters/profile-human.js.map +1 -0
- package/dist/reporters/simple.cjs +67 -45
- package/dist/reporters/simple.cjs.map +1 -1
- package/dist/reporters/simple.d.cts +14 -14
- package/dist/reporters/simple.d.cts.map +1 -1
- package/dist/reporters/simple.d.ts +14 -14
- package/dist/reporters/simple.d.ts.map +1 -1
- package/dist/reporters/simple.js +67 -45
- package/dist/reporters/simple.js.map +1 -1
- package/dist/schema/modestbench-config.schema.json +153 -0
- package/dist/services/baseline-storage.cjs +151 -0
- package/dist/services/baseline-storage.cjs.map +1 -0
- package/dist/services/baseline-storage.d.cts +55 -0
- package/dist/services/baseline-storage.d.cts.map +1 -0
- package/dist/services/baseline-storage.d.ts +55 -0
- package/dist/services/baseline-storage.d.ts.map +1 -0
- package/dist/services/baseline-storage.js +147 -0
- package/dist/services/baseline-storage.js.map +1 -0
- package/dist/services/budget-evaluator.cjs +146 -0
- package/dist/services/budget-evaluator.cjs.map +1 -0
- package/dist/services/budget-evaluator.d.cts +29 -0
- package/dist/services/budget-evaluator.d.cts.map +1 -0
- package/dist/services/budget-evaluator.d.ts +29 -0
- package/dist/services/budget-evaluator.d.ts.map +1 -0
- package/dist/services/budget-evaluator.js +142 -0
- package/dist/services/budget-evaluator.js.map +1 -0
- package/dist/services/config-manager.cjs +24 -10
- package/dist/services/config-manager.cjs.map +1 -1
- package/dist/services/config-manager.d.cts +6 -1
- package/dist/services/config-manager.d.cts.map +1 -1
- package/dist/services/config-manager.d.ts +6 -1
- package/dist/services/config-manager.d.ts.map +1 -1
- package/dist/services/config-manager.js +24 -10
- package/dist/services/config-manager.js.map +1 -1
- package/dist/services/file-loader.cjs +3 -6
- package/dist/services/file-loader.cjs.map +1 -1
- package/dist/services/file-loader.d.cts.map +1 -1
- package/dist/services/file-loader.d.ts.map +1 -1
- package/dist/services/file-loader.js +3 -6
- package/dist/services/file-loader.js.map +1 -1
- package/dist/services/profiler/profile-filter.cjs +116 -0
- package/dist/services/profiler/profile-filter.cjs.map +1 -0
- package/dist/services/profiler/profile-filter.d.cts +20 -0
- package/dist/services/profiler/profile-filter.d.cts.map +1 -0
- package/dist/services/profiler/profile-filter.d.ts +20 -0
- package/dist/services/profiler/profile-filter.d.ts.map +1 -0
- package/dist/services/profiler/profile-filter.js +112 -0
- package/dist/services/profiler/profile-filter.js.map +1 -0
- package/dist/services/profiler/profile-parser.cjs +139 -0
- package/dist/services/profiler/profile-parser.cjs.map +1 -0
- package/dist/services/profiler/profile-parser.d.cts +18 -0
- package/dist/services/profiler/profile-parser.d.cts.map +1 -0
- package/dist/services/profiler/profile-parser.d.ts +18 -0
- package/dist/services/profiler/profile-parser.d.ts.map +1 -0
- package/dist/services/profiler/profile-parser.js +132 -0
- package/dist/services/profiler/profile-parser.js.map +1 -0
- package/dist/services/profiler/profile-runner.cjs +90 -0
- package/dist/services/profiler/profile-runner.cjs.map +1 -0
- package/dist/services/profiler/profile-runner.d.cts +29 -0
- package/dist/services/profiler/profile-runner.d.cts.map +1 -0
- package/dist/services/profiler/profile-runner.d.ts +29 -0
- package/dist/services/profiler/profile-runner.d.ts.map +1 -0
- package/dist/services/profiler/profile-runner.js +86 -0
- package/dist/services/profiler/profile-runner.js.map +1 -0
- package/dist/services/progress-manager.cjs +10 -2
- package/dist/services/progress-manager.cjs.map +1 -1
- package/dist/services/progress-manager.d.cts +2 -0
- package/dist/services/progress-manager.d.cts.map +1 -1
- package/dist/services/progress-manager.d.ts +2 -0
- package/dist/services/progress-manager.d.ts.map +1 -1
- package/dist/services/progress-manager.js +10 -2
- package/dist/services/progress-manager.js.map +1 -1
- package/dist/services/reporter-registry.cjs +18 -24
- package/dist/services/reporter-registry.cjs.map +1 -1
- package/dist/services/reporter-registry.d.cts +18 -40
- package/dist/services/reporter-registry.d.cts.map +1 -1
- package/dist/services/reporter-registry.d.ts +18 -40
- package/dist/services/reporter-registry.d.ts.map +1 -1
- package/dist/services/reporter-registry.js +18 -24
- package/dist/services/reporter-registry.js.map +1 -1
- package/dist/types/budgets.cjs +8 -0
- package/dist/types/budgets.cjs.map +1 -0
- package/dist/types/budgets.d.cts +149 -0
- package/dist/types/budgets.d.cts.map +1 -0
- package/dist/types/budgets.d.ts +149 -0
- package/dist/types/budgets.d.ts.map +1 -0
- package/dist/types/budgets.js +7 -0
- package/dist/types/budgets.js.map +1 -0
- package/dist/types/cli.cjs +2 -11
- package/dist/types/cli.cjs.map +1 -1
- package/dist/types/cli.d.cts +3 -227
- package/dist/types/cli.d.cts.map +1 -1
- package/dist/types/cli.d.ts +3 -227
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/cli.js +2 -11
- package/dist/types/cli.js.map +1 -1
- package/dist/types/core.cjs +6 -1
- package/dist/types/core.cjs.map +1 -1
- package/dist/types/core.d.cts +15 -2
- package/dist/types/core.d.cts.map +1 -1
- package/dist/types/core.d.ts +15 -2
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/core.js +2 -1
- package/dist/types/core.js.map +1 -1
- package/dist/types/index.cjs +5 -0
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.cts +2 -0
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/interfaces.d.cts +19 -8
- package/dist/types/interfaces.d.cts.map +1 -1
- package/dist/types/interfaces.d.ts +19 -8
- package/dist/types/interfaces.d.ts.map +1 -1
- package/dist/types/profiler.cjs +11 -0
- package/dist/types/profiler.cjs.map +1 -0
- package/dist/types/profiler.d.cts +102 -0
- package/dist/types/profiler.d.cts.map +1 -0
- package/dist/types/profiler.d.ts +102 -0
- package/dist/types/profiler.d.ts.map +1 -0
- package/dist/types/profiler.js +10 -0
- package/dist/types/profiler.js.map +1 -0
- package/dist/types/utility.cjs.map +1 -1
- package/dist/types/utility.d.cts +0 -8
- package/dist/types/utility.d.cts.map +1 -1
- package/dist/types/utility.d.ts +0 -8
- package/dist/types/utility.d.ts.map +1 -1
- package/dist/types/utility.js.map +1 -1
- package/dist/utils/identifiers.cjs +32 -0
- package/dist/utils/identifiers.cjs.map +1 -0
- package/dist/utils/identifiers.d.cts +32 -0
- package/dist/utils/identifiers.d.cts.map +1 -0
- package/dist/utils/identifiers.d.ts +32 -0
- package/dist/utils/identifiers.d.ts.map +1 -0
- package/dist/utils/identifiers.js +27 -0
- package/dist/utils/identifiers.js.map +1 -0
- package/dist/utils/package.cjs +40 -0
- package/dist/utils/package.cjs.map +1 -0
- package/dist/utils/package.d.cts +15 -0
- package/dist/utils/package.d.cts.map +1 -0
- package/dist/utils/package.d.ts +15 -0
- package/dist/utils/package.d.ts.map +1 -0
- package/dist/utils/package.js +33 -0
- package/dist/utils/package.js.map +1 -0
- package/dist/utils/type-guards.cjs +48 -0
- package/dist/utils/type-guards.cjs.map +1 -0
- package/dist/utils/type-guards.d.cts +22 -0
- package/dist/utils/type-guards.d.cts.map +1 -0
- package/dist/utils/type-guards.d.ts +22 -0
- package/dist/utils/type-guards.d.ts.map +1 -0
- package/dist/utils/type-guards.js +43 -0
- package/dist/utils/type-guards.js.map +1 -0
- package/package.json +18 -19
- package/src/cli/commands/analyze.ts +101 -0
- package/src/cli/commands/baseline.ts +577 -0
- package/src/cli/commands/history.ts +1 -1
- package/src/cli/commands/init.ts +116 -194
- package/src/cli/commands/run.ts +183 -113
- package/src/cli/index.ts +425 -183
- package/src/config/budget-schema.ts +189 -0
- package/src/config/schema.ts +260 -1
- package/src/constants.ts +53 -1
- package/src/core/engine.ts +169 -22
- package/src/core/engines/accurate-engine.ts +195 -44
- package/src/core/engines/tinybench-engine.ts +3 -2
- package/src/core/output-path-resolver.ts +10 -2
- package/src/errors/base.ts +11 -2
- package/src/errors/budget.ts +38 -0
- package/src/errors/index.ts +3 -0
- package/src/index.ts +9 -0
- package/src/reporters/csv.ts +54 -25
- package/src/reporters/human.ts +434 -115
- package/src/reporters/json.ts +26 -71
- package/src/reporters/profile-human.ts +210 -0
- package/src/reporters/simple.ts +88 -54
- package/src/services/baseline-storage.ts +199 -0
- package/src/services/budget-evaluator.ts +182 -0
- package/src/services/config-manager.ts +24 -9
- package/src/services/file-loader.ts +3 -6
- package/src/services/profiler/profile-filter.ts +147 -0
- package/src/services/profiler/profile-parser.ts +194 -0
- package/src/services/profiler/profile-runner.ts +121 -0
- package/src/services/progress-manager.ts +12 -2
- package/src/services/reporter-registry.ts +46 -81
- package/src/types/budgets.ts +180 -0
- package/src/types/cli.ts +5 -238
- package/src/types/core.ts +52 -10
- package/src/types/index.ts +5 -0
- package/src/types/interfaces.ts +24 -6
- package/src/types/profiler.ts +135 -0
- package/src/types/utility.ts +0 -10
- package/src/utils/identifiers.ts +58 -0
- package/src/utils/package.ts +35 -0
- package/src/utils/type-guards.ts +51 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import type { BaselineStorage } from '../types/budgets.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Zod schema for budget configuration
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parse time string to nanoseconds Supports: "10ms", "5s", "100us", "50ns"
|
|
13
|
+
*/
|
|
14
|
+
export const parseTimeString = (value: string): number => {
|
|
15
|
+
const match = value.match(/^(\d+(?:\.\d+)?)(ns|us|ms|s)$/i);
|
|
16
|
+
if (!match) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`Invalid time format: "${value}". Expected format like "10ms", "5s", "100us", "50ns"`,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const num = parseFloat(match[1]!);
|
|
23
|
+
const unit = match[2]!.toLowerCase();
|
|
24
|
+
|
|
25
|
+
const multipliers = {
|
|
26
|
+
ms: 1_000_000,
|
|
27
|
+
ns: 1,
|
|
28
|
+
s: 1_000_000_000,
|
|
29
|
+
us: 1_000,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return num * multipliers[unit as keyof typeof multipliers];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Parse percentage string to decimal Supports: "10%", "5.5%"
|
|
37
|
+
*
|
|
38
|
+
* Note: Does not round result - preserves full precision from input
|
|
39
|
+
*/
|
|
40
|
+
export const parsePercentageString = (value: string): number => {
|
|
41
|
+
const match = value.match(/^(\d+(?:\.\d+)?)%$/);
|
|
42
|
+
if (!match) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Invalid percentage format: "${value}". Expected format like "10%", "5.5%"`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return parseFloat(match[1]!) / 100;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Time or nanoseconds
|
|
53
|
+
*/
|
|
54
|
+
const timeSchema = z
|
|
55
|
+
.union([
|
|
56
|
+
z.number().int().nonnegative().describe('Time in nanoseconds'),
|
|
57
|
+
z
|
|
58
|
+
.string()
|
|
59
|
+
.transform(parseTimeString)
|
|
60
|
+
.describe('Time string (e.g., "10ms")'),
|
|
61
|
+
])
|
|
62
|
+
.describe('Time value as nanoseconds or time string');
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Percentage or decimal
|
|
66
|
+
*/
|
|
67
|
+
const percentageSchema = z
|
|
68
|
+
.union([
|
|
69
|
+
z
|
|
70
|
+
.number()
|
|
71
|
+
.min(0)
|
|
72
|
+
.max(1)
|
|
73
|
+
.describe('Percentage as decimal (e.g., 0.1 for 10%)'),
|
|
74
|
+
z
|
|
75
|
+
.string()
|
|
76
|
+
.transform(parsePercentageString)
|
|
77
|
+
.describe('Percentage string (e.g., "10%")'),
|
|
78
|
+
])
|
|
79
|
+
.describe('Percentage value as decimal or percentage string');
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Absolute budget schema
|
|
83
|
+
*/
|
|
84
|
+
const absoluteBudgetSchema = z
|
|
85
|
+
.object({
|
|
86
|
+
maxP99: timeSchema
|
|
87
|
+
.optional()
|
|
88
|
+
.describe('Maximum 99th percentile execution time'),
|
|
89
|
+
maxTime: timeSchema.optional().describe('Maximum mean execution time'),
|
|
90
|
+
minOpsPerSec: z
|
|
91
|
+
.number()
|
|
92
|
+
.positive()
|
|
93
|
+
.optional()
|
|
94
|
+
.describe('Minimum operations per second'),
|
|
95
|
+
})
|
|
96
|
+
.describe('Absolute performance budget thresholds');
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Relative budget schema
|
|
100
|
+
*/
|
|
101
|
+
const relativeBudgetSchema = z
|
|
102
|
+
.object({
|
|
103
|
+
baseline: z
|
|
104
|
+
.string()
|
|
105
|
+
.optional()
|
|
106
|
+
.describe('Name of baseline to compare against'),
|
|
107
|
+
maxRegression: percentageSchema
|
|
108
|
+
.optional()
|
|
109
|
+
.describe('Maximum allowed performance regression'),
|
|
110
|
+
})
|
|
111
|
+
.describe('Relative performance budget thresholds compared to baseline');
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Complete budget schema
|
|
115
|
+
*/
|
|
116
|
+
export const budgetSchema = z
|
|
117
|
+
.object({
|
|
118
|
+
absolute: absoluteBudgetSchema
|
|
119
|
+
.optional()
|
|
120
|
+
.describe('Absolute performance thresholds'),
|
|
121
|
+
relative: relativeBudgetSchema
|
|
122
|
+
.optional()
|
|
123
|
+
.describe('Relative performance thresholds'),
|
|
124
|
+
})
|
|
125
|
+
.describe('Performance budget configuration');
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Baseline reference schema
|
|
129
|
+
*
|
|
130
|
+
* Note: This validates and transforms to branded types (RunId, TaskId). The
|
|
131
|
+
* transforms are safe as they only add compile-time type information.
|
|
132
|
+
*/
|
|
133
|
+
const baselineReferenceSchema = z
|
|
134
|
+
.object({
|
|
135
|
+
branch: z.string().optional().describe('Git branch name'),
|
|
136
|
+
commit: z
|
|
137
|
+
.string()
|
|
138
|
+
.length(40)
|
|
139
|
+
.regex(/^[0-9a-f]{40}$/)
|
|
140
|
+
.optional()
|
|
141
|
+
.describe('Full Git commit hash (40 hex characters)'),
|
|
142
|
+
date: z.coerce.date().describe('Date baseline was created'),
|
|
143
|
+
name: z.string().describe('Baseline name identifier'),
|
|
144
|
+
runId: z
|
|
145
|
+
.string()
|
|
146
|
+
.length(7)
|
|
147
|
+
.regex(/^[0-9a-z]{7}$/)
|
|
148
|
+
.describe('Benchmark run ID (7 lowercase alphanumeric characters)'),
|
|
149
|
+
summary: z
|
|
150
|
+
.record(
|
|
151
|
+
z.string(),
|
|
152
|
+
z.object({
|
|
153
|
+
mean: z.number().describe('Mean execution time in nanoseconds'),
|
|
154
|
+
opsPerSecond: z.number().describe('Operations per second'),
|
|
155
|
+
p99: z
|
|
156
|
+
.number()
|
|
157
|
+
.optional()
|
|
158
|
+
.describe('99th percentile execution time in nanoseconds'),
|
|
159
|
+
}),
|
|
160
|
+
)
|
|
161
|
+
.describe('Summary of benchmark results for each task'),
|
|
162
|
+
})
|
|
163
|
+
.describe('Named baseline reference with benchmark results summary');
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Baseline storage schema
|
|
167
|
+
*/
|
|
168
|
+
export const baselineStorageSchema = z
|
|
169
|
+
.object({
|
|
170
|
+
baselines: z
|
|
171
|
+
.record(z.string(), baselineReferenceSchema)
|
|
172
|
+
.describe('Map of baseline names to baseline references'),
|
|
173
|
+
default: z.string().optional().describe('Default baseline name'),
|
|
174
|
+
version: z.string().describe('Schema version'),
|
|
175
|
+
})
|
|
176
|
+
.describe('Baseline storage file format');
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Validate baseline storage
|
|
180
|
+
*
|
|
181
|
+
* Note: The parsed data contains plain strings that are cast to branded types
|
|
182
|
+
* (RunId, TaskId). This is safe because branded types are compile-time only
|
|
183
|
+
* constructs.
|
|
184
|
+
*/
|
|
185
|
+
export const validateBaselineStorage = (storage: unknown): BaselineStorage => {
|
|
186
|
+
const parsed = baselineStorageSchema.parse(storage);
|
|
187
|
+
// Cast is safe: branded types are erased at runtime
|
|
188
|
+
return parsed as unknown as BaselineStorage;
|
|
189
|
+
};
|
package/src/config/schema.ts
CHANGED
|
@@ -11,6 +11,7 @@ import * as z from 'zod';
|
|
|
11
11
|
import type { ModestBenchConfig } from '../types/core.js';
|
|
12
12
|
|
|
13
13
|
import { BENCHMARK_FILE_PATTERN } from '../constants.js';
|
|
14
|
+
import { parsePercentageString, parseTimeString } from './budget-schema.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Schema for threshold configuration
|
|
@@ -56,6 +57,46 @@ const thresholdConfigSchema = z
|
|
|
56
57
|
title: 'Threshold Configuration',
|
|
57
58
|
});
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Inline budget schema for configuration (no transforms for JSON Schema
|
|
62
|
+
* compatibility - transforms are applied manually in transformBudgets
|
|
63
|
+
* function)
|
|
64
|
+
*/
|
|
65
|
+
const budgetSchema = z
|
|
66
|
+
.object({
|
|
67
|
+
absolute: z
|
|
68
|
+
.object({
|
|
69
|
+
maxP99: z
|
|
70
|
+
.union([z.number().positive(), z.string()])
|
|
71
|
+
.optional()
|
|
72
|
+
.describe('Maximum 99th percentile in nanoseconds or time string'),
|
|
73
|
+
maxTime: z
|
|
74
|
+
.union([z.number().positive(), z.string()])
|
|
75
|
+
.describe(
|
|
76
|
+
'Maximum mean time in nanoseconds or time string (e.g., "10ms")',
|
|
77
|
+
),
|
|
78
|
+
minOpsPerSec: z
|
|
79
|
+
.number()
|
|
80
|
+
.positive()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe('Minimum operations per second'),
|
|
83
|
+
})
|
|
84
|
+
.optional()
|
|
85
|
+
.describe('Absolute performance thresholds'),
|
|
86
|
+
relative: z
|
|
87
|
+
.object({
|
|
88
|
+
maxRegression: z
|
|
89
|
+
.union([z.number().min(0).max(1), z.string()])
|
|
90
|
+
.optional()
|
|
91
|
+
.describe(
|
|
92
|
+
'Maximum regression as decimal (0.1) or percentage string ("10%")',
|
|
93
|
+
),
|
|
94
|
+
})
|
|
95
|
+
.optional()
|
|
96
|
+
.describe('Relative performance thresholds vs baseline'),
|
|
97
|
+
})
|
|
98
|
+
.describe('Performance budget with absolute and/or relative thresholds');
|
|
99
|
+
|
|
59
100
|
/**
|
|
60
101
|
* Schema for the main ModestBench configuration
|
|
61
102
|
*
|
|
@@ -71,6 +112,27 @@ const modestBenchConfigSchema = z
|
|
|
71
112
|
'JSON Schema reference for IDE support (not used by ModestBench)',
|
|
72
113
|
),
|
|
73
114
|
bail: z.boolean().describe('Stop benchmark execution on first failure'),
|
|
115
|
+
baseline: z
|
|
116
|
+
.string()
|
|
117
|
+
.optional()
|
|
118
|
+
.describe(
|
|
119
|
+
'Name of baseline to use for relative budget comparisons. Must match a saved baseline name.',
|
|
120
|
+
),
|
|
121
|
+
budgetMode: z
|
|
122
|
+
.enum(['fail', 'warn', 'report'])
|
|
123
|
+
.optional()
|
|
124
|
+
.describe(
|
|
125
|
+
'How to handle budget violations: "fail" exits with error (default), "warn" shows warnings, "report" includes in output without failing',
|
|
126
|
+
),
|
|
127
|
+
budgets: z
|
|
128
|
+
.record(
|
|
129
|
+
z.string(),
|
|
130
|
+
z.record(z.string(), z.record(z.string(), budgetSchema)),
|
|
131
|
+
)
|
|
132
|
+
.optional()
|
|
133
|
+
.describe(
|
|
134
|
+
'Performance budgets organized by file → suite → task. Budgets define acceptable performance thresholds.',
|
|
135
|
+
),
|
|
74
136
|
exclude: z
|
|
75
137
|
.array(z.string())
|
|
76
138
|
.describe(
|
|
@@ -109,6 +171,55 @@ const modestBenchConfigSchema = z
|
|
|
109
171
|
.describe(
|
|
110
172
|
`Glob pattern(s) for discovering benchmark files. Can be a single pattern string or array of patterns (e.g., "**/*${BENCHMARK_FILE_PATTERN}")`,
|
|
111
173
|
),
|
|
174
|
+
profile: z
|
|
175
|
+
.object({
|
|
176
|
+
exclude: z
|
|
177
|
+
.array(z.string())
|
|
178
|
+
.optional()
|
|
179
|
+
.describe('Glob patterns to exclude from profiling results'),
|
|
180
|
+
focus: z
|
|
181
|
+
.array(z.string())
|
|
182
|
+
.optional()
|
|
183
|
+
.describe(
|
|
184
|
+
'Glob patterns to focus on in profiling results. If specified, only matching files will be shown',
|
|
185
|
+
),
|
|
186
|
+
minCallCount: z
|
|
187
|
+
.number()
|
|
188
|
+
.int()
|
|
189
|
+
.nonnegative()
|
|
190
|
+
.optional()
|
|
191
|
+
.describe(
|
|
192
|
+
'Minimum number of times a function must be called to be included in results',
|
|
193
|
+
),
|
|
194
|
+
minExecutionPercent: z
|
|
195
|
+
.number()
|
|
196
|
+
.nonnegative()
|
|
197
|
+
.max(100)
|
|
198
|
+
.default(1.0)
|
|
199
|
+
.describe(
|
|
200
|
+
'Minimum execution percentage threshold for including functions in results',
|
|
201
|
+
),
|
|
202
|
+
outputFile: z
|
|
203
|
+
.string()
|
|
204
|
+
.optional()
|
|
205
|
+
.describe('Path to write profile report to file'),
|
|
206
|
+
smartDetection: z
|
|
207
|
+
.boolean()
|
|
208
|
+
.default(true)
|
|
209
|
+
.describe(
|
|
210
|
+
'Automatically detect and focus on user code, excluding node_modules and Node.js internals',
|
|
211
|
+
),
|
|
212
|
+
topN: z
|
|
213
|
+
.number()
|
|
214
|
+
.int()
|
|
215
|
+
.positive()
|
|
216
|
+
.default(25)
|
|
217
|
+
.describe('Maximum number of top functions to show in results'),
|
|
218
|
+
})
|
|
219
|
+
.optional()
|
|
220
|
+
.describe(
|
|
221
|
+
'Configuration for profile command to identify benchmark candidates',
|
|
222
|
+
),
|
|
112
223
|
quiet: z
|
|
113
224
|
.boolean()
|
|
114
225
|
.describe(
|
|
@@ -176,6 +287,136 @@ export const partialModestBenchConfigSchema: z.ZodType<
|
|
|
176
287
|
Partial<ModestBenchConfig>
|
|
177
288
|
> = modestBenchConfigSchema.partial();
|
|
178
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Input budget type (before transformation)
|
|
292
|
+
*/
|
|
293
|
+
interface BudgetInput {
|
|
294
|
+
absolute?: {
|
|
295
|
+
maxP99?: number | string;
|
|
296
|
+
maxTime?: number | string;
|
|
297
|
+
minOpsPerSec?: number;
|
|
298
|
+
};
|
|
299
|
+
relative?: {
|
|
300
|
+
maxRegression?: number | string;
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Output budget type (after transformation)
|
|
306
|
+
*/
|
|
307
|
+
interface BudgetOutput {
|
|
308
|
+
absolute?: {
|
|
309
|
+
maxP99?: number;
|
|
310
|
+
maxTime?: number;
|
|
311
|
+
minOpsPerSec?: number;
|
|
312
|
+
};
|
|
313
|
+
relative?: {
|
|
314
|
+
maxRegression?: number;
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Transform budget values (parse time/percentage strings)
|
|
320
|
+
*/
|
|
321
|
+
const transformBudgetValues = (budget: BudgetInput): BudgetOutput => {
|
|
322
|
+
const transformed: BudgetOutput = {};
|
|
323
|
+
|
|
324
|
+
if (budget.absolute) {
|
|
325
|
+
transformed.absolute = {};
|
|
326
|
+
|
|
327
|
+
// Copy minOpsPerSec as-is (already a number)
|
|
328
|
+
if (budget.absolute.minOpsPerSec !== undefined) {
|
|
329
|
+
transformed.absolute.minOpsPerSec = budget.absolute.minOpsPerSec;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Parse time strings
|
|
333
|
+
if (budget.absolute.maxTime !== undefined) {
|
|
334
|
+
transformed.absolute.maxTime =
|
|
335
|
+
typeof budget.absolute.maxTime === 'string'
|
|
336
|
+
? parseTimeString(budget.absolute.maxTime)
|
|
337
|
+
: budget.absolute.maxTime;
|
|
338
|
+
}
|
|
339
|
+
if (budget.absolute.maxP99 !== undefined) {
|
|
340
|
+
transformed.absolute.maxP99 =
|
|
341
|
+
typeof budget.absolute.maxP99 === 'string'
|
|
342
|
+
? parseTimeString(budget.absolute.maxP99)
|
|
343
|
+
: budget.absolute.maxP99;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (budget.relative) {
|
|
348
|
+
transformed.relative = {};
|
|
349
|
+
|
|
350
|
+
// Parse percentage strings
|
|
351
|
+
if (budget.relative.maxRegression !== undefined) {
|
|
352
|
+
transformed.relative.maxRegression =
|
|
353
|
+
typeof budget.relative.maxRegression === 'string'
|
|
354
|
+
? parsePercentageString(budget.relative.maxRegression)
|
|
355
|
+
: budget.relative.maxRegression;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return transformed;
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Transform nested budget structure to flat TaskId → Budget mapping Also parses
|
|
364
|
+
* time and percentage strings
|
|
365
|
+
*
|
|
366
|
+
* @internal
|
|
367
|
+
*/
|
|
368
|
+
const transformBudgets = (
|
|
369
|
+
nested: Record<string, Record<string, Record<string, unknown>>> | undefined,
|
|
370
|
+
): Record<string, unknown> | undefined => {
|
|
371
|
+
if (!nested) {
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const flat: Record<string, unknown> = {};
|
|
376
|
+
|
|
377
|
+
for (const [file, suites] of Object.entries(nested)) {
|
|
378
|
+
for (const [suite, tasks] of Object.entries(suites)) {
|
|
379
|
+
for (const [task, budget] of Object.entries(tasks)) {
|
|
380
|
+
const taskId = `${file}/${suite}/${task}`;
|
|
381
|
+
// Transform budget values (parse strings)
|
|
382
|
+
flat[taskId] = transformBudgetValues(budget as BudgetInput);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return flat;
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Safely parse and validate a partial configuration object with budget
|
|
392
|
+
* transformation
|
|
393
|
+
*
|
|
394
|
+
* @param config - The configuration object to validate
|
|
395
|
+
* @returns A result object with either success: true and data, or success:
|
|
396
|
+
* false and error
|
|
397
|
+
*/
|
|
398
|
+
export const safeParsePartialConfig = (config: unknown) => {
|
|
399
|
+
const result = partialModestBenchConfigSchema.safeParse(config);
|
|
400
|
+
|
|
401
|
+
// Transform nested budgets to flat structure after validation
|
|
402
|
+
if (result.success && result.data.budgets) {
|
|
403
|
+
return {
|
|
404
|
+
...result,
|
|
405
|
+
data: {
|
|
406
|
+
...result.data,
|
|
407
|
+
budgets: transformBudgets(
|
|
408
|
+
result.data.budgets as Record<
|
|
409
|
+
string,
|
|
410
|
+
Record<string, Record<string, unknown>>
|
|
411
|
+
>,
|
|
412
|
+
),
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return result;
|
|
418
|
+
};
|
|
419
|
+
|
|
179
420
|
/**
|
|
180
421
|
* Safely parse and validate a configuration object
|
|
181
422
|
*
|
|
@@ -184,5 +425,23 @@ export const partialModestBenchConfigSchema: z.ZodType<
|
|
|
184
425
|
* false and error
|
|
185
426
|
*/
|
|
186
427
|
export const safeParseConfig = (config: unknown) => {
|
|
187
|
-
|
|
428
|
+
const result = modestBenchConfigSchema.safeParse(config);
|
|
429
|
+
|
|
430
|
+
// Transform nested budgets to flat structure after validation
|
|
431
|
+
if (result.success && result.data.budgets) {
|
|
432
|
+
return {
|
|
433
|
+
...result,
|
|
434
|
+
data: {
|
|
435
|
+
...result.data,
|
|
436
|
+
budgets: transformBudgets(
|
|
437
|
+
result.data.budgets as Record<
|
|
438
|
+
string,
|
|
439
|
+
Record<string, Record<string, unknown>>
|
|
440
|
+
>,
|
|
441
|
+
),
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return result;
|
|
188
447
|
};
|
package/src/constants.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type Engine } from './types/cli.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Supported benchmark file extensions
|
|
3
5
|
*/
|
|
@@ -20,21 +22,71 @@ export const BENCHMARK_FILE_PATTERN = `.bench.{${Array.from(
|
|
|
20
22
|
.map((ext) => ext.slice(1))
|
|
21
23
|
.join(',')}}`;
|
|
22
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Timeout before we force-quit when aborting benchmarks (ms)
|
|
27
|
+
*/
|
|
28
|
+
export const ABORT_TIMEOUT = 500;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Exit codes for the CLI
|
|
32
|
+
*/
|
|
33
|
+
export const ExitCodes = {
|
|
34
|
+
BENCHMARK_FAILURES: 1,
|
|
35
|
+
CONFIG_ERROR: 2,
|
|
36
|
+
DISCOVERY_ERROR: 3,
|
|
37
|
+
RUNTIME_ERROR: 5,
|
|
38
|
+
SUCCESS: 0,
|
|
39
|
+
UNKNOWN_ERROR: 99,
|
|
40
|
+
VALIDATION_ERROR: 4,
|
|
41
|
+
} as const;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Supported benchmark engines
|
|
45
|
+
*/
|
|
46
|
+
export const Engines = {
|
|
47
|
+
ACCURATE: 'accurate',
|
|
48
|
+
TINYBENCH: 'tinybench',
|
|
49
|
+
} as const satisfies Record<string, Engine>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Default benchmark engine
|
|
53
|
+
*/
|
|
54
|
+
export const DEFAULT_ENGINE = Engines.TINYBENCH;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Supported reporters
|
|
58
|
+
*/
|
|
59
|
+
export const Reporters = {
|
|
60
|
+
CSV: 'csv',
|
|
61
|
+
HUMAN: 'human',
|
|
62
|
+
JSON: 'json',
|
|
63
|
+
SIMPLE: 'simple',
|
|
64
|
+
} as const;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Default reporter
|
|
68
|
+
*/
|
|
69
|
+
export const DEFAULT_REPORTER = Reporters.HUMAN;
|
|
70
|
+
|
|
23
71
|
/**
|
|
24
72
|
* Error codes for all ModestBench errors
|
|
25
73
|
*
|
|
26
74
|
* Use these constants to check error types instead of instanceof checks.
|
|
27
75
|
*/
|
|
28
76
|
export const ErrorCodes = {
|
|
77
|
+
//#region budget-errors
|
|
78
|
+
BUDGET_EXCEEDED: 'ERR_MB_BUDGET_EXCEEDED',
|
|
29
79
|
//#region cli-errors
|
|
30
80
|
CLI_INVALID_ARGUMENT: 'ERR_MB_CLI_INVALID_ARGUMENT',
|
|
31
|
-
CLI_INVALID_DATE_FORMAT: 'ERR_MB_CLI_INVALID_DATE_FORMAT',
|
|
32
81
|
//#endregion
|
|
33
82
|
|
|
83
|
+
CLI_INVALID_DATE_FORMAT: 'ERR_MB_CLI_INVALID_DATE_FORMAT',
|
|
34
84
|
//#region config-errors
|
|
35
85
|
CONFIG_LOAD_FAILED: 'ERR_MB_CONFIG_LOAD_FAILED',
|
|
36
86
|
CONFIG_NOT_FOUND: 'ERR_MB_CONFIG_NOT_FOUND',
|
|
37
87
|
CONFIG_UNSUPPORTED_FORMAT: 'ERR_MB_CONFIG_UNSUPPORTED_FORMAT',
|
|
88
|
+
//#endregion
|
|
89
|
+
|
|
38
90
|
CONFIG_VALIDATION_FAILED: 'ERR_MB_CONFIG_VALIDATION_FAILED',
|
|
39
91
|
//#endregion
|
|
40
92
|
|