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
package/src/core/engine.ts
CHANGED
|
@@ -7,13 +7,17 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { randomBytes } from 'node:crypto';
|
|
10
|
+
import { relative as pathRelative } from 'node:path';
|
|
10
11
|
|
|
11
12
|
import type {
|
|
13
|
+
BaselineSummaryData,
|
|
12
14
|
BenchmarkDefinition,
|
|
13
15
|
BenchmarkEngine,
|
|
14
16
|
BenchmarkRun,
|
|
15
17
|
BenchmarkSuite,
|
|
16
18
|
BenchmarkTask,
|
|
19
|
+
Budget,
|
|
20
|
+
BudgetSummary,
|
|
17
21
|
CiInfo,
|
|
18
22
|
ConfigurationManager,
|
|
19
23
|
EnvironmentInfo,
|
|
@@ -26,7 +30,9 @@ import type {
|
|
|
26
30
|
Reporter,
|
|
27
31
|
ReporterRegistry,
|
|
28
32
|
RunConfiguration,
|
|
33
|
+
RunId,
|
|
29
34
|
SuiteResult,
|
|
35
|
+
TaskId,
|
|
30
36
|
TaskResult,
|
|
31
37
|
ValidationError,
|
|
32
38
|
ValidationResult,
|
|
@@ -35,11 +41,15 @@ import type {
|
|
|
35
41
|
|
|
36
42
|
import {
|
|
37
43
|
BenchmarkExecutionError,
|
|
44
|
+
BudgetExceededError,
|
|
38
45
|
FileDiscoveryError,
|
|
39
46
|
SchemaValidationError,
|
|
40
47
|
SetupError,
|
|
41
48
|
StructureValidationError,
|
|
42
49
|
} from '../errors/index.js';
|
|
50
|
+
import { BaselineStorageService } from '../services/baseline-storage.js';
|
|
51
|
+
import { BudgetEvaluator } from '../services/budget-evaluator.js';
|
|
52
|
+
import { createRunId, createTaskId } from '../types/index.js';
|
|
43
53
|
|
|
44
54
|
/**
|
|
45
55
|
* Dependencies required by the BenchmarkEngine
|
|
@@ -78,6 +88,20 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
|
|
|
78
88
|
this.progressManager = dependencies.progressManager;
|
|
79
89
|
}
|
|
80
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Generate a unique run ID
|
|
93
|
+
*
|
|
94
|
+
* Uses crypto.randomBytes for cryptographically random 7-character IDs.
|
|
95
|
+
* Format: 7 lowercase alphanumeric characters (e.g., "k3m9x2p")
|
|
96
|
+
*/
|
|
97
|
+
private static generateRunId(this: void): RunId {
|
|
98
|
+
// Generate random bytes, convert to hex, then to base36, take first 7 chars
|
|
99
|
+
const hex = randomBytes(4).toString('hex');
|
|
100
|
+
const num = parseInt(hex, 16);
|
|
101
|
+
const id = num.toString(36).padStart(7, '0').substring(0, 7);
|
|
102
|
+
return createRunId(id);
|
|
103
|
+
}
|
|
104
|
+
|
|
81
105
|
/**
|
|
82
106
|
* Discover benchmark files matching the pattern(s)
|
|
83
107
|
*/
|
|
@@ -136,7 +160,7 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
|
|
|
136
160
|
}
|
|
137
161
|
|
|
138
162
|
// 4. Initialize progress tracking
|
|
139
|
-
const runId =
|
|
163
|
+
const runId = ModestBenchEngine.generateRunId();
|
|
140
164
|
|
|
141
165
|
// Pre-calculate total tasks for progress tracking
|
|
142
166
|
let totalTasks = 0;
|
|
@@ -212,9 +236,9 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
|
|
|
212
236
|
|
|
213
237
|
// Register progress callbacks with reporters that support them
|
|
214
238
|
for (const reporter of reporters) {
|
|
215
|
-
if (
|
|
239
|
+
if (reporter.onProgress) {
|
|
216
240
|
this.progressManager.onProgress((state) => {
|
|
217
|
-
void reporter.onProgress(state);
|
|
241
|
+
void reporter.onProgress?.(state);
|
|
218
242
|
});
|
|
219
243
|
}
|
|
220
244
|
}
|
|
@@ -227,12 +251,17 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
|
|
|
227
251
|
|
|
228
252
|
for (const filePath of files) {
|
|
229
253
|
try {
|
|
230
|
-
//
|
|
231
|
-
|
|
254
|
+
// Normalize file path to be relative to cwd
|
|
255
|
+
const cwd = config.cwd || process.cwd();
|
|
256
|
+
const relativePath = pathRelative(cwd, filePath);
|
|
257
|
+
|
|
258
|
+
// Call reporter onFileStart with relative path
|
|
259
|
+
await this.callReporters(reporters, 'onFileStart', relativePath);
|
|
232
260
|
|
|
233
261
|
const fileResult = await this.executeBenchmarkFile(
|
|
234
262
|
filePath,
|
|
235
263
|
mergedConfig,
|
|
264
|
+
cwd,
|
|
236
265
|
reporters,
|
|
237
266
|
signal,
|
|
238
267
|
);
|
|
@@ -246,6 +275,16 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
|
|
|
246
275
|
currentFile: filePath,
|
|
247
276
|
filesCompleted: fileResults.length,
|
|
248
277
|
});
|
|
278
|
+
|
|
279
|
+
// Check for bail: stop execution if any task failed
|
|
280
|
+
if (mergedConfig.bail) {
|
|
281
|
+
const hasFailedTask = fileResult.suites.some((suite) =>
|
|
282
|
+
suite.tasks.some((task) => task.error),
|
|
283
|
+
);
|
|
284
|
+
if (hasFailedTask) {
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
249
288
|
} catch (error) {
|
|
250
289
|
const fileError =
|
|
251
290
|
error instanceof Error ? error : new Error(String(error));
|
|
@@ -267,6 +306,11 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
|
|
|
267
306
|
|
|
268
307
|
// Call reporter onFileEnd for error case
|
|
269
308
|
await this.callReporters(reporters, 'onFileEnd', errorResult);
|
|
309
|
+
|
|
310
|
+
// Check bail flag for file-level errors
|
|
311
|
+
if (mergedConfig.bail) {
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
270
314
|
}
|
|
271
315
|
}
|
|
272
316
|
|
|
@@ -302,10 +346,103 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
|
|
|
302
346
|
}
|
|
303
347
|
|
|
304
348
|
const overallMean = totalOperations > 0 ? totalTime / totalOperations : 0;
|
|
349
|
+
// Evaluate budgets if configured
|
|
350
|
+
let budgetSummary: BudgetSummary | undefined;
|
|
351
|
+
|
|
352
|
+
if (config.budgets && Object.keys(config.budgets).length > 0) {
|
|
353
|
+
const evaluator = new BudgetEvaluator();
|
|
354
|
+
const baselineStorage = new BaselineStorageService(process.cwd());
|
|
355
|
+
|
|
356
|
+
// Collect task results
|
|
357
|
+
const taskResults = new Map<TaskId, TaskResult>();
|
|
358
|
+
|
|
359
|
+
for (const file of fileResults) {
|
|
360
|
+
for (const suite of file.suites) {
|
|
361
|
+
for (const task of suite.tasks) {
|
|
362
|
+
if (!task.error) {
|
|
363
|
+
// file.filePath is already relative to cwd
|
|
364
|
+
const taskId = createTaskId(
|
|
365
|
+
file.filePath,
|
|
366
|
+
suite.name,
|
|
367
|
+
task.name,
|
|
368
|
+
);
|
|
369
|
+
taskResults.set(taskId, task);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Load baseline data if needed for relative budgets
|
|
376
|
+
let baselineData: Map<TaskId, BaselineSummaryData> | undefined;
|
|
377
|
+
|
|
378
|
+
// Check if any budgets use relative thresholds
|
|
379
|
+
const hasRelativeBudgets = Object.values(config.budgets).some(
|
|
380
|
+
(budget) => (budget as Budget).relative,
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
if (hasRelativeBudgets) {
|
|
384
|
+
const baselineName =
|
|
385
|
+
config.baseline || (await baselineStorage.getDefault());
|
|
386
|
+
|
|
387
|
+
if (baselineName) {
|
|
388
|
+
const baseline = await baselineStorage.getBaseline(baselineName);
|
|
389
|
+
|
|
390
|
+
if (baseline) {
|
|
391
|
+
// Cast keys to TaskId since they come from validated baseline storage
|
|
392
|
+
baselineData = new Map(
|
|
393
|
+
Object.entries(baseline.summary) as [
|
|
394
|
+
TaskId,
|
|
395
|
+
BaselineSummaryData,
|
|
396
|
+
][],
|
|
397
|
+
);
|
|
398
|
+
} else {
|
|
399
|
+
console.warn(
|
|
400
|
+
`Warning: Baseline "${baselineName}" not found. Relative budgets will be skipped.`,
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
} else {
|
|
404
|
+
console.warn(
|
|
405
|
+
'Warning: Relative budgets configured but no baseline specified. Relative budgets will be skipped.',
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Evaluate budgets
|
|
411
|
+
budgetSummary = evaluator.evaluateRun(
|
|
412
|
+
config.budgets as Record<string, Budget>,
|
|
413
|
+
taskResults,
|
|
414
|
+
baselineData,
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
// Notify reporters of budget results
|
|
418
|
+
for (const reporter of reporters) {
|
|
419
|
+
if (reporter.onBudgetResult) {
|
|
420
|
+
await reporter.onBudgetResult(budgetSummary);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Handle budget failures based on budgetMode
|
|
425
|
+
if (budgetSummary.failed > 0) {
|
|
426
|
+
const mode = config.budgetMode || 'fail';
|
|
427
|
+
|
|
428
|
+
if (mode === 'fail') {
|
|
429
|
+
throw new BudgetExceededError(
|
|
430
|
+
`${budgetSummary.failed} of ${budgetSummary.total} budget(s) exceeded`,
|
|
431
|
+
budgetSummary,
|
|
432
|
+
);
|
|
433
|
+
} else if (mode === 'warn') {
|
|
434
|
+
console.warn(
|
|
435
|
+
`Warning: ${budgetSummary.failed} of ${budgetSummary.total} budget(s) exceeded`,
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
// mode === 'report': just include in output, don't fail
|
|
439
|
+
}
|
|
440
|
+
}
|
|
305
441
|
|
|
306
442
|
const endTime = new Date();
|
|
307
443
|
const finalRun: BenchmarkRun = {
|
|
308
444
|
...initialRun,
|
|
445
|
+
budgetSummary,
|
|
309
446
|
duration: endTime.getTime() - startTime.getTime(),
|
|
310
447
|
endTime,
|
|
311
448
|
files: fileResults,
|
|
@@ -489,6 +626,7 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
|
|
|
489
626
|
private async executeBenchmarkFile(
|
|
490
627
|
filePath: string,
|
|
491
628
|
config: ModestBenchConfig,
|
|
629
|
+
cwd: string,
|
|
492
630
|
reporters: Reporter[] = [],
|
|
493
631
|
signal?: AbortSignal,
|
|
494
632
|
): Promise<FileResult> {
|
|
@@ -543,11 +681,14 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
|
|
|
543
681
|
|
|
544
682
|
const endTime = new Date();
|
|
545
683
|
|
|
684
|
+
// Normalize file path to be relative to cwd
|
|
685
|
+
const relativePath = pathRelative(cwd, filePath);
|
|
686
|
+
|
|
546
687
|
return {
|
|
547
688
|
config: benchmarkDef.config,
|
|
548
689
|
duration: endTime.getTime() - startTime.getTime(),
|
|
549
690
|
endTime,
|
|
550
|
-
filePath,
|
|
691
|
+
filePath: relativePath,
|
|
551
692
|
startTime,
|
|
552
693
|
suites: suiteResults,
|
|
553
694
|
};
|
|
@@ -556,11 +697,14 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
|
|
|
556
697
|
const executionError =
|
|
557
698
|
error instanceof Error ? error : new Error(String(error));
|
|
558
699
|
|
|
700
|
+
// Normalize file path to be relative to cwd
|
|
701
|
+
const relativePath = pathRelative(cwd, filePath);
|
|
702
|
+
|
|
559
703
|
return {
|
|
560
704
|
duration: endTime.getTime() - startTime.getTime(),
|
|
561
705
|
endTime,
|
|
562
706
|
error: executionError,
|
|
563
|
-
filePath,
|
|
707
|
+
filePath: relativePath,
|
|
564
708
|
startTime,
|
|
565
709
|
suites: [],
|
|
566
710
|
};
|
|
@@ -697,19 +841,6 @@ export abstract class ModestBenchEngine implements BenchmarkEngine {
|
|
|
697
841
|
}
|
|
698
842
|
}
|
|
699
843
|
|
|
700
|
-
/**
|
|
701
|
-
* Generate a unique run ID
|
|
702
|
-
*
|
|
703
|
-
* Uses crypto.randomBytes for cryptographically random 7-character IDs.
|
|
704
|
-
* Format: 7 lowercase alphanumeric characters (e.g., "k3m9x2p")
|
|
705
|
-
*/
|
|
706
|
-
private generateRunId(): string {
|
|
707
|
-
// Generate random bytes, convert to hex, then to base36, take first 7 chars
|
|
708
|
-
const hex = randomBytes(4).toString('hex');
|
|
709
|
-
const num = parseInt(hex, 16);
|
|
710
|
-
return num.toString(36).padStart(7, '0').substring(0, 7);
|
|
711
|
-
}
|
|
712
|
-
|
|
713
844
|
/**
|
|
714
845
|
* Get CI/CD information if available
|
|
715
846
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isAbsolute, join, resolve } from 'node:path';
|
|
1
|
+
import { extname, isAbsolute, join, resolve } from 'node:path';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Resolves the final output path for a reporter
|
|
@@ -29,7 +29,15 @@ export const resolveOutputPath = (
|
|
|
29
29
|
return resolve(process.cwd(), outputFile);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
//
|
|
32
|
+
// If outputDir looks like a file (has extension), treat it as a file path
|
|
33
|
+
// This handles cases like: --output results.csv
|
|
34
|
+
if (outputDir && extname(outputDir)) {
|
|
35
|
+
return isAbsolute(outputDir)
|
|
36
|
+
? outputDir
|
|
37
|
+
: resolve(process.cwd(), outputDir);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Fall back to default behavior (outputDir is a directory)
|
|
33
41
|
if (outputDir && defaultFilename) {
|
|
34
42
|
return join(outputDir, defaultFilename);
|
|
35
43
|
}
|
package/src/errors/base.ts
CHANGED
|
@@ -143,10 +143,19 @@ export const isModestBenchError = (
|
|
|
143
143
|
error: unknown,
|
|
144
144
|
): error is ModestBenchError => {
|
|
145
145
|
return (
|
|
146
|
-
|
|
147
|
-
error !== null &&
|
|
146
|
+
isError(error) &&
|
|
148
147
|
'code' in error &&
|
|
149
148
|
typeof (error as { code: unknown }).code === 'string' &&
|
|
150
149
|
(error as { code: string }).code.startsWith('ERR_MB_')
|
|
151
150
|
);
|
|
152
151
|
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Type guard to check if an error is a standard Error
|
|
155
|
+
*
|
|
156
|
+
* @param error - The error to check
|
|
157
|
+
* @returns `true` if the error is an `Error`
|
|
158
|
+
*/
|
|
159
|
+
export const isError = (error: unknown): error is Error => {
|
|
160
|
+
return error instanceof Error;
|
|
161
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Budget-related errors
|
|
3
|
+
*
|
|
4
|
+
* Errors that occur during budget evaluation and enforcement.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { BudgetSummary } from '../types/core.js';
|
|
8
|
+
|
|
9
|
+
import { ModestBenchError } from './base.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Error thrown when performance budgets are exceeded
|
|
13
|
+
*
|
|
14
|
+
* Thrown when budget evaluation fails and budgetMode is set to 'fail'. Contains
|
|
15
|
+
* the full budget summary for detailed reporting.
|
|
16
|
+
*/
|
|
17
|
+
export class BudgetExceededError extends ModestBenchError {
|
|
18
|
+
/**
|
|
19
|
+
* Budget summary containing details of all violations
|
|
20
|
+
*/
|
|
21
|
+
public readonly budgetSummary: BudgetSummary;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Error code for budget exceeded errors
|
|
25
|
+
*/
|
|
26
|
+
readonly code = 'ERR_MB_BUDGET_EXCEEDED';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create a new budget exceeded error
|
|
30
|
+
*
|
|
31
|
+
* @param message - Human-readable error message
|
|
32
|
+
* @param budgetSummary - Budget evaluation results
|
|
33
|
+
*/
|
|
34
|
+
constructor(message: string, budgetSummary: BudgetSummary) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.budgetSummary = budgetSummary;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/errors/index.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -26,11 +26,17 @@ export * from './errors/index.js';
|
|
|
26
26
|
export { CsvReporter } from './reporters/csv.js';
|
|
27
27
|
export { HumanReporter } from './reporters/human.js';
|
|
28
28
|
export { JsonReporter } from './reporters/json.js';
|
|
29
|
+
export { ProfileHumanReporter } from './reporters/profile-human.js';
|
|
29
30
|
|
|
30
31
|
// Services
|
|
31
32
|
export { ModestBenchConfigurationManager } from './services/config-manager.js';
|
|
32
33
|
export { BenchmarkFileLoader } from './services/file-loader.js';
|
|
33
34
|
export { FileHistoryStorage } from './services/history-storage.js';
|
|
35
|
+
// Profiler services
|
|
36
|
+
export { filterProfile } from './services/profiler/profile-filter.js';
|
|
37
|
+
export { parseProfile } from './services/profiler/profile-parser.js';
|
|
38
|
+
|
|
39
|
+
export { runWithProfiling } from './services/profiler/profile-runner.js';
|
|
34
40
|
export { ModestBenchProgressManager } from './services/progress-manager.js';
|
|
35
41
|
export {
|
|
36
42
|
BaseReporter,
|
|
@@ -40,3 +46,6 @@ export {
|
|
|
40
46
|
|
|
41
47
|
// Export all types
|
|
42
48
|
export * from './types/index.js';
|
|
49
|
+
|
|
50
|
+
// Utilities
|
|
51
|
+
export { findPackageRoot } from './utils/package.js';
|
package/src/reporters/csv.ts
CHANGED
|
@@ -11,20 +11,24 @@ import { dirname } from 'node:path';
|
|
|
11
11
|
|
|
12
12
|
import type {
|
|
13
13
|
BenchmarkRun,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
BudgetResult,
|
|
15
|
+
BudgetSummary,
|
|
16
|
+
TaskId,
|
|
17
17
|
TaskResult,
|
|
18
18
|
} from '../types/index.js';
|
|
19
19
|
|
|
20
20
|
import { ReporterOutputError } from '../errors/index.js';
|
|
21
21
|
import { BaseReporter } from '../services/reporter-registry.js';
|
|
22
|
+
import { createTaskId } from '../types/index.js';
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* CSV column definitions for task results
|
|
25
26
|
*/
|
|
26
27
|
interface CsvRow {
|
|
27
28
|
readonly arch: string;
|
|
29
|
+
/** Budget passed: 1 (pass), 0 (fail), undefined (no budget) */
|
|
30
|
+
readonly budgetPassed?: number | undefined;
|
|
31
|
+
readonly budgetViolations?: string | undefined;
|
|
28
32
|
readonly ciProvider?: string | undefined;
|
|
29
33
|
readonly cpuCores: number;
|
|
30
34
|
readonly cpuModel: string;
|
|
@@ -54,6 +58,8 @@ interface CsvRow {
|
|
|
54
58
|
* CSV reporter for structured tabular output
|
|
55
59
|
*/
|
|
56
60
|
export class CsvReporter extends BaseReporter {
|
|
61
|
+
private budgetResults: Map<TaskId, BudgetResult> = new Map();
|
|
62
|
+
|
|
57
63
|
private currentFile = '';
|
|
58
64
|
|
|
59
65
|
private currentRun?: BenchmarkRun;
|
|
@@ -68,8 +74,6 @@ export class CsvReporter extends BaseReporter {
|
|
|
68
74
|
|
|
69
75
|
private readonly outputPath?: string | undefined;
|
|
70
76
|
|
|
71
|
-
private readonly quiet: boolean;
|
|
72
|
-
|
|
73
77
|
private readonly quote: string;
|
|
74
78
|
|
|
75
79
|
private rows: CsvRow[] = [];
|
|
@@ -92,7 +96,6 @@ export class CsvReporter extends BaseReporter {
|
|
|
92
96
|
this.includeMetadata = options.includeMetadata ?? true;
|
|
93
97
|
this.delimiter = options.delimiter ?? ',';
|
|
94
98
|
this.quote = options.quote ?? '"';
|
|
95
|
-
this.quiet = options.quiet ?? false;
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
/**
|
|
@@ -137,6 +140,31 @@ export class CsvReporter extends BaseReporter {
|
|
|
137
140
|
return this.includeMetadata;
|
|
138
141
|
}
|
|
139
142
|
|
|
143
|
+
onBudgetResult(summary: BudgetSummary): void {
|
|
144
|
+
// Store budget results indexed by taskId
|
|
145
|
+
for (const result of summary.results) {
|
|
146
|
+
this.budgetResults.set(result.taskId, result);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Update existing rows with budget data (since onTaskResult is called before onBudgetResult)
|
|
150
|
+
for (const row of this.rows) {
|
|
151
|
+
// row.file is already relative to cwd
|
|
152
|
+
const taskId = createTaskId(row.file, row.suite, row.task);
|
|
153
|
+
const budgetResult = this.budgetResults.get(taskId);
|
|
154
|
+
if (budgetResult) {
|
|
155
|
+
// Need to cast to mutable to update readonly properties
|
|
156
|
+
const mutableRow = row as {
|
|
157
|
+
budgetPassed?: number;
|
|
158
|
+
budgetViolations?: string;
|
|
159
|
+
};
|
|
160
|
+
mutableRow.budgetPassed = budgetResult.passed ? 1 : 0;
|
|
161
|
+
mutableRow.budgetViolations = budgetResult.violations
|
|
162
|
+
.map((v) => v.type)
|
|
163
|
+
.join('; ');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
140
168
|
async onEnd(_run: BenchmarkRun): Promise<void> {
|
|
141
169
|
const csvContent = this.generateCsv();
|
|
142
170
|
|
|
@@ -151,27 +179,15 @@ export class CsvReporter extends BaseReporter {
|
|
|
151
179
|
console.error('CSV Reporter Error:', error.message);
|
|
152
180
|
}
|
|
153
181
|
|
|
154
|
-
onFileEnd(_result: FileResult): void {
|
|
155
|
-
// No-op for CSV reporter
|
|
156
|
-
}
|
|
157
|
-
|
|
158
182
|
onFileStart(file: string): void {
|
|
159
183
|
this.currentFile = file;
|
|
160
184
|
}
|
|
161
185
|
|
|
162
|
-
onProgress(_state: ProgressState): void {
|
|
163
|
-
// No-op for CSV reporter
|
|
164
|
-
}
|
|
165
|
-
|
|
166
186
|
onStart(run: BenchmarkRun): void {
|
|
167
187
|
this.currentRun = run;
|
|
168
188
|
this.rows = [];
|
|
169
189
|
}
|
|
170
190
|
|
|
171
|
-
onSuiteEnd(_result: SuiteResult): void {
|
|
172
|
-
// No-op for CSV reporter
|
|
173
|
-
}
|
|
174
|
-
|
|
175
191
|
onSuiteStart(suite: string): void {
|
|
176
192
|
this.currentSuite = suite;
|
|
177
193
|
}
|
|
@@ -181,8 +197,21 @@ export class CsvReporter extends BaseReporter {
|
|
|
181
197
|
return;
|
|
182
198
|
}
|
|
183
199
|
|
|
200
|
+
// Look up budget result for this task
|
|
201
|
+
// this.currentFile is already relative to cwd (comes from engine)
|
|
202
|
+
const taskId = createTaskId(
|
|
203
|
+
this.currentFile,
|
|
204
|
+
this.currentSuite,
|
|
205
|
+
result.name,
|
|
206
|
+
);
|
|
207
|
+
const budgetResult = this.budgetResults.get(taskId);
|
|
208
|
+
|
|
184
209
|
const row: CsvRow = {
|
|
185
210
|
arch: this.currentRun.environment.arch,
|
|
211
|
+
budgetPassed: budgetResult ? (budgetResult.passed ? 1 : 0) : undefined,
|
|
212
|
+
budgetViolations: budgetResult
|
|
213
|
+
? budgetResult.violations.map((v) => v.type).join('; ')
|
|
214
|
+
: undefined,
|
|
186
215
|
ciProvider: this.currentRun.ci?.provider,
|
|
187
216
|
cpuCores: this.currentRun.environment.cpu.cores,
|
|
188
217
|
cpuModel: this.currentRun.environment.cpu.model,
|
|
@@ -211,10 +240,6 @@ export class CsvReporter extends BaseReporter {
|
|
|
211
240
|
this.rows.push(row);
|
|
212
241
|
}
|
|
213
242
|
|
|
214
|
-
onTaskStart(_task: string): void {
|
|
215
|
-
// No-op for CSV reporter
|
|
216
|
-
}
|
|
217
|
-
|
|
218
243
|
/**
|
|
219
244
|
* Escape a field value for CSV format
|
|
220
245
|
*/
|
|
@@ -277,6 +302,8 @@ export class CsvReporter extends BaseReporter {
|
|
|
277
302
|
'p95',
|
|
278
303
|
'p99',
|
|
279
304
|
'error',
|
|
305
|
+
'budgetPassed',
|
|
306
|
+
'budgetViolations',
|
|
280
307
|
'timestamp',
|
|
281
308
|
];
|
|
282
309
|
|
|
@@ -302,9 +329,9 @@ export class CsvReporter extends BaseReporter {
|
|
|
302
329
|
*/
|
|
303
330
|
private generateRow(row: CsvRow): string {
|
|
304
331
|
const values = [
|
|
305
|
-
row.file
|
|
306
|
-
row.suite
|
|
307
|
-
row.task
|
|
332
|
+
row.file,
|
|
333
|
+
row.suite,
|
|
334
|
+
row.task,
|
|
308
335
|
(row.mean ?? 0).toString(),
|
|
309
336
|
(row.stdDev ?? 0).toString(),
|
|
310
337
|
(row.min ?? 0).toString(),
|
|
@@ -316,6 +343,8 @@ export class CsvReporter extends BaseReporter {
|
|
|
316
343
|
(row.p95 ?? 0).toString(),
|
|
317
344
|
(row.p99 ?? 0).toString(),
|
|
318
345
|
row.error || '',
|
|
346
|
+
row.budgetPassed !== undefined ? row.budgetPassed.toString() : '',
|
|
347
|
+
row.budgetViolations || '',
|
|
319
348
|
row.timestamp || '',
|
|
320
349
|
];
|
|
321
350
|
|