Skip to content

Commit fdb8f5a

Browse files
authored
fixup! test(@angular/build): add library builder scaling benchmark suite
Reported benchmark results in csv file or json.
1 parent a39f001 commit fdb8f5a

4 files changed

Lines changed: 66 additions & 12 deletions

File tree

‎scripts/benchmark.mts‎

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,17 @@ function parseIntList(raw: string | number | undefined): number[] | undefined {
4949
.map((s) => Number(s.trim()));
5050
}
5151

52-
async function runLibraryBuilderSubsystem(options: {
53-
layout?: string;
54-
count?: string;
55-
depth?: string;
56-
iterations?: string | number;
57-
json?: boolean;
58-
}): Promise<number> {
52+
async function runLibraryBuilderSubsystem(
53+
options: {
54+
layout?: string;
55+
count?: string;
56+
depth?: string;
57+
iterations?: string | number;
58+
json?: boolean;
59+
csv?: string | boolean;
60+
},
61+
cwd: string,
62+
): Promise<number> {
5963
if (options.layout !== undefined && options.layout !== 'flat' && options.layout !== 'deep') {
6064
// eslint-disable-next-line no-console
6165
console.error('Error: --layout must be "flat" or "deep", got "' + options.layout + '".');
@@ -97,12 +101,20 @@ async function runLibraryBuilderSubsystem(options: {
97101
return 1;
98102
}
99103

104+
if (options.csv !== undefined && (typeof options.csv !== 'string' || options.csv.length === 0)) {
105+
// eslint-disable-next-line no-console
106+
console.error('Error: --csv requires a file path, got "' + options.csv + '".');
107+
108+
return 1;
109+
}
110+
100111
const cliOptions: LibraryBuilderBenchmarkOptions = {
101112
layout: options.layout as LibraryBuilderBenchmarkOptions['layout'],
102113
count,
103114
depth,
104115
iterations,
105116
json: Boolean(options.json),
117+
csv: options.csv !== undefined ? path.resolve(cwd, options.csv as string) : undefined,
106118
};
107119

108120
const { exitCode } = await runLibraryBuilderBenchmarks(cliOptions);
@@ -167,7 +179,8 @@ Options (library-builder):
167179
that would otherwise be derived from --count (the smallest
168180
depth with 2^depth >= each target count).
169181
--iterations=<n> Measured iterations (default: 3)
170-
--json Output raw measurements as JSON
182+
--json Output raw measurements as JSON to stdout
183+
--csv=<path> Write raw per-iteration measurements as CSV to <path>
171184
172185
--help Show this help message
173186
`);
@@ -176,7 +189,7 @@ Options (library-builder):
176189
}
177190

178191
if (targetSubsystem === 'library-builder') {
179-
return runLibraryBuilderSubsystem(options);
192+
return runLibraryBuilderSubsystem(options, _cwd);
180193
}
181194

182195
if (targetSubsystem !== 'i18n') {

‎scripts/benchmarks/library-builder/README.md‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ pnpm admin benchmark library-builder [options]
9494
| `--count=<n,n,...>` | Entry-point count targets, drives both layouts | `10,20,50,100,200,300,400,500,1000,2000` |
9595
| `--depth=<n,n,...>` | **`layout=deep` only.** Explicit tree depths, overriding the depths otherwise derived from `--count` | derived from `--count` |
9696
| `--iterations=<n>` | Measured iterations per size | `3` |
97-
| `--json` | Output raw measurements as JSON | N/A |
97+
| `--json` | Output raw per-iteration measurements as JSON to stdout | N/A |
98+
| `--csv=<path>` | Write raw per-iteration measurements as CSV to `<path>` | N/A (opt-in only) |
9899

99100
> `--count` and `--depth` are not independent sweep axes for `layout=deep`: `--count` is translated
100101
> into depths (smallest `depth` with `2^depth >= n` per target, de-duplicated); `--depth` only
@@ -167,5 +168,10 @@ All 60 builds (10 sizes x 3 iterations x 2 layouts) completed with `status: "ok"
167168
- Dependencies between entry points.
168169
- Shared stylesheet tokens imported by multiple components.
169170
- Watch mode.
170-
- Peak memory (`ru_maxrss`) and CPU time (`ru_utime` + `ru_stime`) metrics per build, in addition to wall-clock build time.
171+
- Peak memory and CPU time metrics per build, in addition to wall-clock build time. Note:
172+
`child.resourceUsage()` does **not** exist on Node's `ChildProcess` (only `process.resourceUsage()`
173+
exists, and it reports the calling process's own usage, not a child's) -- the real path is
174+
polling `/proc/<pid>/status` (`VmHWM`) and `/proc/<pid>/stat` (`utime`+`stime`) on a timer while
175+
the build subprocess is alive, which is Linux-only and requires moving `buildOnce` off
176+
`spawnSync` onto `spawn()` so a polling timer can interleave with the child's lifetime.
171177
- Library "taste" variants beyond plain flat/deep, e.g. flat/deep with inter-entry-point dependencies or shared SCSS tokens (currently out of scope, listed above).

‎scripts/benchmarks/library-builder/index.mts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ import path from 'node:path';
1515
import { fileURLToPath } from 'node:url';
1616
import { type FixtureOptions, type Layout, generateFixture } from './fixtures.mts';
1717
import { type BuildMeasurement, buildOnce, checkBuilderIsBuilt, median } from './harness.mts';
18+
import { formatCsvReport, formatJsonReport } from './reporters.mts';
1819

1920
export interface LibraryBuilderBenchmarkOptions {
2021
layout?: Layout;
2122
count?: number[];
2223
depth?: number[];
2324
iterations?: number;
2425
json?: boolean;
26+
csv?: string;
2527
}
2628

2729
const repoRoot = fileURLToPath(new URL('../../../', import.meta.url));
@@ -125,7 +127,15 @@ export async function runLibraryBuilderBenchmarks(
125127
const anyFailed = measurements.some((m) => m.status === 'fail');
126128
if (options.json) {
127129
// eslint-disable-next-line no-console
128-
console.log(JSON.stringify(measurements, null, 2));
130+
console.log(formatJsonReport(measurements));
131+
}
132+
133+
if (options.csv) {
134+
fs.writeFileSync(options.csv, formatCsvReport(measurements));
135+
if (!options.json) {
136+
// eslint-disable-next-line no-console
137+
console.log('CSV written to: ' + options.csv);
138+
}
129139
}
130140

131141
return { measurements, exitCode: anyFailed ? 1 : 0 };
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
// Pure output formatting for the library-builder benchmark's measurements. No console/file I/O
10+
// here -- `index.mts` owns deciding when and where to print or write what this module formats.
11+
12+
import type { BuildMeasurement } from './harness.mts';
13+
14+
export function formatJsonReport(measurements: BuildMeasurement[]): string {
15+
return JSON.stringify(measurements, null, 2);
16+
}
17+
18+
export function formatCsvReport(measurements: BuildMeasurement[]): string {
19+
const header = 'layout,entryPoints,iteration,durationMs,status';
20+
const rows = measurements.map((m) =>
21+
[m.layout, m.entryPoints, m.iteration, m.durationMs.toFixed(2), m.status].join(','),
22+
);
23+
24+
return [header, ...rows].join('\n') + '\n';
25+
}

0 commit comments

Comments
 (0)