mirror of
https://github.com/pnpm/pnpm.git
synced 2026-05-24 08:35:19 -04:00
ci(integrated-benchmark): scenarios without lockfiles (#11838)
* ci(benchmark): run all six integrated-benchmark scenarios Wires `clean-install`, `full-resolution`, `peek`, and `gvs-warm` into `pacquet-integrated-benchmark.yml` so per-PR runs cover the same scenario set the manual `benchmark.yml` workflow already exercises via `benchmarks/bench.sh`. Requested for #11837, where the perf delta affects the resolution-bound scenarios (`firstInstall`, `withWarmCache`, `withWarmModules`, `updatedDependencies`) that the prior two-scenario set did not measure. Each scenario gets its own step with a 10 min hyperfine timeout (same rationale as the existing steps) and writes per-scenario report copies that the summary step concatenates into `SUMMARY.md`. * ci(benchmark): drop peek and gvs-warm scenarios Keep only the two new no-lockfile scenarios (`clean-install`, `full-resolution`) on top of the existing `frozen-lockfile` and `frozen-lockfile-hot-cache`. #11837's perf change is in the fresh-lockfile install path, which only runs when resolution runs — i.e., exactly the no-lockfile scenarios. `peek` mutates an existing lockfile and `gvs-warm` is a frozen-lockfile variant; neither exercises the affected path, and including them only costs per-PR CI wall time. * fix(bench): pin packages: ['.'] in synthesized pnpm-workspace.yaml The integrated-benchmark clones each pacquet revision's source tree into `<bench_dir>/pacquet/`, which on the pnpm/pnpm monorepo includes upstream test fixtures like `workspace/project-manifest-reader/__fixtures__/invalid-package-json/package.json` — intentionally malformed JSON used to exercise pnpm's manifest reader. Without a `packages:` field, both pnpm's `findPackages.ts:28` and pacquet's `crates/workspace/src/projects.rs:128` default to `[".", "**"]`, so the fresh-resolve install path's `find_workspace_projects` walk descends into the cloned source tree and trips on the bad fixture: Error: pacquet_package_manifest::serialization_error × installing dependencies ╰─▶ expected `,` or `}` at line 3 column 3 The walk only runs on the fresh-lockfile branch (`install.rs:628-630`), which is why frozen-lockfile and frozen-lockfile-hot-cache stay green while clean-install and full-resolution fail every time. Pin `packages: ['.']` in the synthesized manifest so enumeration stays at the workspace root. The benchmark's installs are single-project, so this doesn't narrow anything the install actually needed to see. Fixtures supplied via `--fixture-dir` that already declare `packages:` keep their own value. * ci(benchmark): bump no-lockfile scenarios to 20 min Clean-install and full-resolution go through pacquet's fresh-resolve install path, which is currently ~3-5x slower than pnpm on the `alotta-files` fixture (pnpm/pnpm#11832). Hyperfine's default 1 warmup + 10 timed runs across three benchmark targets (pacquet@HEAD, pacquet@main, system pnpm) projects to ~13 min wallclock for these two scenarios, putting the previous 10 min cap right on the edge. Doubling to 20 min keeps the per-step timeout meaningful as a stuck- install detector without losing CI time when the bench is healthy. The frozen-lockfile steps stay at 10 min — they don't traverse the slower fresh-resolve path. * fix(bench): drop --no-frozen-lockfile from full-resolution scenario Pacquet doesn't expose `--no-frozen-lockfile` (only `--frozen-lockfile`, `--prefer-frozen-lockfile`, and `--no-prefer-frozen-lockfile`). Passing it makes clap reject the install: error: unexpected argument '--no-frozen-lockfile' found tip: a similar argument exists: '--frozen-lockfile' The flag was redundant for this scenario anyway: full-resolution starts every iteration with no lockfile on disk (init() skips the lockfile when `lockfile_enabled()` is false; cleanup removes it; `lockfile=false` in the synthesized npmrc/workspace prevents writing one). With no lockfile present the frozen path is unreachable regardless of the flag, so both tools take fresh resolution by definition. Fold full-resolution into clean-install's bare `install` arm. --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -163,12 +163,29 @@ jobs:
|
||||
cp bench-work-env/BENCHMARK_REPORT.md bench-work-env/BENCHMARK_REPORT_FROZEN_LOCKFILE_HOT_CACHE.md
|
||||
cp bench-work-env/BENCHMARK_REPORT.json bench-work-env/BENCHMARK_REPORT_FROZEN_LOCKFILE_HOT_CACHE.json
|
||||
|
||||
# - name: 'Benchmark: Clean Install'
|
||||
# shell: bash
|
||||
# run: |
|
||||
# just integrated-benchmark --scenario=clean-install --registry=verdaccio --with-pnpm pacquet@HEAD pacquet@main
|
||||
# cp bench-work-env/BENCHMARK_REPORT.md bench-work-env/BENCHMARK_REPORT_CLEAN_INSTALL.md
|
||||
# cp bench-work-env/BENCHMARK_REPORT.json bench-work-env/BENCHMARK_REPORT_CLEAN_INSTALL.json
|
||||
- name: 'Benchmark: Clean Install'
|
||||
shell: bash
|
||||
# No-lockfile scenario: pacquet does fresh resolution against
|
||||
# the registry, ~3-5x slower than pnpm on `alotta-files`
|
||||
# (pnpm/pnpm#11832), so a single iteration runs longer than
|
||||
# the frozen-lockfile steps. 20 min leaves headroom for the
|
||||
# default hyperfine 1 warmup + 10 timed runs across all three
|
||||
# benchmark targets without losing the per-command timeout
|
||||
# safety net the other steps document.
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
just integrated-benchmark --scenario=clean-install --registry=verdaccio --with-pnpm pacquet@HEAD pacquet@main
|
||||
cp bench-work-env/BENCHMARK_REPORT.md bench-work-env/BENCHMARK_REPORT_CLEAN_INSTALL.md
|
||||
cp bench-work-env/BENCHMARK_REPORT.json bench-work-env/BENCHMARK_REPORT_CLEAN_INSTALL.json
|
||||
|
||||
- name: 'Benchmark: Full Resolution'
|
||||
shell: bash
|
||||
# Same timeout rationale as the clean-install step above.
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
just integrated-benchmark --scenario=full-resolution --registry=verdaccio --with-pnpm pacquet@HEAD pacquet@main
|
||||
cp bench-work-env/BENCHMARK_REPORT.md bench-work-env/BENCHMARK_REPORT_FULL_RESOLUTION.md
|
||||
cp bench-work-env/BENCHMARK_REPORT.json bench-work-env/BENCHMARK_REPORT_FULL_RESOLUTION.json
|
||||
|
||||
- name: Generate summary
|
||||
shell: bash
|
||||
@@ -199,18 +216,30 @@ jobs:
|
||||
echo '```'
|
||||
echo
|
||||
echo '</details>'
|
||||
# echo
|
||||
# echo '### Scenario: Clean Install'
|
||||
# echo
|
||||
# cat bench-work-env/BENCHMARK_REPORT_CLEAN_INSTALL.md
|
||||
# echo
|
||||
# echo '<details><summary>BENCHMARK_REPORT.json</summary>'
|
||||
# echo
|
||||
# echo '```json'
|
||||
# jq 'del(.results[].exit_codes)' bench-work-env/BENCHMARK_REPORT_CLEAN_INSTALL.json
|
||||
# echo '```'
|
||||
# echo
|
||||
# echo '</details>'
|
||||
echo
|
||||
echo '### Scenario: Clean Install'
|
||||
echo
|
||||
cat bench-work-env/BENCHMARK_REPORT_CLEAN_INSTALL.md
|
||||
echo
|
||||
echo '<details><summary>BENCHMARK_REPORT.json</summary>'
|
||||
echo
|
||||
echo '```json'
|
||||
jq 'del(.results[].exit_codes)' bench-work-env/BENCHMARK_REPORT_CLEAN_INSTALL.json
|
||||
echo '```'
|
||||
echo
|
||||
echo '</details>'
|
||||
echo
|
||||
echo '### Scenario: Full Resolution'
|
||||
echo
|
||||
cat bench-work-env/BENCHMARK_REPORT_FULL_RESOLUTION.md
|
||||
echo
|
||||
echo '<details><summary>BENCHMARK_REPORT.json</summary>'
|
||||
echo
|
||||
echo '```json'
|
||||
jq 'del(.results[].exit_codes)' bench-work-env/BENCHMARK_REPORT_FULL_RESOLUTION.json
|
||||
echo '```'
|
||||
echo
|
||||
echo '</details>'
|
||||
) > bench-work-env/SUMMARY.md
|
||||
|
||||
- name: Stage artifact contents
|
||||
|
||||
@@ -128,12 +128,11 @@ impl BenchmarkScenario {
|
||||
/// element (`install` or `add`), followed by any flags.
|
||||
pub fn install_args(self) -> &'static [&'static str] {
|
||||
match self {
|
||||
BenchmarkScenario::CleanInstall => &["install"],
|
||||
BenchmarkScenario::CleanInstall | BenchmarkScenario::FullResolution => &["install"],
|
||||
BenchmarkScenario::FrozenLockfile
|
||||
| BenchmarkScenario::FrozenLockfileHotCache
|
||||
| BenchmarkScenario::GvsWarm => &["install", "--frozen-lockfile"],
|
||||
BenchmarkScenario::Peek => &["add", "is-odd"],
|
||||
BenchmarkScenario::FullResolution => &["install", "--no-frozen-lockfile"],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -521,6 +521,17 @@ fn create_pnpm_workspace(
|
||||
if manifest.store_dir.is_none() {
|
||||
manifest.store_dir = Some("./store-dir".to_string());
|
||||
}
|
||||
// Pin `packages: ['.']` when the fixture didn't set it. Without this
|
||||
// the fresh-resolve install path's project walker
|
||||
// (`find_workspace_projects`) defaults to `[".", "**"]` and recurses
|
||||
// into the per-revision `<bench_dir>/pacquet/` clone of pnpm/pnpm,
|
||||
// tripping on the intentionally malformed test fixture at
|
||||
// `workspace/project-manifest-reader/__fixtures__/invalid-package-json/package.json`.
|
||||
// The benchmark's installs are always single-project, so restricting
|
||||
// to the root is the right scope regardless of fixture.
|
||||
if manifest.packages.is_none() {
|
||||
manifest.packages = Some(vec![".".to_string()]);
|
||||
}
|
||||
manifest.registry = Some(registry.to_string());
|
||||
manifest.auto_install_peers = Some(true);
|
||||
manifest.ignore_scripts = Some(true);
|
||||
|
||||
@@ -25,6 +25,22 @@ pub struct MinimalWorkspaceManifest {
|
||||
pub ignore_scripts: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub lockfile: Option<bool>,
|
||||
/// Workspace project globs. The benchmark forces this to `['.']`
|
||||
/// (workspace root only) whenever the fixture doesn't set it, so
|
||||
/// the fresh-resolve install path's
|
||||
/// `find_workspace_projects` walk doesn't recurse into the
|
||||
/// per-revision `<bench_dir>/pacquet/` clone of `pnpm/pnpm` and
|
||||
/// trip over upstream's intentionally malformed test fixtures
|
||||
/// (e.g. `workspace/project-manifest-reader/__fixtures__/invalid-package-json/package.json`).
|
||||
/// Both pnpm and pacquet default to `[".", "**"]` when the field
|
||||
/// is absent — see pnpm's
|
||||
/// [`findPackages.ts:28`](https://github.com/pnpm/pnpm/blob/9eb632bfbd/workspace/projects-reader/src/findPackages.ts#L28)
|
||||
/// and pacquet's
|
||||
/// [`projects.rs:128`](https://github.com/pnpm/pnpm/blob/9eb632bfbd/pacquet/crates/workspace/src/projects.rs#L128) —
|
||||
/// which is why a synthesized manifest with no `packages:` field
|
||||
/// pulls the source tree into the install's project enumeration.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub packages: Option<Vec<String>>,
|
||||
/// Mirrors pnpm's
|
||||
/// [`enableGlobalVirtualStore`](https://github.com/pnpm/pnpm/blob/94240bc046/config/reader/src/index.ts#L392-L394).
|
||||
/// Effective default is `false` for non-`--global` installs in
|
||||
|
||||
Reference in New Issue
Block a user