@mui/internal-benchmark 0.0.3-canary.1 → 0.0.3-canary.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/ciReport.d.mts +22 -1
- package/ciReport.mjs +13 -12
- package/package.json +2 -2
- package/reporter.d.mts +2 -0
- package/reporter.mjs +19 -3
- package/vitest.d.mts +8 -1
- package/vitest.mjs +3 -1
package/README.md
CHANGED
|
@@ -114,7 +114,8 @@ vitest run
|
|
|
114
114
|
|
|
115
115
|
`createBenchmarkVitestConfig` accepts:
|
|
116
116
|
|
|
117
|
-
- `outputPath` — path for JSON results (default: `benchmarks/results.json`)
|
|
117
|
+
- `outputPath` — path for JSON results (default: `benchmarks/results.json`). Also settable via `BENCHMARK_OUTPUT_PATH`.
|
|
118
|
+
- `baselinePath` — path to a prior results JSON file to inline as the comparison base (see [Baseline comparisons](#baseline-comparisons)). Also settable via `BENCHMARK_BASELINE_PATH`.
|
|
118
119
|
- `launchArgs` — additional browser launch arguments
|
|
119
120
|
|
|
120
121
|
To override standard Vitest options (e.g. `include`, `testTimeout`, `headless`), use `mergeConfig`:
|
|
@@ -130,6 +131,19 @@ export default mergeConfig(createBenchmarkVitestConfig(), {
|
|
|
130
131
|
});
|
|
131
132
|
```
|
|
132
133
|
|
|
134
|
+
### Baseline comparisons
|
|
135
|
+
|
|
136
|
+
Benchmark runs are noisy across machines. To get a clean comparison in a PR, run the baseline benchmark in the _same_ CI job as the head: the results are inlined into the head upload as a `base` field, and the dashboard / PR comment render the comparison without fetching a separate base artifact from S3.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# in a PR CI job
|
|
140
|
+
git worktree add /tmp/base $BASE_SHA
|
|
141
|
+
(cd /tmp/base && pnpm install && BENCHMARK_OUTPUT_PATH=/tmp/base-bench.json pnpm test:bench)
|
|
142
|
+
BENCHMARK_BASELINE_PATH=/tmp/base-bench.json pnpm test:bench # head run, inlines base
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The feature is opt-in — without `BENCHMARK_BASELINE_PATH` (or the `baselinePath` config option), the dashboard falls back to fetching the base from S3 by merge-base SHA as before.
|
|
146
|
+
|
|
133
147
|
## API
|
|
134
148
|
|
|
135
149
|
- `benchmark` — define a benchmark test case
|
package/ciReport.d.mts
CHANGED
|
@@ -58,6 +58,16 @@ declare const benchmarkReportSchema: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
|
58
58
|
outliers: z.ZodNumber;
|
|
59
59
|
}, z.core.$strip>>;
|
|
60
60
|
}, z.core.$strip>>;
|
|
61
|
+
declare const benchmarkBaseUploadSchema: z.ZodObject<{
|
|
62
|
+
version: z.ZodLiteral<number>;
|
|
63
|
+
timestamp: z.ZodNumber;
|
|
64
|
+
commitSha: z.ZodString;
|
|
65
|
+
repo: z.ZodString;
|
|
66
|
+
reportType: z.ZodLiteral<string>;
|
|
67
|
+
prNumber: z.ZodOptional<z.ZodNumber>;
|
|
68
|
+
branch: z.ZodString;
|
|
69
|
+
report: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
70
|
+
}, z.core.$strip>;
|
|
61
71
|
export declare const benchmarkUploadSchema: z.ZodObject<{
|
|
62
72
|
version: z.ZodLiteral<number>;
|
|
63
73
|
timestamp: z.ZodNumber;
|
|
@@ -67,17 +77,28 @@ export declare const benchmarkUploadSchema: z.ZodObject<{
|
|
|
67
77
|
prNumber: z.ZodOptional<z.ZodNumber>;
|
|
68
78
|
branch: z.ZodString;
|
|
69
79
|
report: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
80
|
+
base: z.ZodOptional<z.ZodObject<{
|
|
81
|
+
version: z.ZodLiteral<number>;
|
|
82
|
+
timestamp: z.ZodNumber;
|
|
83
|
+
commitSha: z.ZodString;
|
|
84
|
+
repo: z.ZodString;
|
|
85
|
+
reportType: z.ZodLiteral<string>;
|
|
86
|
+
prNumber: z.ZodOptional<z.ZodNumber>;
|
|
87
|
+
branch: z.ZodString;
|
|
88
|
+
report: z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
89
|
+
}, z.core.$strip>>;
|
|
70
90
|
}, z.core.$strip>;
|
|
71
91
|
export type RenderStats = z.infer<typeof renderStatsSchema>;
|
|
72
92
|
export type MetricStats = z.infer<typeof metricStatsSchema>;
|
|
73
93
|
export type BenchmarkReportEntry = z.infer<typeof benchmarkReportEntrySchema>;
|
|
74
94
|
export type BenchmarkReport = z.infer<typeof benchmarkReportSchema>;
|
|
95
|
+
export type BenchmarkBaseUpload = z.infer<typeof benchmarkBaseUploadSchema>;
|
|
75
96
|
export type BenchmarkUpload = z.infer<typeof benchmarkUploadSchema>;
|
|
76
97
|
export declare function getCiMetadata(): Promise<{
|
|
77
98
|
timestamp: number;
|
|
78
99
|
repo: string;
|
|
79
|
-
branch: string;
|
|
80
100
|
prNumber: number | undefined;
|
|
101
|
+
branch: string;
|
|
81
102
|
commitSha: string;
|
|
82
103
|
}>;
|
|
83
104
|
export {};
|
package/ciReport.mjs
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { execa } from 'execa';
|
|
2
2
|
import { z } from 'zod/v4';
|
|
3
3
|
import envCi from 'env-ci';
|
|
4
|
-
async function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} catch {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
4
|
+
async function getGitInfo() {
|
|
5
|
+
const [commit, branch] = await Promise.all([execa('git', ['rev-parse', 'HEAD']), execa('git', ['branch', '--show-current'])]);
|
|
6
|
+
return {
|
|
7
|
+
commitSha: commit.stdout,
|
|
8
|
+
branch: branch.stdout
|
|
9
|
+
};
|
|
13
10
|
}
|
|
14
11
|
|
|
15
12
|
/**
|
|
@@ -48,14 +45,18 @@ const benchmarkReportEntrySchema = z.object({
|
|
|
48
45
|
metrics: z.record(z.string(), metricStatsSchema)
|
|
49
46
|
});
|
|
50
47
|
const benchmarkReportSchema = z.record(z.string(), benchmarkReportEntrySchema);
|
|
51
|
-
|
|
48
|
+
const benchmarkBaseUploadSchema = ciReportUploadSchema('benchmark', 1, benchmarkReportSchema);
|
|
49
|
+
export const benchmarkUploadSchema = benchmarkBaseUploadSchema.extend({
|
|
50
|
+
base: benchmarkBaseUploadSchema.optional()
|
|
51
|
+
});
|
|
52
52
|
export async function getCiMetadata() {
|
|
53
53
|
const ciInfo = envCi();
|
|
54
|
+
const gitInfo = await getGitInfo();
|
|
54
55
|
return {
|
|
55
56
|
timestamp: Date.now(),
|
|
56
57
|
repo: ciInfo.slug ?? '',
|
|
57
|
-
branch: ciInfo.isPr ? ciInfo.prBranch ?? '' : ciInfo.branch ?? '',
|
|
58
58
|
prNumber: ciInfo.pr ? Number(ciInfo.pr) : undefined,
|
|
59
|
-
|
|
59
|
+
branch: gitInfo.branch || process.env.BENCHMARK_BRANCH || (ciInfo.isPr ? ciInfo.prBranch : ciInfo.branch) || '',
|
|
60
|
+
commitSha: gitInfo.commitSha
|
|
60
61
|
};
|
|
61
62
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mui/internal-benchmark",
|
|
3
|
-
"version": "0.0.3-canary.
|
|
3
|
+
"version": "0.0.3-canary.3",
|
|
4
4
|
"author": "MUI Team",
|
|
5
5
|
"description": "Benchmark utilities for MUI projects. Internal package.",
|
|
6
6
|
"repository": {
|
|
@@ -68,5 +68,5 @@
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
|
-
"gitSha": "
|
|
71
|
+
"gitSha": "b5ecb08ed447855ed858e123d924a08949b6ad6d"
|
|
72
72
|
}
|
package/reporter.d.mts
CHANGED
|
@@ -6,11 +6,13 @@ declare function generateReportFromIterations(iterations: IterationData[]): Benc
|
|
|
6
6
|
export interface BenchmarkReporterOptions {
|
|
7
7
|
outputPath?: string;
|
|
8
8
|
upload?: boolean;
|
|
9
|
+
baselinePath?: string;
|
|
9
10
|
}
|
|
10
11
|
declare class BenchmarkReporter implements Reporter {
|
|
11
12
|
private benchmarks;
|
|
12
13
|
private outputPath;
|
|
13
14
|
private upload;
|
|
15
|
+
private baselinePath;
|
|
14
16
|
private hasFailures;
|
|
15
17
|
constructor(options?: BenchmarkReporterOptions);
|
|
16
18
|
onTestCaseResult(testCase: TestCase): void;
|
package/reporter.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
2
|
import * as fs from 'node:fs/promises';
|
|
3
|
-
import { getCiMetadata } from "./ciReport.mjs";
|
|
3
|
+
import { benchmarkUploadSchema, getCiMetadata } from "./ciReport.mjs";
|
|
4
4
|
import { calculateMean, calculateStdDev, quantile, isOutlier } from "./stats.mjs";
|
|
5
5
|
import { dim, red, green, yellow, cyan, printTable, fileUrl } from "./format.mjs";
|
|
6
6
|
import { uploadCiReport } from "./upload.mjs";
|
|
@@ -191,12 +191,24 @@ function printMetricsTable(name, metrics, iterationCount) {
|
|
|
191
191
|
width: 4
|
|
192
192
|
}], rows, dim(`${iterationCount} iterations`), `${name} — Metrics`);
|
|
193
193
|
}
|
|
194
|
+
async function loadBaselineReport(baselinePath) {
|
|
195
|
+
const raw = await fs.readFile(baselinePath, 'utf8');
|
|
196
|
+
const parsed = benchmarkUploadSchema.parse(JSON.parse(raw));
|
|
197
|
+
// Strip any nested `.base` field to keep the inlined data single-level.
|
|
198
|
+
const {
|
|
199
|
+
base,
|
|
200
|
+
...single
|
|
201
|
+
} = parsed;
|
|
202
|
+
void base;
|
|
203
|
+
return single;
|
|
204
|
+
}
|
|
194
205
|
class BenchmarkReporter {
|
|
195
206
|
benchmarks = {};
|
|
196
207
|
hasFailures = false;
|
|
197
208
|
constructor(options) {
|
|
198
|
-
this.outputPath = options?.outputPath ?? path.resolve(process.cwd(), 'benchmarks', 'results.json');
|
|
209
|
+
this.outputPath = options?.outputPath ?? process.env.BENCHMARK_OUTPUT_PATH ?? path.resolve(process.cwd(), 'benchmarks', 'results.json');
|
|
199
210
|
this.upload = options?.upload ?? process.env.BENCHMARK_UPLOAD === 'true';
|
|
211
|
+
this.baselinePath = options?.baselinePath ?? process.env.BENCHMARK_BASELINE_PATH;
|
|
200
212
|
}
|
|
201
213
|
onTestCaseResult(testCase) {
|
|
202
214
|
if (testCase.result().state === 'failed') {
|
|
@@ -224,11 +236,15 @@ class BenchmarkReporter {
|
|
|
224
236
|
// eslint-disable-next-line no-console
|
|
225
237
|
console.log(` ${name}: ${result.totalDuration.toFixed(2)}ms ${dim(`(${result.renders.length} renders, ${result.iterations} iterations)`)}`);
|
|
226
238
|
}
|
|
239
|
+
const baseline = this.baselinePath ? await loadBaselineReport(this.baselinePath) : undefined;
|
|
227
240
|
const results = {
|
|
228
241
|
version: 1,
|
|
229
242
|
reportType: 'benchmark',
|
|
230
243
|
...(await getCiMetadata()),
|
|
231
|
-
report: this.benchmarks
|
|
244
|
+
report: this.benchmarks,
|
|
245
|
+
...(baseline ? {
|
|
246
|
+
base: baseline
|
|
247
|
+
} : {})
|
|
232
248
|
};
|
|
233
249
|
const outputDir = path.dirname(this.outputPath);
|
|
234
250
|
await fs.mkdir(outputDir, {
|
package/vitest.d.mts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import { ViteUserConfig } from 'vitest/config';
|
|
1
|
+
import { type ViteUserConfig } from 'vitest/config';
|
|
2
2
|
export interface CreateBenchmarkVitestConfigOptions {
|
|
3
3
|
/**
|
|
4
4
|
* Path to save benchmark results JSON file. If not provided, results will not be saved to disk.
|
|
5
5
|
*/
|
|
6
6
|
outputPath?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Path to a prior benchmark results JSON file. When set, its contents are
|
|
9
|
+
* inlined as the `base` field of the head upload so the dashboard and PR
|
|
10
|
+
* comment can render a comparison without fetching a separate base
|
|
11
|
+
* artifact from S3. Also settable via the `BENCHMARK_BASELINE_PATH` env var.
|
|
12
|
+
*/
|
|
13
|
+
baselinePath?: string;
|
|
7
14
|
/**
|
|
8
15
|
* Additional Chromium launch arguments.
|
|
9
16
|
*/
|
package/vitest.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { playwright } from '@vitest/browser-playwright';
|
|
|
3
3
|
export function createBenchmarkVitestConfig(options) {
|
|
4
4
|
const {
|
|
5
5
|
outputPath,
|
|
6
|
+
baselinePath,
|
|
6
7
|
launchArgs = []
|
|
7
8
|
} = options ?? {};
|
|
8
9
|
return {
|
|
@@ -43,7 +44,8 @@ export function createBenchmarkVitestConfig(options) {
|
|
|
43
44
|
},
|
|
44
45
|
fileParallelism: false,
|
|
45
46
|
reporters: ['default', ['@mui/internal-benchmark/reporter', {
|
|
46
|
-
outputPath
|
|
47
|
+
outputPath,
|
|
48
|
+
baselinePath
|
|
47
49
|
}]],
|
|
48
50
|
include: ['**/*.bench.tsx']
|
|
49
51
|
}
|