Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
83 changes: 79 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest]
# RFC 0058 — the benchmark lane. Gates on WeavePy/CPython *ratios*
# against baselines/bench.json (host-independent, unlike absolute
# times); the 25% threshold absorbs shared-runner noise. The
# markdown report lands in the job summary so every PR shows its
# ratio table.
# against the per-platform baselines/bench-{os}-{arch}.json (RFC
# 0062 WS3; host-independent, unlike absolute times); the 25%
# threshold absorbs shared-runner noise. The markdown report lands
# in the job summary so every PR shows its ratio table.
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
Expand All @@ -129,9 +129,15 @@ jobs:
- name: Build weavepy CLI + bench harness
run: cargo build --release -p weavepy-cli -p weavepy-bench
- name: Run bench gate
# Linux has no committed per-platform baseline yet, so the gate
# is advisory there (--allow-missing-baseline prints a note and
# exits 0). It graduates to strict once a measured
# bench-linux-x86_64.json is committed — drop the flag then.
# macOS gates strictly against bench-macos-aarch64.json.
run: |
target/release/weavepy-bench gate --pct=25 \
--weavepy=target/release/weavepy \
${{ matrix.os == 'ubuntu-latest' && '--allow-missing-baseline' || '' }} \
| tee bench-report.md
- name: Append bench report to job summary
if: always()
Expand All @@ -148,6 +154,75 @@ jobs:
path: bench-report.md
if-no-files-found: warn

ecosystem:
name: ecosystem (blocking, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
# RFC 0062 WS3 — the ecosystem lane in CI. Installs the manifest's
# packages from an offline wheel cache into venvs running under the
# built weavepy, runs each row's probe (and package self-tests),
# and gates on tests/ecosystem/expectations.toml. Blocking, same
# policy as regrtest. The host CPython is only used by the wheel
# fetcher (pip download needs a real Python); the runners' system
# cc covers source builds.
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Cache ecosystem wheels
uses: actions/cache@v4
with:
path: target/ecosystem-wheels
key: ecosystem-wheels-${{ runner.os }}-${{ hashFiles('tests/ecosystem/manifest.toml') }}
- name: Build weavepy CLI
run: cargo build --release -p weavepy-cli
- name: Fetch ecosystem wheels (no-op on cache hit)
run: python3 tools/ecosystem_fetch.py --dest target/ecosystem-wheels
- name: Run ecosystem harness
run: |
cargo run --release -p weavepy-conformance -- ecosystem \
--wheels target/ecosystem-wheels \
--selftests \
--check

dist-check:
name: dist check (blocking, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
# RFC 0062 — builds the relocatable artifact from the release CLI
# and runs `weavepy-dist check` against it (boot, stdlib, venv +
# pip smoke). The pip leg reuses the offline wheel cache from the
# ecosystem job's actions/cache key.
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Cache ecosystem wheels
uses: actions/cache@v4
with:
path: target/ecosystem-wheels
key: ecosystem-wheels-${{ runner.os }}-${{ hashFiles('tests/ecosystem/manifest.toml') }}
- name: Build weavepy CLI
run: cargo build --release -p weavepy-cli
- name: Fetch ecosystem wheels (no-op on cache hit)
run: python3 tools/ecosystem_fetch.py --dest target/ecosystem-wheels
- name: Run dist check
run: |
cargo run --release -p weavepy-dist -- check \
--wheels target/ecosystem-wheels

conformance:
name: cpython conformance (non-blocking)
runs-on: ubuntu-latest
Expand Down
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@ members = [
"crates/weavepy-cli",
"crates/weavepy-compiler",
"crates/weavepy-conformance",
"crates/weavepy-dist",
"crates/weavepy-jit",
"crates/weavepy-lexer",
"crates/weavepy-parser",
"crates/weavepy-vm",
"vendor/expat-sys",
]
# `weavepy-conformance` and `weavepy-bench` are development tools that shell
# out to `python3` and aren't useful to publish or to compile by default.
# `weavepy-conformance`, `weavepy-bench`, and `weavepy-dist` are development
# tools that aren't useful to publish or to compile by default.
# Keep them out of `default-members` so `cargo build/test --workspace`
# stays light; opt in explicitly via `-p weavepy-conformance` /
# `-p weavepy-bench`.
# `-p weavepy-bench` / `-p weavepy-dist`.
default-members = [
"crates/weavepy",
"crates/weavepy-capi",
Expand Down
43 changes: 33 additions & 10 deletions crates/weavepy-bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,28 @@ with `time.perf_counter_ns()` and print `WEAVEPY_BENCH_NS=<int>`, so
startup / parse / import cost is excluded symmetrically (the dedicated
`startup` fixture measures full subprocess wall time instead).

The tracked baseline (`baselines/bench.json`) stores medians for both
interpreters plus the WeavePy/CPython **ratio** per fixture and the
suite geometric mean. `gate` compares ratios — host-independent,
unlike absolute nanoseconds — and fails on regressions beyond a
threshold, like the regrtest and ecosystem lanes' `--check`. CI runs
`gate --pct=25` on ubuntu + macos (the `bench` job).
Baselines are tracked **per platform** (RFC 0062 WS3) as
`baselines/bench-{os}-{arch}.json`, resolved against the host's
`std::env::consts::{OS, ARCH}` — e.g. `bench-macos-aarch64.json`,
`bench-linux-x86_64.json`. Each file stores medians for both
interpreters plus the WeavePy/CPython **ratio** per fixture, the
suite geometric mean, and the platform it was measured on. `gate`
compares ratios — host-independent, unlike absolute nanoseconds —
and fails on regressions beyond a threshold, like the regrtest and
ecosystem lanes' `--check`. CI runs `gate --pct=25` on ubuntu +
macos (the `bench` job).

Two per-platform guardrails:

- If the host has **no** baseline file, `gate` fails with a message
naming the missing `bench-{os}-{arch}.json` — pass
`--allow-missing-baseline` to turn that into an advisory note with
exit 0 (what CI does on platforms whose baseline hasn't been
measured and committed yet).
- Each baseline records the platform it was measured on, and `gate`
refuses a baseline whose recorded platform mismatches the host —
copying `bench-macos-aarch64.json` to `bench-linux-x86_64.json`
cannot silently gate Linux against macOS ratios.

The crate is excluded from `default-members` so `cargo build` /
`cargo test --workspace` doesn't pull it in. Opt in with
Expand All @@ -30,12 +46,17 @@ cargo build --release -p weavepy-cli -p weavepy-bench
# Run all fixtures, print a markdown report (ratio column + geomean).
cargo xbench run

# Compare current ratios against the baseline; exit non-zero on
# regression beyond 10% (default threshold).
# Compare current ratios against the host platform's baseline; exit
# non-zero on regression beyond 10% (default threshold).
cargo xbench gate
cargo xbench gate --pct=25

# Refresh the baseline JSON tracked at `baselines/bench.json`.
# Advisory mode: exit 0 with a note when the host platform has no
# committed baseline (a present-but-foreign baseline still fails).
cargo xbench gate --allow-missing-baseline

# Refresh the host platform's baseline JSON tracked at
# `baselines/bench-{os}-{arch}.json`.
cargo xbench run --update-baseline

# Point at explicit interpreters.
Expand All @@ -62,4 +83,6 @@ cargo xbench run --json
~25–65 ms.
4. Run `cargo xbench run --update-baseline` and inspect the diff
before committing. The gate fails fixtures that have no baseline
row, so the baseline refresh ships in the same change.
row, so the baseline refresh ships in the same change. (This only
refreshes the *host* platform's file; other platforms' baselines
are refreshed on their own hardware.)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"version": 3,
"version": 4,
"host": "unknown",
"platform": "macos-aarch64",
"created_at": "ts=1786352866",
"geomean_ratio": 8.407741671174728,
"rows": [
Expand Down
47 changes: 44 additions & 3 deletions crates/weavepy-bench/src/fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,50 @@ pub fn discover_fixtures() -> Vec<Fixture> {
.collect()
}

/// Path to the baseline JSON tracked alongside the fixtures.
pub fn baseline_path() -> PathBuf {
/// Host platform key used to resolve the tracked baseline (RFC 0062
/// WS3): `{os}-{arch}` from `std::env::consts`, e.g. `macos-aarch64`
/// or `linux-x86_64`.
pub fn platform_key() -> String {
format!("{}-{}", std::env::consts::OS, std::env::consts::ARCH)
}

/// Path to the per-platform baseline JSON for an explicit platform
/// key (see [`platform_key`]).
pub fn baseline_path_for(platform: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("baselines")
.join("bench.json")
.join(format!("bench-{platform}.json"))
}

/// Path to the baseline JSON for the host platform, tracked
/// alongside the fixtures as `baselines/bench-{os}-{arch}.json`.
pub fn baseline_path() -> PathBuf {
baseline_path_for(&platform_key())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn baseline_path_is_per_platform() {
let p = baseline_path_for("linux-x86_64");
assert!(p.ends_with("baselines/bench-linux-x86_64.json"), "{p:?}");
}

#[test]
fn host_baseline_path_uses_host_platform_key() {
let key = platform_key();
assert_eq!(
key,
format!("{}-{}", std::env::consts::OS, std::env::consts::ARCH)
);
let p = baseline_path();
assert!(
p.file_name()
.and_then(|n| n.to_str())
.is_some_and(|n| n == format!("bench-{key}.json")),
"{p:?}"
);
}
}
23 changes: 14 additions & 9 deletions crates/weavepy-bench/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@
//! and imports are excluded from the loop metric. (The dedicated
//! `startup` fixture measures full subprocess wall time instead.)
//!
//! The tracked baseline (`baselines/bench.json`) stores WeavePy and
//! CPython medians plus the WeavePy/CPython **ratio** per fixture and
//! the suite geometric mean. `gate` compares ratios — which are
//! host-independent, unlike absolute nanoseconds — and fails on
//! regressions beyond a threshold, exactly like the regrtest and
//! ecosystem lanes' `--check`.
//! The tracked baselines are per platform (RFC 0062 WS3):
//! `baselines/bench-{os}-{arch}.json`, resolved against the host's
//! `std::env::consts::{OS, ARCH}`. Each file stores WeavePy and
//! CPython medians plus the WeavePy/CPython **ratio** per fixture,
//! the suite geometric mean, and the platform it was measured on.
//! `gate` compares ratios — which are host-independent, unlike
//! absolute nanoseconds — and fails on regressions beyond a
//! threshold, exactly like the regrtest and ecosystem lanes'
//! `--check`. It refuses baselines whose recorded platform
//! mismatches the host, and a missing per-platform file is an error
//! unless `--allow-missing-baseline` makes the gate advisory.
//!
//! ## Adding a fixture
//!
Expand All @@ -24,9 +29,9 @@
//! 2. Add `"foo"` to [`fixtures::FIXTURES`] and a `default_work`
//! entry sized so the CPython leg takes ~50–300 ms.
//! 3. Run `cargo run --release -p weavepy-bench -- run
//! --update-baseline` and inspect the diff in
//! `baselines/bench.json` before committing. The gate fails on
//! fixtures that have no baseline row (the RFC 0049 "no
//! --update-baseline` and inspect the diff in the host's
//! `baselines/bench-{os}-{arch}.json` before committing. The gate
//! fails on fixtures that have no baseline row (the RFC 0049 "no
//! unmeasured rows" rule applied to speed).

pub mod fixtures;
Expand Down
Loading
Loading