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
39 changes: 4 additions & 35 deletions .github/workflows/benchmark_pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ jobs:
publish:
needs: [preview]
if: >-
always() && !cancelled() && (needs.preview.outputs.available == 'true' ||
always() && !cancelled() && github.ref == 'refs/heads/main' &&
(needs.preview.outputs.available == 'true' ||
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
(github.event.workflow_run.name == 'Release' ||
Expand All @@ -57,12 +58,7 @@ jobs:
permissions:
actions: read
contents: write
pull-requests: write
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
pull-requests: read
steps:
# Only trusted publisher code executes; incoming benchmark artifacts are JSON data.
- uses: actions/checkout@v7
Expand Down Expand Up @@ -140,7 +136,7 @@ jobs:
run: |
npm ci --ignore-scripts --no-audit --no-fund
npm run build
- name: Refresh stable benchmark references and deploy
- name: Refresh and retain stable benchmark references
env:
GH_TOKEN: ${{ github.token }}
PREVIEW: ${{ needs.preview.outputs.available }}
Expand Down Expand Up @@ -172,30 +168,3 @@ jobs:
export -f refresh_site
python3 source/tools/publish_site.py --repo=site -- \
bash -c 'set -euo pipefail; refresh_site retry'
mkdir -p public
rsync --archive --exclude='.git' site/ public/
python3 source/tools/site_artwork.py source/docs/assets public
python3 source/tools/compact_site.py public
- uses: actions/upload-pages-artifact@v5
with:
path: public
- uses: actions/deploy-pages@v5
id: deployment
- name: Verify live benchmark publication
env:
PAGE_URL: ${{ steps.deployment.outputs.page_url }}
run: |
python3 source/tools/verify_benchmark_publication.py \
--index=public/benchmarks/index.html --url="${PAGE_URL}benchmarks/index.html"
- name: Update the current PR benchmark overview after publication
if: steps.preview.outputs.available == 'true'
env:
GH_TOKEN: ${{ github.token }}
PAGE_URL: ${{ steps.deployment.outputs.page_url }}
SOURCE_RUN: ${{ github.event.workflow_run.id }}
SOURCE_ATTEMPT: ${{ github.event.workflow_run.run_attempt }}
run: |
python3 source/tools/site_storage.py expand-benchmarks site/benchmarks/previews
python3 source/tools/benchmark_preview_ci.py comment --repository="${GITHUB_REPOSITORY}" \
--run="${SOURCE_RUN}" --attempt="${SOURCE_ATTEMPT}" --root=site/benchmarks \
--site-url="${PAGE_URL}benchmarks/"
20 changes: 3 additions & 17 deletions .github/workflows/coverage_pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ on:
workflows: [Release, Test]
types: [completed]

# Updating the retained site and deploying it must be one serial operation: two runs restoring the
# same branch concurrently would otherwise overwrite whichever report was pushed first.
# Retain every source report serially: concurrent branch writers could overwrite one another.
# Deployment runs separately, so a Pages timeout never holds this ingestion lock.
concurrency:
group: coverage-pages
queue: max
Expand All @@ -23,16 +23,12 @@ permissions: read-all

jobs:
publish:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
actions: read
contents: write
id-token: write
pages: write
pull-requests: read
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
# This workflow always comes from the trusted default branch. It consumes the report emitted
# by an unprivileged test run; PR code never receives the token that updates Pages.
Expand Down Expand Up @@ -177,13 +173,3 @@ jobs:
git -C site commit -m "coverage: publish reports and refresh PR metadata"
git -C site push
fi
mkdir -p public
rsync --archive --exclude='.git' site/ public/
python3 source/tools/site_artwork.py source/docs/assets public
python3 source/tools/compact_site.py public
- uses: actions/upload-pages-artifact@v5
with:
path: public
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
24 changes: 4 additions & 20 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ on:

permissions: {}

# Share the lock and retained branch with coverage: every deployment includes both.
# Serialize retained-site updates with the other report producers.
concurrency:
group: coverage-pages
queue: max
Expand All @@ -30,16 +30,11 @@ concurrency:
jobs:
publish:
if: >-
github.event_name != 'workflow_run' ||
(github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push')
github.ref == 'refs/heads/main' && (github.event_name != 'workflow_run' ||
(github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push'))
runs-on: ubuntu-latest
permissions:
contents: write
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v7
with:
Expand Down Expand Up @@ -131,7 +126,7 @@ jobs:
--repository "${GITHUB_REPOSITORY}" --tag "${RELEASE_TAG}" --latest "${LATEST_TAG}" \
--generated generated \
"${config_args[@]}"
- name: Retain and stage complete Pages tree
- name: Retain complete Pages tree
env:
RELEASE_TAG: ${{ steps.release.outputs.tag }}
run: |
Expand All @@ -145,14 +140,3 @@ jobs:
git -C site commit -m "site: publish ${RELEASE_TAG}"
git -C site push origin HEAD:coverage-pages
fi
mkdir -p public
rsync --archive --exclude='.git' site/ public/
python3 source/tools/site_artwork.py source/docs/assets public
python3 source/tools/compact_site.py public
- uses: actions/configure-pages@v6
- uses: actions/upload-pages-artifact@v5
with:
path: public
- name: Deploy complete site
id: deployment
uses: actions/deploy-pages@v5
80 changes: 80 additions & 0 deletions .github/workflows/pages_deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# SPDX-FileCopyrightText: Copyright (c) M. Boerger, the MBO Works authors
# SPDX-License-Identifier: Apache-2.0
name: Deploy retained site

on:
workflow_run:
workflows: [Publish coverage, Publish benchmarks, Publish release site]
types: [completed]
workflow_dispatch: {}

permissions: {}

jobs:
deploy:
if: >-
github.ref == 'refs/heads/main' &&
(github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success')
# Only deployment notifications coalesce. Producers retain every source report before this
# workflow starts; each deployment reads the newest retained branch at its own checkout.
concurrency:
group: retained-pages-deployment
queue: single
cancel-in-progress: false
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
actions: read
pages: write
id-token: write
pull-requests: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
# Execute trusted default-branch tools, never files from the retained site.
- uses: actions/checkout@v7
with:
ref: main
path: source
persist-credentials: false
- uses: actions/checkout@v7
with:
ref: coverage-pages
path: site
persist-credentials: false
- name: Record the immutable deployment snapshot
run: |
snapshot="$(git -C site rev-parse HEAD)"
echo "Retained site snapshot: ${snapshot}" >> "${GITHUB_STEP_SUMMARY}"
- name: Stage complete site
run: |
set -euo pipefail
mkdir -p public
rsync --archive --exclude='.git' site/ public/
python3 source/tools/site_artwork.py source/docs/assets public
python3 source/tools/compact_site.py public
- uses: actions/configure-pages@v6
- uses: actions/upload-pages-artifact@v5
with:
path: public
- name: Deploy immutable snapshot
id: deployment
uses: actions/deploy-pages@v5
- name: Verify live benchmark publication
if: hashFiles('public/benchmarks/index.html') != ''
env:
PAGE_URL: ${{ steps.deployment.outputs.page_url }}
run: |
python3 source/tools/verify_benchmark_publication.py \
--index=public/benchmarks/index.html --url="${PAGE_URL}benchmarks/index.html"
- name: Update current PR overviews from the deployed snapshot
if: hashFiles('public/benchmarks/index.html') != ''
env:
GH_TOKEN: ${{ github.token }}
PAGE_URL: ${{ steps.deployment.outputs.page_url }}
run: |
python3 source/tools/site_storage.py expand-benchmarks site/benchmarks/previews
python3 source/tools/benchmark_preview_ci.py comment-retained --repository="${GITHUB_REPOSITORY}" \
--root=site/benchmarks --site-url="${PAGE_URL}benchmarks/"
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ filegroup(
".github/workflows/fuzz.yml",
".github/workflows/main.yml",
".github/workflows/pages.yml",
".github/workflows/pages_deploy.yml",
".github/workflows/release.yml",
],
visibility = ["//tools:__pkg__"],
Expand Down
8 changes: 8 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ scope or declaring new flags implemented. See

## Pages deployment size

- [x] Separate retained-site writers from latest-snapshot deployment; coalesce deployment
notifications only, preserve every source ingestion, and update current PR comments only
after verifying the deployed snapshot.
- [ ] Verify the first separate Pages deployment and current-head PR comments on main. Recover
source events rejected by the full legacy publisher queue without canceling healthy ingestion
or dropping release/main observations. Old queued definitions retain their deployment steps.
- [ ] Reduce full historical rendering inside the writer lock; preserve all raw measurements and
coverage aggregate history when designing coalesced rendering or ingestion replay.
- [x] Redirect byte-identical stable coverage HTML to preserved run archives in all publishers;
check the complete staged payload before upload and verify the served benchmark index.
- [ ] Decide long-term coverage history hosting or retention before immutable reports alone
Expand Down
3 changes: 2 additions & 1 deletion docs/benchmark-backfill.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,8 @@ raw reports and the frozen host/build contract under
batch fails. Binaries, source checkouts and build logs are not uploaded. There is no automatic
upload from the measurement machine.

Publication is restricted to `main` by the Pages environment and uses trusted main code.
Retention and deployment explicitly require `main` and use trusted main code. The separate
deployment worker also uses the Pages environment; ingestion does not wait for Pages deployment.
Every deployment fetches the public benchmark index and checks its digest, so a successful
Pages response alone cannot mark stale content as successfully published.

Expand Down
10 changes: 8 additions & 2 deletions docs/benchmark-comparisons.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,25 @@ selection on that refresh; the original observations remain archived at their im

The trusted publisher starts watching when PR CI starts. Once the combined Linux/macOS comparison
job passes, it publishes both existing JSON results without waiting for unrelated tests. It does not
collect another set of measurements. Publication still waits for any active Pages deployment. The browser
collect another set of measurements. Retention waits for other site writers, independently of
deployment. A separate deployment worker publishes the newest retained snapshot after any active
deployment finishes. The browser
loads public data from the site and needs no GitHub credentials. Preview publication does not mean
the rest of CI passed.

The publisher checks the current PR head, repository, run attempt, successful comparison job,
artifact creation times, and the tested merge commit's parents. Reports preserve both the PR
branch head and tested merge commit, the base parent, and artifact identity. Only code checked out
from `main` executes with publication permissions. Uploaded HTML is ignored; bounded JSON is
validated and rendered again. Waiting happens outside the shared deployment lock. Preview updates
validated and rendered again. Waiting happens outside the shared writer lock. Preview updates
reuse merged pages and their catalog instead of rebuilding every historical chart.
Temporary GitHub read failures retry with bounded backoff; comment creation and updates are never
blindly retried.

Deployment notifications may coalesce across several PRs. After verifying the deployed benchmark
index, the deployment worker reconciles comments for every current PR represented by its snapshot,
not just the notification that started it. It never comments from newer, undeployed retained data.

The compact **Performance overview** appears before the detailed tables, in the selected chart
view, and in one bot comment updated on the PR after successful publication. It gives:

Expand Down
7 changes: 5 additions & 2 deletions docs/benchmark-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ as JSON so summaries can be reproduced.

The publisher executes trusted main-branch code and treats measurement artifacts as JSON data.
It checks the measured head against the source workflow SHA and derives run identity from GitHub.
It shares the `coverage-pages` deployment queue and retained branch with coverage and release
publication, preserving their pages.
It shares the `coverage-pages` writer queue and retained branch with coverage and release
publication, preserving their pages and every incoming measurement. After retention succeeds,
`Deploy retained site` reads and deploys the latest immutable branch snapshot. Only deployment
notifications coalesce; source-artifact ingestion remains serialized without cancellation. A Pages
timeout no longer holds the writer lock. Rendering remains inside the writer queue.

Local uploads can advance the retained branch while a publisher is rendering it. A rejected push
fetches and rebases onto that update, then performs a full benchmark refresh before retrying, with
Expand Down
24 changes: 21 additions & 3 deletions docs/release-website.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,30 @@ copied, including remote badges. Markdown conversion uses the
browsing the result requires no Markdown renderer or CDN.

After the Release workflow succeeds, `Publish release site` retains the snapshot on
`coverage-pages` and deploys the complete Pages tree. Coverage and site publication share a
concurrency group to preserve both trees. GitHub's latest stable release selects the root redirect;
`coverage-pages`. Coverage, benchmark, and release producers share a writer queue to preserve every
incoming report. A separate `Deploy retained site` workflow deploys the latest complete branch
snapshot after a producer succeeds. GitHub's latest stable release selects the root redirect;
backfilling an older release does not make it latest. The workflow can also be dispatched with a
published tag to retry publication. Enable GitHub Pages with **GitHub Actions** as its source, and
set the repository's About website to `https://mboworks.github.io/xff/`.

Deployment notifications coalesce; report ingestion does not. An active deployment completes, and
one pending deployment reads the newest retained snapshot when it starts. The deployment never
updates the retained branch. Producers and deployment explicitly require `main`; removing the Pages
environment from producers does not relax that branch restriction. A Pages timeout therefore cannot
hold the writer queue or discard source measurements. The deployment verifies the served benchmark index before updating PR
comments from that same snapshot, rechecking each PR's current head and workflow attempt.
To retry deployment of already retained data without downloading or regenerating reports:

```sh
gh workflow run pages_deploy.yml --repo mboworks/xff --ref main
```

This separation does not recover source events previously rejected by GitHub's full writer queue.
Those require a separate ingestion replay. Runs already queued with an older workflow definition
retain their original steps, including deployment; allow for that overlap during rollout. Rendering
still occurs inside each producer's writer lock and can remain the ingestion bottleneck.

The release site renders `NOTICE.md` as `NOTICE.html`, with the same navigation as other pages.
The Markdown notice is generated from the all-extras binary via `--help=notice --help-format=markdown`. `LICENSE` links remain pinned to the tagged commit on
GitHub, where readers can use GitHub's license analysis.
Expand Down Expand Up @@ -145,7 +163,7 @@ The README uses the original transparent MBO Works logo in
The favicon assets are derived from that same artwork: a 32-pixel PNG, a 180-pixel Apple
Touch icon, and an ICO containing 16-, 32-, 48-, and 256-pixel PNG images.

All three Pages publishers (release, coverage, and benchmarks) run `tools/site_artwork.py` on the final deployment directory.
`Deploy retained site` runs `tools/site_artwork.py` on the final deployment directory.
This supplies favicons to documentation, native HTML references, redirects, coverage
reports, and benchmark reports, including existing releases. Relative links work at every nesting depth and under
xff's project URL. Only the deployment copy receives these links; retained release files and
Expand Down
4 changes: 2 additions & 2 deletions tools/benchmark_history_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ def test_workflow_keeps_measurement_unprivileged_and_publication_serialized(self
self.assertIn("ref: main\n persist-credentials: false", waiter)
self.assertNotIn(": write", waiter)
self.assertIn("ref: main\n path: source", publish)
self.assertIn("Verify live benchmark publication", publish)
self.assertNotIn("actions/deploy-pages@", publish)
self.assertIn('python3 source/tools/publish_site.py --repo=site', publish)
self.assertIn("bash -c 'set -euo pipefail; refresh_site retry'", publish)
self.assertIn('[ "${PREVIEW}" = \'true\' ] && [ "$1" = \'initial\' ]', publish)
Expand Down Expand Up @@ -596,7 +596,7 @@ def test_release_refresh_does_not_download_measurement_artifacts(self):
self.assertIn("workflow_dispatch: {}", publish)
self.assertIn("fetch-depth: 0", publish)
self.assertIn("uses: actions/download-artifact@v8\n if: github.event.workflow_run.name == 'Benchmarks'", publish)
refresh = publish.split(" - name: Refresh stable benchmark references and deploy", 1)[1].split(
refresh = publish.split(" - name: Refresh and retain stable benchmark references", 1)[1].split(
" - uses: actions/upload-pages-artifact", 1)[0]
self.assertNotIn("SOURCE_RUN", refresh)
self.assertNotIn("download-artifact", refresh)
Expand Down
Loading
Loading