@mui/internal-benchmark 0.0.3-canary.0 → 0.0.3-canary.2

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 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 getCommitSha() {
5
- try {
6
- const {
7
- stdout
8
- } = await execa('git', ['rev-parse', 'HEAD']);
9
- return stdout.trim();
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
- export const benchmarkUploadSchema = ciReportUploadSchema('benchmark', 1, benchmarkReportSchema);
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
- commitSha: ciInfo.commit ?? (await getCommitSha()) ?? ''
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.0",
3
+ "version": "0.0.3-canary.2",
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": "a979a4f067e608130789df8bf32aaf3e9708b7ae"
71
+ "gitSha": "2e4759d55d99ba6104718b973ed93e965326be98"
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/syncPrComment.mjs CHANGED
@@ -1,4 +1,4 @@
1
- const DEFAULT_API_URL = 'https://code-infra-dashboard.onrender.com';
1
+ const DEFAULT_API_URL = 'https://frontend-public.mui.com';
2
2
  /**
3
3
  * Syncs a PR comment via the dashboard API.
4
4
  */
package/upload.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { benchmarkUploadSchema } from "./ciReport.mjs";
2
2
  async function uploadCiReport(report, options) {
3
3
  benchmarkUploadSchema.parse(report);
4
- const apiUrl = options?.apiUrl ?? process.env.CI_REPORT_API_URL ?? 'https://code-infra-dashboard.onrender.com';
4
+ const apiUrl = options?.apiUrl ?? process.env.CI_REPORT_API_URL ?? 'https://frontend-public.mui.com';
5
5
  const url = new URL('/api/ci-reports/upload', apiUrl);
6
6
  const oidcToken = process.env.CIRCLE_OIDC_TOKEN_V2;
7
7
  if (!oidcToken) {
package/vitest.d.mts CHANGED
@@ -4,6 +4,13 @@ export interface CreateBenchmarkVitestConfigOptions {
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
  }