modestbench 0.2.0 → 0.3.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/CHANGELOG.md +20 -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 +88 -155
- 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 +88 -155
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/run.cjs +132 -114
- 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 +131 -80
- 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 +103 -21
- 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 +104 -22
- package/dist/core/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 +66 -40
- package/dist/reporters/human.cjs.map +1 -1
- package/dist/reporters/human.d.cts +14 -13
- package/dist/reporters/human.d.cts.map +1 -1
- package/dist/reporters/human.d.ts +14 -13
- package/dist/reporters/human.d.ts.map +1 -1
- package/dist/reporters/human.js +66 -40
- 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 +149 -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 +142 -0
- package/dist/reporters/profile-human.js.map +1 -0
- package/dist/reporters/simple.cjs +64 -44
- 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 +64 -44
- 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 +23 -9
- 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 +23 -9
- 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 +113 -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 +109 -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/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 +13 -2
- package/dist/types/core.d.cts.map +1 -1
- package/dist/types/core.d.ts +13 -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 +15 -8
- package/dist/types/interfaces.d.cts.map +1 -1
- package/dist/types/interfaces.d.ts +15 -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 +100 -0
- package/dist/types/profiler.d.cts.map +1 -0
- package/dist/types/profiler.d.ts +100 -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 +10 -10
- 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 +105 -183
- package/src/cli/commands/run.ts +167 -98
- 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 +151 -20
- 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 +88 -47
- package/src/reporters/json.ts +26 -71
- package/src/reporters/profile-human.ts +204 -0
- package/src/reporters/simple.ts +84 -53
- package/src/services/baseline-storage.ts +199 -0
- package/src/services/budget-evaluator.ts +182 -0
- package/src/services/config-manager.ts +23 -8
- package/src/services/file-loader.ts +3 -6
- package/src/services/profiler/profile-filter.ts +143 -0
- package/src/services/profiler/profile-parser.ts +194 -0
- package/src/services/profiler/profile-runner.ts +121 -0
- 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 +50 -10
- package/src/types/index.ts +5 -0
- package/src/types/interfaces.ts +16 -6
- package/src/types/profiler.ts +132 -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,577 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModestBench Baseline Command
|
|
3
|
+
*
|
|
4
|
+
* Manage performance baselines for regression testing and budget comparison.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { relative } from 'node:path';
|
|
8
|
+
|
|
9
|
+
import type { BenchmarkRun } from '../../types/index.js';
|
|
10
|
+
import type { CliContext } from '../index.js';
|
|
11
|
+
|
|
12
|
+
import { BaselineStorageService } from '../../services/baseline-storage.js';
|
|
13
|
+
import { createTaskId } from '../../types/index.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Options for baseline analyze command
|
|
17
|
+
*/
|
|
18
|
+
interface BaselineAnalyzeOptions extends BaselineBaseOptions {
|
|
19
|
+
confidence?: number | undefined;
|
|
20
|
+
runs?: number | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Base options shared by all baseline subcommands
|
|
25
|
+
*/
|
|
26
|
+
interface BaselineBaseOptions {
|
|
27
|
+
cwd?: string;
|
|
28
|
+
quiet?: boolean | undefined;
|
|
29
|
+
verbose?: boolean | undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Options for baseline delete command
|
|
34
|
+
*/
|
|
35
|
+
interface BaselineDeleteOptions extends BaselineBaseOptions {
|
|
36
|
+
name: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Options for baseline list command
|
|
41
|
+
*/
|
|
42
|
+
interface BaselineListOptions extends BaselineBaseOptions {
|
|
43
|
+
format?: 'human' | 'json' | undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Options for baseline set command
|
|
48
|
+
*/
|
|
49
|
+
interface BaselineSetOptions extends BaselineBaseOptions {
|
|
50
|
+
branch?: string | undefined;
|
|
51
|
+
commit?: string | undefined;
|
|
52
|
+
default?: boolean | undefined;
|
|
53
|
+
name: string;
|
|
54
|
+
runId?: string | undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Options for baseline show command
|
|
59
|
+
*/
|
|
60
|
+
interface BaselineShowOptions extends BaselineBaseOptions {
|
|
61
|
+
format?: 'human' | 'json' | undefined;
|
|
62
|
+
name: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Format duration in human-readable format
|
|
67
|
+
*/
|
|
68
|
+
const formatDuration = (nanoseconds: number): string => {
|
|
69
|
+
if (nanoseconds < 1000) {
|
|
70
|
+
return `${nanoseconds.toFixed(2)}ns`;
|
|
71
|
+
}
|
|
72
|
+
if (nanoseconds < 1000000) {
|
|
73
|
+
return `${(nanoseconds / 1000).toFixed(2)}μs`;
|
|
74
|
+
}
|
|
75
|
+
if (nanoseconds < 1000000000) {
|
|
76
|
+
return `${(nanoseconds / 1000000).toFixed(2)}ms`;
|
|
77
|
+
}
|
|
78
|
+
return `${(nanoseconds / 1000000000).toFixed(2)}s`;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Format operations per second
|
|
83
|
+
*/
|
|
84
|
+
const formatOpsPerSec = (ops: number): string => {
|
|
85
|
+
if (ops < 1000) {
|
|
86
|
+
return `${ops.toFixed(2)} ops/sec`;
|
|
87
|
+
}
|
|
88
|
+
if (ops < 1000000) {
|
|
89
|
+
return `${(ops / 1000).toFixed(2)}K ops/sec`;
|
|
90
|
+
}
|
|
91
|
+
if (ops < 1000000000) {
|
|
92
|
+
return `${(ops / 1000000).toFixed(2)}M ops/sec`;
|
|
93
|
+
}
|
|
94
|
+
return `${(ops / 1000000000).toFixed(2)}B ops/sec`;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Format date in readable format
|
|
99
|
+
*/
|
|
100
|
+
const formatDate = (date: Date): string => {
|
|
101
|
+
return date.toISOString().replace('T', ' ').substring(0, 19);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Calculate mean of an array of numbers
|
|
106
|
+
*/
|
|
107
|
+
const calculateMean = (values: number[]): number => {
|
|
108
|
+
if (values.length === 0) {
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
return values.reduce((sum, val) => sum + val, 0) / values.length;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Calculate standard deviation
|
|
116
|
+
*/
|
|
117
|
+
const calculateStdDev = (values: number[]): number => {
|
|
118
|
+
if (values.length === 0) {
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
const mean = calculateMean(values);
|
|
122
|
+
const squaredDiffs = values.map((val) => Math.pow(val - mean, 2));
|
|
123
|
+
const variance = calculateMean(squaredDiffs);
|
|
124
|
+
return Math.sqrt(variance);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get z-score for confidence level
|
|
129
|
+
*/
|
|
130
|
+
const getZScore = (confidence: number): number => {
|
|
131
|
+
// Common confidence levels
|
|
132
|
+
if (confidence >= 0.99) {
|
|
133
|
+
return 2.576;
|
|
134
|
+
} // 99%
|
|
135
|
+
if (confidence >= 0.98) {
|
|
136
|
+
return 2.326;
|
|
137
|
+
} // 98%
|
|
138
|
+
if (confidence >= 0.95) {
|
|
139
|
+
return 1.96;
|
|
140
|
+
} // 95%
|
|
141
|
+
if (confidence >= 0.9) {
|
|
142
|
+
return 1.645;
|
|
143
|
+
} // 90%
|
|
144
|
+
if (confidence >= 0.85) {
|
|
145
|
+
return 1.44;
|
|
146
|
+
} // 85%
|
|
147
|
+
if (confidence >= 0.8) {
|
|
148
|
+
return 1.282;
|
|
149
|
+
} // 80%
|
|
150
|
+
return 1.96; // Default to 95%
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Handle the set subcommand
|
|
155
|
+
*/
|
|
156
|
+
export const handleSetCommand = async (
|
|
157
|
+
context: CliContext,
|
|
158
|
+
options: BaselineSetOptions,
|
|
159
|
+
): Promise<number> => {
|
|
160
|
+
try {
|
|
161
|
+
const storage = new BaselineStorageService(options.cwd);
|
|
162
|
+
|
|
163
|
+
// Get the benchmark run
|
|
164
|
+
let run: BenchmarkRun | null = null;
|
|
165
|
+
|
|
166
|
+
if (options.runId) {
|
|
167
|
+
// Load specific run by ID
|
|
168
|
+
run = await context.historyStorage.loadRun(options.runId);
|
|
169
|
+
if (!run) {
|
|
170
|
+
console.error(`Error: Run with ID "${options.runId}" not found`);
|
|
171
|
+
return 1;
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
// Get most recent run
|
|
175
|
+
const runs = await context.historyStorage.queryRuns({ limit: 1 });
|
|
176
|
+
if (runs.length === 0) {
|
|
177
|
+
console.error(
|
|
178
|
+
'Error: No benchmark runs found. Run benchmarks first with "modestbench run"',
|
|
179
|
+
);
|
|
180
|
+
return 1;
|
|
181
|
+
}
|
|
182
|
+
run = runs[0]!;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Save baseline
|
|
186
|
+
await storage.saveBaseline(options.name, run, {
|
|
187
|
+
branch: options.branch,
|
|
188
|
+
commit: options.commit,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Set as default if requested
|
|
192
|
+
if (options.default) {
|
|
193
|
+
await storage.setDefault(options.name);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!options.quiet) {
|
|
197
|
+
console.log(`✓ Baseline "${options.name}" saved successfully`);
|
|
198
|
+
console.log(` Run ID: ${run.id}`);
|
|
199
|
+
if (options.commit) {
|
|
200
|
+
console.log(` Commit: ${options.commit}`);
|
|
201
|
+
}
|
|
202
|
+
if (options.branch) {
|
|
203
|
+
console.log(` Branch: ${options.branch}`);
|
|
204
|
+
}
|
|
205
|
+
if (options.default) {
|
|
206
|
+
console.log(` Set as default baseline`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return 0;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error(
|
|
213
|
+
'Failed to save baseline:',
|
|
214
|
+
error instanceof Error ? error.message : String(error),
|
|
215
|
+
);
|
|
216
|
+
return 5;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Handle the list subcommand
|
|
222
|
+
*/
|
|
223
|
+
export const handleListCommand = async (
|
|
224
|
+
context: CliContext,
|
|
225
|
+
options: BaselineListOptions,
|
|
226
|
+
): Promise<number> => {
|
|
227
|
+
try {
|
|
228
|
+
const storage = new BaselineStorageService(options.cwd);
|
|
229
|
+
const baselines = await storage.listBaselines();
|
|
230
|
+
const defaultBaseline = await storage.getDefault();
|
|
231
|
+
|
|
232
|
+
if (baselines.length === 0) {
|
|
233
|
+
if (!options.quiet) {
|
|
234
|
+
console.log('No baselines found');
|
|
235
|
+
}
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (options.format === 'json') {
|
|
240
|
+
console.log(
|
|
241
|
+
JSON.stringify(
|
|
242
|
+
{
|
|
243
|
+
baselines: baselines.map((b) => ({
|
|
244
|
+
...b,
|
|
245
|
+
date: b.date.toISOString(),
|
|
246
|
+
isDefault: b.name === defaultBaseline,
|
|
247
|
+
})),
|
|
248
|
+
default: defaultBaseline,
|
|
249
|
+
},
|
|
250
|
+
null,
|
|
251
|
+
2,
|
|
252
|
+
),
|
|
253
|
+
);
|
|
254
|
+
} else {
|
|
255
|
+
// Human-readable format
|
|
256
|
+
if (!options.quiet) {
|
|
257
|
+
console.log(`\nBaselines (${baselines.length}):\n`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
for (const baseline of baselines) {
|
|
261
|
+
const isDefault = baseline.name === defaultBaseline;
|
|
262
|
+
const defaultMarker = isDefault ? ' (default)' : '';
|
|
263
|
+
console.log(` ${baseline.name}${defaultMarker}`);
|
|
264
|
+
console.log(` Date: ${formatDate(baseline.date)}`);
|
|
265
|
+
console.log(` Run ID: ${baseline.runId}`);
|
|
266
|
+
if (baseline.commit) {
|
|
267
|
+
console.log(` Commit: ${baseline.commit.substring(0, 8)}`);
|
|
268
|
+
}
|
|
269
|
+
if (baseline.branch) {
|
|
270
|
+
console.log(` Branch: ${baseline.branch}`);
|
|
271
|
+
}
|
|
272
|
+
console.log(` Tasks: ${Object.keys(baseline.summary).length}`);
|
|
273
|
+
console.log();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return 0;
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.error(
|
|
280
|
+
'Failed to list baselines:',
|
|
281
|
+
error instanceof Error ? error.message : String(error),
|
|
282
|
+
);
|
|
283
|
+
return 5;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Handle the show subcommand
|
|
289
|
+
*/
|
|
290
|
+
export const handleShowCommand = async (
|
|
291
|
+
context: CliContext,
|
|
292
|
+
options: BaselineShowOptions,
|
|
293
|
+
): Promise<number> => {
|
|
294
|
+
try {
|
|
295
|
+
const storage = new BaselineStorageService(options.cwd);
|
|
296
|
+
const baseline = await storage.getBaseline(options.name);
|
|
297
|
+
|
|
298
|
+
if (!baseline) {
|
|
299
|
+
console.error(`Error: Baseline "${options.name}" not found`);
|
|
300
|
+
return 1;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const defaultBaseline = await storage.getDefault();
|
|
304
|
+
const isDefault = baseline.name === defaultBaseline;
|
|
305
|
+
|
|
306
|
+
if (options.format === 'json') {
|
|
307
|
+
console.log(
|
|
308
|
+
JSON.stringify(
|
|
309
|
+
{
|
|
310
|
+
...baseline,
|
|
311
|
+
date: baseline.date.toISOString(),
|
|
312
|
+
isDefault,
|
|
313
|
+
},
|
|
314
|
+
null,
|
|
315
|
+
2,
|
|
316
|
+
),
|
|
317
|
+
);
|
|
318
|
+
} else {
|
|
319
|
+
// Human-readable format
|
|
320
|
+
console.log(
|
|
321
|
+
`\nBaseline: ${baseline.name}${isDefault ? ' (default)' : ''}\n`,
|
|
322
|
+
);
|
|
323
|
+
console.log(` Date: ${formatDate(baseline.date)}`);
|
|
324
|
+
console.log(` Run ID: ${baseline.runId}`);
|
|
325
|
+
if (baseline.commit) {
|
|
326
|
+
console.log(` Commit: ${baseline.commit}`);
|
|
327
|
+
}
|
|
328
|
+
if (baseline.branch) {
|
|
329
|
+
console.log(` Branch: ${baseline.branch}`);
|
|
330
|
+
}
|
|
331
|
+
console.log();
|
|
332
|
+
|
|
333
|
+
// Show task summary
|
|
334
|
+
const tasks = Object.entries(baseline.summary);
|
|
335
|
+
if (tasks.length > 0) {
|
|
336
|
+
console.log(` Tasks (${tasks.length}):\n`);
|
|
337
|
+
for (const [taskId, data] of tasks) {
|
|
338
|
+
console.log(` ${taskId}`);
|
|
339
|
+
console.log(` Mean: ${formatDuration(data.mean)}`);
|
|
340
|
+
console.log(` Ops/sec: ${formatOpsPerSec(data.opsPerSecond)}`);
|
|
341
|
+
if (data.p99) {
|
|
342
|
+
console.log(` P99: ${formatDuration(data.p99)}`);
|
|
343
|
+
}
|
|
344
|
+
console.log();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return 0;
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.error(
|
|
352
|
+
'Failed to show baseline:',
|
|
353
|
+
error instanceof Error ? error.message : String(error),
|
|
354
|
+
);
|
|
355
|
+
return 5;
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Handle the delete subcommand
|
|
361
|
+
*/
|
|
362
|
+
export const handleDeleteCommand = async (
|
|
363
|
+
context: CliContext,
|
|
364
|
+
options: BaselineDeleteOptions,
|
|
365
|
+
): Promise<number> => {
|
|
366
|
+
try {
|
|
367
|
+
const storage = new BaselineStorageService(options.cwd);
|
|
368
|
+
|
|
369
|
+
// Check if baseline exists
|
|
370
|
+
const baseline = await storage.getBaseline(options.name);
|
|
371
|
+
if (!baseline) {
|
|
372
|
+
console.error(`Error: Baseline "${options.name}" not found`);
|
|
373
|
+
return 1;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Delete it
|
|
377
|
+
await storage.deleteBaseline(options.name);
|
|
378
|
+
|
|
379
|
+
if (!options.quiet) {
|
|
380
|
+
console.log(`✓ Baseline "${options.name}" deleted successfully`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return 0;
|
|
384
|
+
} catch (error) {
|
|
385
|
+
console.error(
|
|
386
|
+
'Failed to delete baseline:',
|
|
387
|
+
error instanceof Error ? error.message : String(error),
|
|
388
|
+
);
|
|
389
|
+
return 5;
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Handle the analyze subcommand
|
|
395
|
+
*/
|
|
396
|
+
export const handleAnalyzeCommand = async (
|
|
397
|
+
context: CliContext,
|
|
398
|
+
options: BaselineAnalyzeOptions,
|
|
399
|
+
): Promise<number> => {
|
|
400
|
+
try {
|
|
401
|
+
const runLimit = options.runs || 10;
|
|
402
|
+
const confidence = options.confidence || 0.95;
|
|
403
|
+
|
|
404
|
+
// Validate confidence
|
|
405
|
+
if (confidence < 0.5 || confidence > 0.999) {
|
|
406
|
+
console.error('Error: Confidence must be between 0.5 and 0.999');
|
|
407
|
+
return 1;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Query recent runs
|
|
411
|
+
const runs = await context.historyStorage.queryRuns({ limit: runLimit });
|
|
412
|
+
|
|
413
|
+
if (runs.length < 2) {
|
|
414
|
+
console.error(
|
|
415
|
+
`Error: Insufficient history. Found ${runs.length} run(s), need at least 2 to analyze trends.`,
|
|
416
|
+
);
|
|
417
|
+
return 1;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Collect metrics per task
|
|
421
|
+
const taskMetrics = new Map<
|
|
422
|
+
string,
|
|
423
|
+
{ means: number[]; opsPerSec: number[]; p99s: number[] }
|
|
424
|
+
>();
|
|
425
|
+
|
|
426
|
+
for (const run of runs) {
|
|
427
|
+
for (const file of run.files) {
|
|
428
|
+
for (const suite of file.suites) {
|
|
429
|
+
for (const task of suite.tasks) {
|
|
430
|
+
// Normalize file path to be relative to cwd for consistency with budgets
|
|
431
|
+
const relativePath = relative(
|
|
432
|
+
options.cwd || process.cwd(),
|
|
433
|
+
file.filePath,
|
|
434
|
+
);
|
|
435
|
+
const taskId = createTaskId(relativePath, suite.name, task.name);
|
|
436
|
+
|
|
437
|
+
if (!taskMetrics.has(taskId)) {
|
|
438
|
+
taskMetrics.set(taskId, { means: [], opsPerSec: [], p99s: [] });
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const metrics = taskMetrics.get(taskId)!;
|
|
442
|
+
metrics.means.push(task.mean);
|
|
443
|
+
metrics.opsPerSec.push(task.opsPerSecond);
|
|
444
|
+
if (task.p99) {
|
|
445
|
+
metrics.p99s.push(task.p99);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Calculate suggested budgets (flat format first)
|
|
453
|
+
const zScore = getZScore(confidence);
|
|
454
|
+
const flatBudgets: Record<string, { absolute: Record<string, unknown> }> =
|
|
455
|
+
{};
|
|
456
|
+
|
|
457
|
+
for (const [taskId, metrics] of taskMetrics.entries()) {
|
|
458
|
+
const avgMean = calculateMean(metrics.means);
|
|
459
|
+
const stdDevMean = calculateStdDev(metrics.means);
|
|
460
|
+
const suggestedMaxTime = Math.ceil(avgMean + zScore * stdDevMean);
|
|
461
|
+
|
|
462
|
+
const avgOps = calculateMean(metrics.opsPerSec);
|
|
463
|
+
const stdDevOps = calculateStdDev(metrics.opsPerSec);
|
|
464
|
+
const suggestedMinOps = Math.floor(
|
|
465
|
+
Math.max(0, avgOps - zScore * stdDevOps),
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
flatBudgets[taskId] = {
|
|
469
|
+
absolute: {
|
|
470
|
+
maxTime: suggestedMaxTime,
|
|
471
|
+
...(suggestedMinOps > 0 && { minOpsPerSec: suggestedMinOps }),
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// Add p99 if available
|
|
476
|
+
if (metrics.p99s.length > 0) {
|
|
477
|
+
const avgP99 = calculateMean(metrics.p99s);
|
|
478
|
+
const stdDevP99 = calculateStdDev(metrics.p99s);
|
|
479
|
+
const suggestedMaxP99 = Math.ceil(avgP99 + zScore * stdDevP99);
|
|
480
|
+
flatBudgets[taskId].absolute.maxP99 = suggestedMaxP99;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Convert flat budgets to nested format for user config
|
|
485
|
+
const nestedBudgets: Record<
|
|
486
|
+
string,
|
|
487
|
+
Record<string, Record<string, unknown>>
|
|
488
|
+
> = {};
|
|
489
|
+
|
|
490
|
+
for (const [taskId, budget] of Object.entries(flatBudgets)) {
|
|
491
|
+
// Parse taskId format: "file/suite/task"
|
|
492
|
+
const lastSlash = taskId.lastIndexOf('/');
|
|
493
|
+
const secondLastSlash = taskId.lastIndexOf('/', lastSlash - 1);
|
|
494
|
+
|
|
495
|
+
const file = taskId.substring(0, secondLastSlash);
|
|
496
|
+
const suite = taskId.substring(secondLastSlash + 1, lastSlash);
|
|
497
|
+
const task = taskId.substring(lastSlash + 1);
|
|
498
|
+
|
|
499
|
+
if (!nestedBudgets[file]) {
|
|
500
|
+
nestedBudgets[file] = {};
|
|
501
|
+
}
|
|
502
|
+
if (!nestedBudgets[file][suite]) {
|
|
503
|
+
nestedBudgets[file][suite] = {};
|
|
504
|
+
}
|
|
505
|
+
nestedBudgets[file][suite][task] = budget;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Output results
|
|
509
|
+
if (!options.quiet) {
|
|
510
|
+
console.log(
|
|
511
|
+
`\nAnalyzed ${runs.length} run(s) with ${confidence * 100}% confidence\n`,
|
|
512
|
+
);
|
|
513
|
+
console.log('Suggested budget configuration:\n');
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const config = {
|
|
517
|
+
budgetMode: 'fail',
|
|
518
|
+
budgets: nestedBudgets,
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
console.log(JSON.stringify(config, null, 2));
|
|
522
|
+
|
|
523
|
+
if (!options.quiet) {
|
|
524
|
+
const confidencePercent = confidence * 100;
|
|
525
|
+
console.log('\n' + '='.repeat(70));
|
|
526
|
+
console.log('How these budget values were calculated:');
|
|
527
|
+
console.log('='.repeat(70));
|
|
528
|
+
console.log();
|
|
529
|
+
console.log(
|
|
530
|
+
`Using ${confidencePercent}% confidence level with ${runs.length} historical runs:`,
|
|
531
|
+
);
|
|
532
|
+
console.log();
|
|
533
|
+
console.log(
|
|
534
|
+
` • maxTime = mean + (${confidencePercent}% z-score × std deviation)`,
|
|
535
|
+
);
|
|
536
|
+
console.log(
|
|
537
|
+
` • minOpsPerSec = mean - (${confidencePercent}% z-score × std deviation)`,
|
|
538
|
+
);
|
|
539
|
+
console.log(
|
|
540
|
+
` • maxP99 = mean + (${confidencePercent}% z-score × std deviation)`,
|
|
541
|
+
);
|
|
542
|
+
console.log();
|
|
543
|
+
console.log(
|
|
544
|
+
`This means each budget is statistically expected to pass ${confidencePercent}% of`,
|
|
545
|
+
);
|
|
546
|
+
console.log(
|
|
547
|
+
'the time based on your historical benchmark data. The higher the',
|
|
548
|
+
);
|
|
549
|
+
console.log(
|
|
550
|
+
'confidence level, the more lenient the budgets (less likely to fail).',
|
|
551
|
+
);
|
|
552
|
+
console.log();
|
|
553
|
+
console.log('To adjust strictness:');
|
|
554
|
+
console.log(
|
|
555
|
+
' • Lower confidence (e.g., 0.90) = stricter budgets, catch smaller regressions',
|
|
556
|
+
);
|
|
557
|
+
console.log(
|
|
558
|
+
' • Higher confidence (e.g., 0.99) = looser budgets, reduce false positives',
|
|
559
|
+
);
|
|
560
|
+
console.log();
|
|
561
|
+
console.log(
|
|
562
|
+
'Copy the above configuration into your modestbench.config.json file.',
|
|
563
|
+
);
|
|
564
|
+
console.log(
|
|
565
|
+
'You may adjust the values based on your performance requirements.',
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return 0;
|
|
570
|
+
} catch (error) {
|
|
571
|
+
console.error(
|
|
572
|
+
'Failed to analyze history:',
|
|
573
|
+
error instanceof Error ? error.message : String(error),
|
|
574
|
+
);
|
|
575
|
+
return 5;
|
|
576
|
+
}
|
|
577
|
+
};
|
|
@@ -27,7 +27,7 @@ import { TrendAnalysisService } from '../../services/history/trend-analysis.js';
|
|
|
27
27
|
* Base options shared by all history subcommands
|
|
28
28
|
*/
|
|
29
29
|
interface BaseHistoryOptions {
|
|
30
|
-
cwd
|
|
30
|
+
cwd?: string;
|
|
31
31
|
quiet?: boolean | undefined;
|
|
32
32
|
verbose?: boolean | undefined;
|
|
33
33
|
}
|