modestbench 0.0.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 +45 -0
- package/LICENSE.md +55 -0
- package/README.md +699 -0
- package/dist/bootstrap.cjs +37 -0
- package/dist/bootstrap.cjs.map +1 -0
- package/dist/bootstrap.d.cts +17 -0
- package/dist/bootstrap.d.cts.map +1 -0
- package/dist/bootstrap.d.ts +17 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +33 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/cli/commands/history.cjs +459 -0
- package/dist/cli/commands/history.cjs.map +1 -0
- package/dist/cli/commands/history.d.cts +34 -0
- package/dist/cli/commands/history.d.cts.map +1 -0
- package/dist/cli/commands/history.d.ts +34 -0
- package/dist/cli/commands/history.d.ts.map +1 -0
- package/dist/cli/commands/history.js +422 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/init.cjs +566 -0
- package/dist/cli/commands/init.cjs.map +1 -0
- package/dist/cli/commands/init.d.cts +26 -0
- package/dist/cli/commands/init.d.cts.map +1 -0
- package/dist/cli/commands/init.d.ts +26 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +562 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/run.cjs +285 -0
- package/dist/cli/commands/run.cjs.map +1 -0
- package/dist/cli/commands/run.d.cts +37 -0
- package/dist/cli/commands/run.d.cts.map +1 -0
- package/dist/cli/commands/run.d.ts +37 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +248 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/index.cjs +523 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +58 -0
- package/dist/cli/index.d.cts.map +1 -0
- package/dist/cli/index.d.ts +58 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +515 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/manager.cjs +370 -0
- package/dist/config/manager.cjs.map +1 -0
- package/dist/config/manager.d.cts +46 -0
- package/dist/config/manager.d.cts.map +1 -0
- package/dist/config/manager.d.ts +46 -0
- package/dist/config/manager.d.ts.map +1 -0
- package/dist/config/manager.js +333 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/config/schema.cjs +182 -0
- package/dist/config/schema.cjs.map +1 -0
- package/dist/config/schema.d.cts +51 -0
- package/dist/config/schema.d.cts.map +1 -0
- package/dist/config/schema.d.ts +51 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +145 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/constants.cjs +22 -0
- package/dist/constants.cjs.map +1 -0
- package/dist/constants.d.cts +10 -0
- package/dist/constants.d.cts.map +1 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +19 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/benchmark-schema.cjs +135 -0
- package/dist/core/benchmark-schema.cjs.map +1 -0
- package/dist/core/benchmark-schema.d.cts +139 -0
- package/dist/core/benchmark-schema.d.cts.map +1 -0
- package/dist/core/benchmark-schema.d.ts +139 -0
- package/dist/core/benchmark-schema.d.ts.map +1 -0
- package/dist/core/benchmark-schema.js +132 -0
- package/dist/core/benchmark-schema.js.map +1 -0
- package/dist/core/engine.cjs +669 -0
- package/dist/core/engine.cjs.map +1 -0
- package/dist/core/engine.d.cts +128 -0
- package/dist/core/engine.d.cts.map +1 -0
- package/dist/core/engine.d.ts +128 -0
- package/dist/core/engine.d.ts.map +1 -0
- package/dist/core/engine.js +632 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/engines/accurate-engine.cjs +292 -0
- package/dist/core/engines/accurate-engine.cjs.map +1 -0
- package/dist/core/engines/accurate-engine.d.cts +63 -0
- package/dist/core/engines/accurate-engine.d.cts.map +1 -0
- package/dist/core/engines/accurate-engine.d.ts +63 -0
- package/dist/core/engines/accurate-engine.d.ts.map +1 -0
- package/dist/core/engines/accurate-engine.js +288 -0
- package/dist/core/engines/accurate-engine.js.map +1 -0
- package/dist/core/engines/index.cjs +21 -0
- package/dist/core/engines/index.cjs.map +1 -0
- package/dist/core/engines/index.d.cts +16 -0
- package/dist/core/engines/index.d.cts.map +1 -0
- package/dist/core/engines/index.d.ts +16 -0
- package/dist/core/engines/index.d.ts.map +1 -0
- package/dist/core/engines/index.js +16 -0
- package/dist/core/engines/index.js.map +1 -0
- package/dist/core/engines/tinybench-engine.cjs +286 -0
- package/dist/core/engines/tinybench-engine.cjs.map +1 -0
- package/dist/core/engines/tinybench-engine.d.cts +18 -0
- package/dist/core/engines/tinybench-engine.d.cts.map +1 -0
- package/dist/core/engines/tinybench-engine.d.ts +18 -0
- package/dist/core/engines/tinybench-engine.d.ts.map +1 -0
- package/dist/core/engines/tinybench-engine.js +282 -0
- package/dist/core/engines/tinybench-engine.js.map +1 -0
- package/dist/core/error-manager.cjs +303 -0
- package/dist/core/error-manager.cjs.map +1 -0
- package/dist/core/error-manager.d.cts +77 -0
- package/dist/core/error-manager.d.cts.map +1 -0
- package/dist/core/error-manager.d.ts +77 -0
- package/dist/core/error-manager.d.ts.map +1 -0
- package/dist/core/error-manager.js +299 -0
- package/dist/core/error-manager.js.map +1 -0
- package/dist/core/loader.cjs +287 -0
- package/dist/core/loader.cjs.map +1 -0
- package/dist/core/loader.d.cts +55 -0
- package/dist/core/loader.d.cts.map +1 -0
- package/dist/core/loader.d.ts +55 -0
- package/dist/core/loader.d.ts.map +1 -0
- package/dist/core/loader.js +250 -0
- package/dist/core/loader.js.map +1 -0
- package/dist/core/stats-utils.cjs +99 -0
- package/dist/core/stats-utils.cjs.map +1 -0
- package/dist/core/stats-utils.d.cts +50 -0
- package/dist/core/stats-utils.d.cts.map +1 -0
- package/dist/core/stats-utils.d.ts +50 -0
- package/dist/core/stats-utils.d.ts.map +1 -0
- package/dist/core/stats-utils.js +94 -0
- package/dist/core/stats-utils.js.map +1 -0
- package/dist/index.cjs +64 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/progress/manager.cjs +325 -0
- package/dist/progress/manager.cjs.map +1 -0
- package/dist/progress/manager.d.cts +125 -0
- package/dist/progress/manager.d.cts.map +1 -0
- package/dist/progress/manager.d.ts +125 -0
- package/dist/progress/manager.d.ts.map +1 -0
- package/dist/progress/manager.js +321 -0
- package/dist/progress/manager.js.map +1 -0
- package/dist/reporters/csv.cjs +250 -0
- package/dist/reporters/csv.cjs.map +1 -0
- package/dist/reporters/csv.d.cts +92 -0
- package/dist/reporters/csv.d.cts.map +1 -0
- package/dist/reporters/csv.d.ts +92 -0
- package/dist/reporters/csv.d.ts.map +1 -0
- package/dist/reporters/csv.js +246 -0
- package/dist/reporters/csv.js.map +1 -0
- package/dist/reporters/human.cjs +516 -0
- package/dist/reporters/human.cjs.map +1 -0
- package/dist/reporters/human.d.cts +86 -0
- package/dist/reporters/human.d.cts.map +1 -0
- package/dist/reporters/human.d.ts +86 -0
- package/dist/reporters/human.d.ts.map +1 -0
- package/dist/reporters/human.js +509 -0
- package/dist/reporters/human.js.map +1 -0
- package/dist/reporters/index.cjs +17 -0
- package/dist/reporters/index.cjs.map +1 -0
- package/dist/reporters/index.d.cts +10 -0
- package/dist/reporters/index.d.cts.map +1 -0
- package/dist/reporters/index.d.ts +10 -0
- package/dist/reporters/index.d.ts.map +1 -0
- package/dist/reporters/index.js +10 -0
- package/dist/reporters/index.js.map +1 -0
- package/dist/reporters/json.cjs +215 -0
- package/dist/reporters/json.cjs.map +1 -0
- package/dist/reporters/json.d.cts +79 -0
- package/dist/reporters/json.d.cts.map +1 -0
- package/dist/reporters/json.d.ts +79 -0
- package/dist/reporters/json.d.ts.map +1 -0
- package/dist/reporters/json.js +211 -0
- package/dist/reporters/json.js.map +1 -0
- package/dist/reporters/registry.cjs +255 -0
- package/dist/reporters/registry.cjs.map +1 -0
- package/dist/reporters/registry.d.cts +155 -0
- package/dist/reporters/registry.d.cts.map +1 -0
- package/dist/reporters/registry.d.ts +155 -0
- package/dist/reporters/registry.d.ts.map +1 -0
- package/dist/reporters/registry.js +249 -0
- package/dist/reporters/registry.js.map +1 -0
- package/dist/reporters/simple.cjs +328 -0
- package/dist/reporters/simple.cjs.map +1 -0
- package/dist/reporters/simple.d.cts +51 -0
- package/dist/reporters/simple.d.cts.map +1 -0
- package/dist/reporters/simple.d.ts +51 -0
- package/dist/reporters/simple.d.ts.map +1 -0
- package/dist/reporters/simple.js +321 -0
- package/dist/reporters/simple.js.map +1 -0
- package/dist/schema/modestbench-config.schema.json +162 -0
- package/dist/storage/history.cjs +456 -0
- package/dist/storage/history.cjs.map +1 -0
- package/dist/storage/history.d.cts +99 -0
- package/dist/storage/history.d.cts.map +1 -0
- package/dist/storage/history.d.ts +99 -0
- package/dist/storage/history.d.ts.map +1 -0
- package/dist/storage/history.js +452 -0
- package/dist/storage/history.js.map +1 -0
- package/dist/types/cli.cjs +21 -0
- package/dist/types/cli.cjs.map +1 -0
- package/dist/types/cli.d.cts +296 -0
- package/dist/types/cli.d.cts.map +1 -0
- package/dist/types/cli.d.ts +296 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/cli.js +18 -0
- package/dist/types/cli.js.map +1 -0
- package/dist/types/core.cjs +14 -0
- package/dist/types/core.cjs.map +1 -0
- package/dist/types/core.d.cts +380 -0
- package/dist/types/core.d.cts.map +1 -0
- package/dist/types/core.d.ts +380 -0
- package/dist/types/core.d.ts.map +1 -0
- package/dist/types/core.js +13 -0
- package/dist/types/core.js.map +1 -0
- package/dist/types/index.cjs +27 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.d.cts +11 -0
- package/dist/types/index.d.cts.map +1 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +11 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/interfaces.cjs +10 -0
- package/dist/types/interfaces.cjs.map +1 -0
- package/dist/types/interfaces.d.cts +381 -0
- package/dist/types/interfaces.d.cts.map +1 -0
- package/dist/types/interfaces.d.ts +381 -0
- package/dist/types/interfaces.d.ts.map +1 -0
- package/dist/types/interfaces.js +9 -0
- package/dist/types/interfaces.js.map +1 -0
- package/dist/types/utility.cjs +92 -0
- package/dist/types/utility.cjs.map +1 -0
- package/dist/types/utility.d.cts +330 -0
- package/dist/types/utility.d.cts.map +1 -0
- package/dist/types/utility.d.ts +330 -0
- package/dist/types/utility.d.ts.map +1 -0
- package/dist/types/utility.js +78 -0
- package/dist/types/utility.js.map +1 -0
- package/package.json +211 -0
- package/src/bootstrap.ts +35 -0
- package/src/cli/commands/history.ts +569 -0
- package/src/cli/commands/init.ts +658 -0
- package/src/cli/commands/run.ts +346 -0
- package/src/cli/index.ts +642 -0
- package/src/config/manager.ts +387 -0
- package/src/config/schema.ts +188 -0
- package/src/constants.ts +21 -0
- package/src/core/benchmark-schema.ts +185 -0
- package/src/core/engine.ts +888 -0
- package/src/core/engines/accurate-engine.ts +408 -0
- package/src/core/engines/index.ts +16 -0
- package/src/core/engines/tinybench-engine.ts +335 -0
- package/src/core/error-manager.ts +372 -0
- package/src/core/loader.ts +324 -0
- package/src/core/stats-utils.ts +135 -0
- package/src/index.ts +46 -0
- package/src/progress/manager.ts +415 -0
- package/src/reporters/csv.ts +368 -0
- package/src/reporters/human.ts +707 -0
- package/src/reporters/index.ts +10 -0
- package/src/reporters/json.ts +302 -0
- package/src/reporters/registry.ts +349 -0
- package/src/reporters/simple.ts +459 -0
- package/src/storage/history.ts +600 -0
- package/src/types/cli.ts +312 -0
- package/src/types/core.ts +414 -0
- package/src/types/index.ts +18 -0
- package/src/types/interfaces.ts +451 -0
- package/src/types/utility.ts +446 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModestBench Reporters
|
|
3
|
+
*
|
|
4
|
+
* Export all available reporters and registry functionality.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { CsvReporter } from './csv.js';
|
|
8
|
+
export { HumanReporter } from './human.js';
|
|
9
|
+
export { JsonReporter } from './json.js';
|
|
10
|
+
export { SimpleReporter } from './simple.js';
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModestBench JSON Reporter
|
|
3
|
+
*
|
|
4
|
+
* Outputs benchmark results in structured JSON format. Suitable for machine
|
|
5
|
+
* processing, CI/CD integration, and data analysis.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { dirname } from 'node:path';
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
BenchmarkRun,
|
|
13
|
+
FileResult,
|
|
14
|
+
ProgressState,
|
|
15
|
+
SuiteResult,
|
|
16
|
+
TaskResult,
|
|
17
|
+
} from '../types/index.js';
|
|
18
|
+
|
|
19
|
+
import { BaseReporter } from './registry.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* JSON output structure for benchmark results
|
|
23
|
+
*/
|
|
24
|
+
interface JsonOutput {
|
|
25
|
+
/** ModestBench metadata */
|
|
26
|
+
readonly meta: {
|
|
27
|
+
readonly format: 'modestbench-json';
|
|
28
|
+
readonly timestamp: string;
|
|
29
|
+
readonly version: string;
|
|
30
|
+
};
|
|
31
|
+
/** Complete benchmark run data */
|
|
32
|
+
readonly run: BenchmarkRun;
|
|
33
|
+
/** Additional computed statistics */
|
|
34
|
+
statistics?: {
|
|
35
|
+
averageOpsPerSecond?: number;
|
|
36
|
+
fastestTask?: TaskResult;
|
|
37
|
+
slowestTask?: TaskResult;
|
|
38
|
+
totalIterations?: number;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* JSON reporter for structured output
|
|
44
|
+
*/
|
|
45
|
+
export class JsonReporter extends BaseReporter {
|
|
46
|
+
private currentRun?: BenchmarkRun;
|
|
47
|
+
|
|
48
|
+
private readonly includeMetadata: boolean;
|
|
49
|
+
|
|
50
|
+
private readonly includeStatistics: boolean;
|
|
51
|
+
|
|
52
|
+
private readonly outputPath?: string | undefined;
|
|
53
|
+
|
|
54
|
+
private readonly prettyPrint: boolean;
|
|
55
|
+
|
|
56
|
+
private readonly quiet: boolean;
|
|
57
|
+
|
|
58
|
+
private statistics: {
|
|
59
|
+
fastestTask?: TaskResult;
|
|
60
|
+
slowestTask?: TaskResult;
|
|
61
|
+
taskCount: number;
|
|
62
|
+
totalIterations: number;
|
|
63
|
+
totalOpsPerSecond: number;
|
|
64
|
+
} = {
|
|
65
|
+
taskCount: 0,
|
|
66
|
+
totalIterations: 0,
|
|
67
|
+
totalOpsPerSecond: 0,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
constructor(
|
|
71
|
+
options: {
|
|
72
|
+
includeMetadata?: boolean;
|
|
73
|
+
includeStatistics?: boolean;
|
|
74
|
+
outputPath?: string;
|
|
75
|
+
prettyPrint?: boolean;
|
|
76
|
+
quiet?: boolean;
|
|
77
|
+
verbose?: boolean;
|
|
78
|
+
} = {},
|
|
79
|
+
) {
|
|
80
|
+
super('json', options);
|
|
81
|
+
|
|
82
|
+
this.outputPath = options.outputPath;
|
|
83
|
+
this.prettyPrint = options.prettyPrint ?? true;
|
|
84
|
+
this.includeStatistics = options.includeStatistics ?? true;
|
|
85
|
+
this.includeMetadata = options.includeMetadata ?? true;
|
|
86
|
+
this.quiet = options.quiet ?? false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Check if statistics are included
|
|
91
|
+
*/
|
|
92
|
+
areStatisticsIncluded(): boolean {
|
|
93
|
+
return this.includeStatistics;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get the output path (if configured)
|
|
98
|
+
*/
|
|
99
|
+
getOutputPath(): string | undefined {
|
|
100
|
+
return this.outputPath;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Check if metadata is included
|
|
105
|
+
*/
|
|
106
|
+
isMetadataIncluded(): boolean {
|
|
107
|
+
return this.includeMetadata;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if pretty printing is enabled
|
|
112
|
+
*/
|
|
113
|
+
isPrettyPrintEnabled(): boolean {
|
|
114
|
+
return this.prettyPrint;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async onEnd(run: BenchmarkRun): Promise<void> {
|
|
118
|
+
const output = this.buildJsonOutput(run);
|
|
119
|
+
|
|
120
|
+
if (this.outputPath) {
|
|
121
|
+
await this.writeToFile(output);
|
|
122
|
+
} else {
|
|
123
|
+
this.writeToStdout(output);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
onError(error: Error): void {
|
|
128
|
+
// For JSON reporter, we'll include errors in the final output
|
|
129
|
+
// but we can also log to stderr for immediate feedback
|
|
130
|
+
console.error('JSON Reporter Error:', error.message);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
onFileEnd(_result: FileResult): void {
|
|
134
|
+
// No-op for JSON reporter
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
onFileStart(_file: string): void {
|
|
138
|
+
// No-op for JSON reporter
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
onProgress(_state: ProgressState): void {
|
|
142
|
+
// No-op for JSON reporter - we don't output progress in JSON format
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
onStart(run: BenchmarkRun): void {
|
|
146
|
+
this.currentRun = run;
|
|
147
|
+
this.resetStatistics();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
onSuiteEnd(_result: SuiteResult): void {
|
|
151
|
+
// No-op for JSON reporter
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
onSuiteStart(_suite: string): void {
|
|
155
|
+
// No-op for JSON reporter
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
onTaskResult(result: TaskResult): void {
|
|
159
|
+
if (!result.error) {
|
|
160
|
+
this.updateStatistics(result);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
onTaskStart(_task: string): void {
|
|
165
|
+
// No-op for JSON reporter
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Build the complete JSON output structure
|
|
170
|
+
*/
|
|
171
|
+
private buildJsonOutput(run: BenchmarkRun): JsonOutput {
|
|
172
|
+
const output: JsonOutput = {
|
|
173
|
+
meta: {
|
|
174
|
+
format: 'modestbench-json',
|
|
175
|
+
timestamp: new Date().toISOString(),
|
|
176
|
+
version: '0.1.0', // TODO: Get from package.json
|
|
177
|
+
},
|
|
178
|
+
run: this.includeMetadata ? run : this.sanitizeRun(run),
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
if (this.includeStatistics) {
|
|
182
|
+
const stats = {
|
|
183
|
+
averageOpsPerSecond:
|
|
184
|
+
this.statistics.taskCount > 0
|
|
185
|
+
? this.statistics.totalOpsPerSecond / this.statistics.taskCount
|
|
186
|
+
: 0,
|
|
187
|
+
totalIterations: this.statistics.totalIterations,
|
|
188
|
+
...(this.statistics.fastestTask && {
|
|
189
|
+
fastestTask: this.statistics.fastestTask,
|
|
190
|
+
}),
|
|
191
|
+
...(this.statistics.slowestTask && {
|
|
192
|
+
slowestTask: this.statistics.slowestTask,
|
|
193
|
+
}),
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
output.statistics = stats;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return output;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Reset statistics tracking
|
|
204
|
+
*/
|
|
205
|
+
private resetStatistics(): void {
|
|
206
|
+
this.statistics = {
|
|
207
|
+
taskCount: 0,
|
|
208
|
+
totalIterations: 0,
|
|
209
|
+
totalOpsPerSecond: 0,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Remove potentially sensitive metadata from run data
|
|
215
|
+
*/
|
|
216
|
+
private sanitizeRun(run: BenchmarkRun): BenchmarkRun {
|
|
217
|
+
let sanitized = {
|
|
218
|
+
...run,
|
|
219
|
+
environment: {
|
|
220
|
+
...run.environment,
|
|
221
|
+
env: {}, // Remove environment variables
|
|
222
|
+
hostname: 'redacted', // Remove hostname
|
|
223
|
+
},
|
|
224
|
+
} as BenchmarkRun;
|
|
225
|
+
|
|
226
|
+
if (run.git) {
|
|
227
|
+
sanitized = {
|
|
228
|
+
...sanitized,
|
|
229
|
+
git: {
|
|
230
|
+
...run.git,
|
|
231
|
+
author: 'redacted', // Remove author info
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return sanitized;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Update running statistics with a task result
|
|
241
|
+
*/
|
|
242
|
+
private updateStatistics(result: TaskResult): void {
|
|
243
|
+
this.statistics.totalIterations += result.iterations;
|
|
244
|
+
this.statistics.totalOpsPerSecond += result.opsPerSecond;
|
|
245
|
+
this.statistics.taskCount++;
|
|
246
|
+
|
|
247
|
+
// Track fastest task
|
|
248
|
+
if (
|
|
249
|
+
!this.statistics.fastestTask ||
|
|
250
|
+
result.mean < this.statistics.fastestTask.mean
|
|
251
|
+
) {
|
|
252
|
+
this.statistics.fastestTask = result;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Track slowest task
|
|
256
|
+
if (
|
|
257
|
+
!this.statistics.slowestTask ||
|
|
258
|
+
result.mean > this.statistics.slowestTask.mean
|
|
259
|
+
) {
|
|
260
|
+
this.statistics.slowestTask = result;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Write JSON output to file
|
|
266
|
+
*/
|
|
267
|
+
private async writeToFile(output: JsonOutput): Promise<void> {
|
|
268
|
+
if (!this.outputPath) {
|
|
269
|
+
throw new Error('Output path not specified');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
// Ensure directory exists
|
|
274
|
+
const dir = dirname(this.outputPath);
|
|
275
|
+
mkdirSync(dir, { recursive: true });
|
|
276
|
+
|
|
277
|
+
// Write JSON file
|
|
278
|
+
const jsonString = this.prettyPrint
|
|
279
|
+
? JSON.stringify(output, null, 2)
|
|
280
|
+
: JSON.stringify(output);
|
|
281
|
+
|
|
282
|
+
writeFileSync(this.outputPath, jsonString, 'utf8');
|
|
283
|
+
} catch (error) {
|
|
284
|
+
throw new Error(
|
|
285
|
+
`Failed to write JSON output to ${this.outputPath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Write JSON output to stdout
|
|
292
|
+
*/
|
|
293
|
+
private writeToStdout(output: JsonOutput): void {
|
|
294
|
+
// Always write to stdout when no output path is specified
|
|
295
|
+
// The quiet flag only affects progress messages (stderr), not data output
|
|
296
|
+
const jsonString = this.prettyPrint
|
|
297
|
+
? JSON.stringify(output, null, 2)
|
|
298
|
+
: JSON.stringify(output);
|
|
299
|
+
|
|
300
|
+
console.log(jsonString);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModestBench Reporter Registry
|
|
3
|
+
*
|
|
4
|
+
* Plugin-based system for managing benchmark output formatters. Supports
|
|
5
|
+
* registration, retrieval, and lifecycle management of reporters.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
BenchmarkRun,
|
|
10
|
+
FileResult,
|
|
11
|
+
ProgressState,
|
|
12
|
+
Reporter,
|
|
13
|
+
ReporterRegistry,
|
|
14
|
+
SuiteResult,
|
|
15
|
+
TaskResult,
|
|
16
|
+
} from '../types/index.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Base abstract reporter class providing common functionality
|
|
20
|
+
*/
|
|
21
|
+
export abstract class BaseReporter implements Reporter {
|
|
22
|
+
protected readonly name: string;
|
|
23
|
+
|
|
24
|
+
protected readonly options: Record<string, unknown>;
|
|
25
|
+
|
|
26
|
+
constructor(name: string, options: Record<string, unknown> = {}) {
|
|
27
|
+
this.name = name;
|
|
28
|
+
this.options = options;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get reporter name
|
|
33
|
+
*/
|
|
34
|
+
getName(): string {
|
|
35
|
+
return this.name;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get reporter options
|
|
40
|
+
*/
|
|
41
|
+
getOptions(): Record<string, unknown> {
|
|
42
|
+
return { ...this.options };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Called when benchmark run completes
|
|
47
|
+
*/
|
|
48
|
+
abstract onEnd(run: BenchmarkRun): Promise<void> | void;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Called when an error occurs
|
|
52
|
+
*/
|
|
53
|
+
abstract onError(error: Error): Promise<void> | void;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Called when a file completes
|
|
57
|
+
*/
|
|
58
|
+
abstract onFileEnd(result: FileResult): Promise<void> | void;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Called when a file starts execution
|
|
62
|
+
*/
|
|
63
|
+
abstract onFileStart(file: string): Promise<void> | void;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Called for progress updates
|
|
67
|
+
*/
|
|
68
|
+
abstract onProgress(state: ProgressState): Promise<void> | void;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Called when benchmark run starts
|
|
72
|
+
*/
|
|
73
|
+
abstract onStart(run: BenchmarkRun): Promise<void> | void;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Called when a suite completes
|
|
77
|
+
*/
|
|
78
|
+
abstract onSuiteEnd(result: SuiteResult): Promise<void> | void;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Called when a suite starts execution
|
|
82
|
+
*/
|
|
83
|
+
abstract onSuiteStart(suite: string): Promise<void> | void;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Called when a task completes
|
|
87
|
+
*/
|
|
88
|
+
abstract onTaskResult(result: TaskResult): Promise<void> | void;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Called when a task starts execution
|
|
92
|
+
*/
|
|
93
|
+
abstract onTaskStart(task: string): Promise<void> | void;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Utility method to format duration in human-readable format
|
|
97
|
+
*/
|
|
98
|
+
protected formatDuration(nanoseconds: number): string {
|
|
99
|
+
if (nanoseconds < 1000) {
|
|
100
|
+
return `${nanoseconds.toFixed(2)}ns`;
|
|
101
|
+
} else if (nanoseconds < 1000000) {
|
|
102
|
+
return `${(nanoseconds / 1000).toFixed(2)}μs`;
|
|
103
|
+
} else if (nanoseconds < 1000000000) {
|
|
104
|
+
return `${(nanoseconds / 1000000).toFixed(2)}ms`;
|
|
105
|
+
} else {
|
|
106
|
+
return `${(nanoseconds / 1000000000).toFixed(2)}s`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Utility method to format operations per second
|
|
112
|
+
*/
|
|
113
|
+
protected formatOpsPerSecond(opsPerSecond: number): string {
|
|
114
|
+
if (opsPerSecond < 1000) {
|
|
115
|
+
return `${opsPerSecond.toFixed(2)} ops/sec`;
|
|
116
|
+
} else if (opsPerSecond < 1000000) {
|
|
117
|
+
return `${(opsPerSecond / 1000).toFixed(2)}K ops/sec`;
|
|
118
|
+
} else if (opsPerSecond < 1000000000) {
|
|
119
|
+
return `${(opsPerSecond / 1000000).toFixed(2)}M ops/sec`;
|
|
120
|
+
} else {
|
|
121
|
+
return `${(opsPerSecond / 1000000000).toFixed(2)}B ops/sec`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Utility method to format percentage
|
|
127
|
+
*/
|
|
128
|
+
protected formatPercentage(value: number): string {
|
|
129
|
+
return `${value.toFixed(2)}%`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Utility method to safely handle async operations
|
|
134
|
+
*/
|
|
135
|
+
protected async safeAsync<T>(operation: () => Promise<T>): Promise<null | T> {
|
|
136
|
+
try {
|
|
137
|
+
return await operation();
|
|
138
|
+
} catch (error) {
|
|
139
|
+
await this.onError(
|
|
140
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
141
|
+
);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Composite reporter that broadcasts events to multiple reporters
|
|
149
|
+
*/
|
|
150
|
+
export class CompositeReporter extends BaseReporter {
|
|
151
|
+
private readonly reporters: Reporter[];
|
|
152
|
+
|
|
153
|
+
constructor(reporters: Reporter[]) {
|
|
154
|
+
super('composite', {});
|
|
155
|
+
this.reporters = [...reporters];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Add a reporter to the composite
|
|
160
|
+
*/
|
|
161
|
+
addReporter(reporter: Reporter): void {
|
|
162
|
+
this.reporters.push(reporter);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get all reporters in the composite
|
|
167
|
+
*/
|
|
168
|
+
getReporters(): Reporter[] {
|
|
169
|
+
return [...this.reporters];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async onEnd(run: BenchmarkRun): Promise<void> {
|
|
173
|
+
await this.broadcastAsync('onEnd', run);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async onError(error: Error): Promise<void> {
|
|
177
|
+
await this.broadcastAsync('onError', error);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async onFileEnd(result: FileResult): Promise<void> {
|
|
181
|
+
await this.broadcastAsync('onFileEnd', result);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async onFileStart(file: string): Promise<void> {
|
|
185
|
+
await this.broadcastAsync('onFileStart', file);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async onProgress(state: ProgressState): Promise<void> {
|
|
189
|
+
await this.broadcastAsync('onProgress', state);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async onStart(run: BenchmarkRun): Promise<void> {
|
|
193
|
+
await this.broadcastAsync('onStart', run);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async onSuiteEnd(result: SuiteResult): Promise<void> {
|
|
197
|
+
await this.broadcastAsync('onSuiteEnd', result);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async onSuiteStart(suite: string): Promise<void> {
|
|
201
|
+
await this.broadcastAsync('onSuiteStart', suite);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async onTaskResult(result: TaskResult): Promise<void> {
|
|
205
|
+
await this.broadcastAsync('onTaskResult', result);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async onTaskStart(task: string): Promise<void> {
|
|
209
|
+
await this.broadcastAsync('onTaskStart', task);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Remove a reporter from the composite
|
|
214
|
+
*/
|
|
215
|
+
removeReporter(reporter: Reporter): boolean {
|
|
216
|
+
const index = this.reporters.indexOf(reporter);
|
|
217
|
+
if (index >= 0) {
|
|
218
|
+
this.reporters.splice(index, 1);
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Broadcast an event to all reporters with error handling
|
|
226
|
+
*/
|
|
227
|
+
private async broadcastAsync(
|
|
228
|
+
method: keyof Reporter,
|
|
229
|
+
|
|
230
|
+
...args: any[]
|
|
231
|
+
): Promise<void> {
|
|
232
|
+
const promises = this.reporters.map(async (reporter) => {
|
|
233
|
+
try {
|
|
234
|
+
const reporterMethod = reporter[method];
|
|
235
|
+
if (typeof reporterMethod === 'function') {
|
|
236
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
|
237
|
+
const result = (reporterMethod as any)(...args);
|
|
238
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
239
|
+
if (result && typeof result.then === 'function') {
|
|
240
|
+
await result;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
} catch (error) {
|
|
244
|
+
// Handle reporter-specific errors without affecting others
|
|
245
|
+
console.error(
|
|
246
|
+
`Reporter error in ${reporter.constructor.name}.${method}:`,
|
|
247
|
+
error,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
await Promise.all(promises);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Reporter registry implementation for managing multiple reporters
|
|
258
|
+
*/
|
|
259
|
+
export class ModestBenchReporterRegistry implements ReporterRegistry {
|
|
260
|
+
private readonly reporters: Map<string, Reporter> = new Map();
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Clear all registered reporters
|
|
264
|
+
*/
|
|
265
|
+
clear(): void {
|
|
266
|
+
this.reporters.clear();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get a reporter by name
|
|
271
|
+
*/
|
|
272
|
+
get(name: string): Reporter | undefined {
|
|
273
|
+
return this.reporters.get(name);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get all registered reporters
|
|
278
|
+
*/
|
|
279
|
+
getAll(): Record<string, Reporter> {
|
|
280
|
+
const result: Record<string, Reporter> = {};
|
|
281
|
+
this.reporters.forEach((reporter, name) => {
|
|
282
|
+
result[name] = reporter;
|
|
283
|
+
});
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Get multiple reporters by names
|
|
289
|
+
*/
|
|
290
|
+
getByNames(names: string[]): Reporter[] {
|
|
291
|
+
const result: Reporter[] = [];
|
|
292
|
+
const missing: string[] = [];
|
|
293
|
+
|
|
294
|
+
for (const name of names) {
|
|
295
|
+
const reporter = this.reporters.get(name);
|
|
296
|
+
if (reporter) {
|
|
297
|
+
result.push(reporter);
|
|
298
|
+
} else {
|
|
299
|
+
missing.push(name);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (missing.length > 0) {
|
|
304
|
+
throw new Error(
|
|
305
|
+
`Unknown reporters: ${missing.join(', ')}. Available: ${Array.from(this.reporters.keys()).join(', ')}`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Get list of registered reporter names
|
|
314
|
+
*/
|
|
315
|
+
getNames(): string[] {
|
|
316
|
+
return Array.from(this.reporters.keys());
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Check if a reporter is registered
|
|
321
|
+
*/
|
|
322
|
+
has(name: string): boolean {
|
|
323
|
+
return this.reporters.has(name);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Register a reporter with a unique name
|
|
328
|
+
*/
|
|
329
|
+
register(name: string, reporter: Reporter): void {
|
|
330
|
+
if (this.reporters.has(name)) {
|
|
331
|
+
throw new Error(`Reporter with name "${name}" is already registered`);
|
|
332
|
+
}
|
|
333
|
+
this.reporters.set(name, reporter);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Get count of registered reporters
|
|
338
|
+
*/
|
|
339
|
+
size(): number {
|
|
340
|
+
return this.reporters.size;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Unregister a reporter
|
|
345
|
+
*/
|
|
346
|
+
unregister(name: string): boolean {
|
|
347
|
+
return this.reporters.delete(name);
|
|
348
|
+
}
|
|
349
|
+
}
|