Files
fx/.github/workflows/bench.yml
T
Pranit c6b629ead8 Gate CI on Linux and run macOS only when a change needs it
- Run the full deterministic E2E suite on Linux in four shards on every pull request, plus the MCP conformance baseline, all against one shared build of fx.
- Run macOS arm64 unit tests and platform E2E files only when a change touches macOS-specific code, build.zig, macOS signing, or the native SDK addon, or carries the ci:macos label. Otherwise the macOS arm64 check passes without a macOS runner.
- Remove the four-platform Full CI workflow and the separate cross-target compile jobs.
- Smoke-test the Linux arm64, macOS x86_64, and macOS arm64 binaries in Binary Size, and reuse the base build for later pushes to the same pull request.
- Build the SDK WASM surfaces and Node-API addons once and share them across the SDK test jobs.
- Run PGSO on pull requests only when the PGSO pipeline changes, and keep release PGSO evidence for 90 days.
- Run the long-turn memory, libfx runtime, and live ACP jobs only on request.
- Limit libfx publishing to an archive check, an install smoke test, and a registry byte comparison.
- Add a warning when an E2E file passes only on retry.
- Remove the unused Prepare Release workflow and document the new CI rules.
2026-10-01 05:12:19 -04:00

364 lines
16 KiB
YAML

name: Benchmarks
on:
workflow_dispatch:
inputs:
long_turn_memory:
description: Run the long-turn memory benchmark
type: boolean
default: false
libfx_runtime:
description: Run the libfx runtime benchmark
type: boolean
default: false
signed_run:
description: Successful main Release validation run to compare (no signing credentials used)
type: string
default: ""
pgso_control_artifact:
description: Qualified main PGSO artifact ID
type: string
default: ""
pgso_candidate_artifact:
description: Qualified PR merge PGSO artifact ID
type: string
default: ""
push:
branches: [main]
pull_request:
concurrency:
group: bench-${{ github.ref }}${{ (inputs.pgso_control_artifact != '' || inputs.pgso_candidate_artifact != '') && '-pgso-artifacts' || '' }}
cancel-in-progress: true
permissions:
contents: read
jobs:
bench:
if: inputs.signed_run == '' && inputs.pgso_control_artifact == '' && inputs.pgso_candidate_artifact == ''
name: Startup Latency
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Zig
uses: mlugg/setup-zig@v2
with:
version: "0.16.0"
- name: Build (ReleaseSafe)
run: zig build -Doptimize=ReleaseSafe
- name: Install hyperfine
run: |
wget -q https://github.com/sharkdp/hyperfine/releases/download/v1.19.0/hyperfine_1.19.0_amd64.deb
sudo dpkg -i hyperfine_1.19.0_amd64.deb
- name: Run benchmarks
run: ./benchmarks/startup.sh --ci
- name: Enforce latency budgets
run: python3 benchmarks/check_budgets.py
- name: Run UI activity progress benchmark
run: |
for _ in 1 2 3; do
zig build run-bench-ui-activity -Doptimize=ReleaseSafe
done
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Run interactive terminal performance gate
working-directory: tests/e2e
env:
FX_TUI_PERFORMANCE: "1"
FX_TUI_PERFORMANCE_REPORT: ${{ runner.temp }}/tui-performance.json
run: bun test tui-performance.test.ts
- name: Upload interactive terminal performance evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: tui-performance
path: ${{ runner.temp }}/tui-performance.json
if-no-files-found: warn
long-turn-memory:
if: github.event_name == 'workflow_dispatch' && inputs.long_turn_memory && inputs.signed_run == '' && inputs.pgso_control_artifact == '' && inputs.pgso_candidate_artifact == ''
name: Long-turn memory
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- name: Setup Zig
uses: mlugg/setup-zig@v2
with:
version: "0.16.0"
- name: Build (ReleaseSafe)
run: zig build -Doptimize=ReleaseSafe
- name: Run long-turn memory benchmark
run: python3 benchmarks/long_turn_memory.py --binary ./zig-out/bin/fx --output ${{ runner.temp }}/long-turn-memory --steps 500 --limit-mib 1024 --timeout 480
- name: Enforce memory budget
run: jq -e '.completed == true and (.peak_rss_kib < 524288)' ${{ runner.temp }}/long-turn-memory/measurements.json
- name: Upload long-turn memory evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: long-turn-memory-${{ github.sha }}
path: |
${{ runner.temp }}/long-turn-memory/measurements.json
${{ runner.temp }}/long-turn-memory/stderr.log
${{ runner.temp }}/long-turn-memory/stdout.json
if-no-files-found: warn
libfx-ttft:
if: github.event_name == 'workflow_dispatch' && inputs.libfx_runtime && inputs.signed_run == '' && inputs.pgso_control_artifact == '' && inputs.pgso_candidate_artifact == ''
name: libfx Runtime
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Zig
uses: mlugg/setup-zig@v2
with:
version: "0.16.0"
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "24"
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: "1.4.2"
- name: Build libfx artifacts
run: |
zig build -Dnapi-surface=core -Doptimize=ReleaseSafe
zig build -Dwasm-surface=core -Doptimize=ReleaseSmall
- name: Install pinned Pi benchmark target
run: npm install --prefix /tmp/libfx-pi @earendil-works/pi-coding-agent@0.84.4
- name: Build external inference fixture
run: zig build-exe benchmarks/libfx/fake-inference-server.zig -O ReleaseSafe -femit-bin=${{ runner.temp }}/libfx-fake-inference-server
- name: Measure libfx and Pi
run: |
node benchmarks/libfx/bench-suite.mjs --samples 100 --pi-root /tmp/libfx-pi --out benchmarks/results/libfx
node benchmarks/libfx/bench-bridge.mjs --backend native --samples 100 > benchmarks/results/libfx/bridge-node-native.json
node --experimental-wasm-jspi benchmarks/libfx/bench-bridge.mjs --backend wasm --samples 100 > benchmarks/results/libfx/bridge-node-wasm.json
bun benchmarks/libfx/bench-bridge.mjs --backend native --samples 100 > benchmarks/results/libfx/bridge-bun-native.json
bun benchmarks/libfx/bench-bridge.mjs --backend wasm --samples 100 > benchmarks/results/libfx/bridge-bun-wasm.json
node benchmarks/libfx/bench-runtime.mjs --backend native --warm-samples 100 --stream-samples 30 > benchmarks/results/libfx/runtime-node-native.json
node --experimental-wasm-jspi benchmarks/libfx/bench-runtime.mjs --backend wasm --warm-samples 100 --stream-samples 30 > benchmarks/results/libfx/runtime-node-wasm.json
bun benchmarks/libfx/bench-runtime.mjs --backend native --warm-samples 100 --stream-samples 30 > benchmarks/results/libfx/runtime-bun-native.json
bun benchmarks/libfx/bench-runtime.mjs --backend wasm --warm-samples 100 --stream-samples 30 > benchmarks/results/libfx/runtime-bun-wasm.json
node --expose-gc benchmarks/libfx/bench-capacity.mjs --backend native --count 25 > benchmarks/results/libfx/capacity-node-native.json
node --experimental-wasm-jspi --expose-gc benchmarks/libfx/bench-capacity.mjs --backend wasm --count 25 > benchmarks/results/libfx/capacity-node-wasm.json
bun benchmarks/libfx/bench-capacity.mjs --backend native --count 25 > benchmarks/results/libfx/capacity-bun-native.json
bun benchmarks/libfx/bench-capacity.mjs --backend wasm --count 25 > benchmarks/results/libfx/capacity-bun-wasm.json
node benchmarks/libfx/bench-competitive.mjs --server ${{ runner.temp }}/libfx-fake-inference-server --pi-root /tmp/libfx-pi --out benchmarks/results/libfx --samples 100 --warmups 3 --rounds 3
- name: Enforce deterministic libfx contracts
run: node benchmarks/libfx/check-results.mjs --dir benchmarks/results/libfx
- name: Upload raw libfx TTFT samples
if: always()
uses: actions/upload-artifact@v4
with:
name: libfx-ttft-${{ github.sha }}
path: benchmarks/results/libfx/*.json
if-no-files-found: error
signed-macos:
name: Signed macOS comparison
if: github.event_name == 'workflow_dispatch' && inputs.signed_run != '' && inputs.pgso_control_artifact == '' && inputs.pgso_candidate_artifact == ''
runs-on: macos-15
timeout-minutes: 20
permissions:
actions: read
contents: read
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- name: Download a successful main validation pair
env:
GH_TOKEN: ${{ github.token }}
SIGNED_RUN: ${{ inputs.signed_run }}
run: |
[[ "$SIGNED_RUN" =~ ^[1-9][0-9]*$ ]]
gh api "repos/$GITHUB_REPOSITORY/actions/runs/$SIGNED_RUN" > "$RUNNER_TEMP/signed-source.json"
jq -e --arg repo "$GITHUB_REPOSITORY" '
.conclusion == "success" and .head_branch == "main" and
.event == "workflow_dispatch" and .path == ".github/workflows/release.yml" and
.head_repository.full_name == $repo
' "$RUNNER_TEMP/signed-source.json"
gh run download "$SIGNED_RUN" --repo "$GITHUB_REPOSITORY" \
--name fx-macos-aarch64 --name signing-control-macos-arm64 \
--dir "$RUNNER_TEMP/signed-input"
mkdir -p "$RUNNER_TEMP/signed-control" "$RUNNER_TEMP/signed-candidate"
tar -xzf "$RUNNER_TEMP/signed-input/signing-control-macos-arm64/fx-signing-control.tar.gz" -C "$RUNNER_TEMP/signed-control" fx
tar -xzf "$RUNNER_TEMP/signed-input/fx-macos-aarch64/fx-macos-aarch64.tar.gz" -C "$RUNNER_TEMP/signed-candidate" fx
- name: Install measurement tools
env:
HOMEBREW_NO_AUTO_UPDATE: "1"
run: |
command -v hyperfine >/dev/null || brew install hyperfine
xcrun clang -O2 benchmarks/signed_macos_resources.c -o "$RUNNER_TEMP/signed-resources"
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
- name: Compare signed startup
run: |
python3 -m scripts.compare_signed_macos \
--control "$RUNNER_TEMP/signed-control/fx" \
--candidate "$RUNNER_TEMP/signed-candidate/fx" \
--source-sha "$(jq -r .head_sha "$RUNNER_TEMP/signed-source.json")" \
--output "$RUNNER_TEMP/signed-comparison"
- name: Compare native image-flow memory
env:
FX_SIGNED_COMPARE_DIR: ${{ runner.temp }}/signed-comparison
FX_SIGNED_RESOURCE_PROBE: ${{ runner.temp }}/signed-resources
run: bun test benchmarks/signed_macos_memory.test.ts
- name: Retain paired measurements
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: signed-macos-comparison-${{ github.run_id }}
path: |
${{ runner.temp }}/signed-comparison/manifest.json
${{ runner.temp }}/signed-comparison/startup-*.json
${{ runner.temp }}/signed-comparison/startup-*/logs/
${{ runner.temp }}/signed-comparison/memory-*.json
${{ runner.temp }}/signed-source.json
if-no-files-found: error
pgso-artifacts:
name: PGSO artifact comparison
if: github.event_name == 'workflow_dispatch' && (inputs.pgso_control_artifact != '' || inputs.pgso_candidate_artifact != '')
runs-on: macos-15
timeout-minutes: 30
permissions:
actions: read
contents: read
env:
FX_E2E_DISABLE_DOTENV: "1"
FX_SOUND: "0"
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
persist-credentials: false
- name: Set comparison directories
run: |
echo "FX_PGSO_COMPARE_INPUT=$RUNNER_TEMP/pgso-input" >> "$GITHUB_ENV"
echo "FX_PGSO_COMPARE_OUTPUT=$RUNNER_TEMP/pgso-comparison" >> "$GITHUB_ENV"
- name: Resolve immutable qualified artifacts
id: artifacts
env:
GH_TOKEN: ${{ github.token }}
CONTROL_ARTIFACT: ${{ inputs.pgso_control_artifact }}
CANDIDATE_ARTIFACT: ${{ inputs.pgso_candidate_artifact }}
SIGNED_RUN: ${{ inputs.signed_run }}
run: |
test -z "$SIGNED_RUN"
[[ "$CONTROL_ARTIFACT" =~ ^[1-9][0-9]*$ ]]
[[ "$CANDIDATE_ARTIFACT" =~ ^[1-9][0-9]*$ ]]
test "$CONTROL_ARTIFACT" != "$CANDIDATE_ARTIFACT"
mkdir -p "$FX_PGSO_COMPARE_INPUT"
for lane in control candidate; do
artifact_id="$CONTROL_ARTIFACT"
if [[ "$lane" == candidate ]]; then artifact_id="$CANDIDATE_ARTIFACT"; fi
gh api "repos/$GITHUB_REPOSITORY/actions/artifacts/$artifact_id" > "$FX_PGSO_COMPARE_INPUT/$lane-artifact.json"
jq -e --argjson id "$artifact_id" '
.id == $id and .expired == false and .size_in_bytes > 0 and
.size_in_bytes <= 67108864 and
(.name | test("^pgso-evidence-macos-arm64-[a-f0-9]{40}(-attempt-[1-9][0-9]*)?$"))
' "$FX_PGSO_COMPARE_INPUT/$lane-artifact.json"
run_id=$(jq -er .workflow_run.id "$FX_PGSO_COMPARE_INPUT/$lane-artifact.json")
[[ "$run_id" =~ ^[1-9][0-9]*$ ]]
gh api "repos/$GITHUB_REPOSITORY/actions/runs/$run_id" > "$FX_PGSO_COMPARE_INPUT/$lane-run.json"
jq -e --arg repo "$GITHUB_REPOSITORY" --arg lane "$lane" '
.status == "completed" and .conclusion == "success" and
(.path == ".github/workflows/pgso-macos-arm64.yml" or
($lane == "control" and .path == ".github/workflows/release.yml" and
.head_branch == "main" and (.event == "push" or .event == "workflow_dispatch"))) and
.repository.full_name == $repo and .head_repository.full_name == $repo
' "$FX_PGSO_COMPARE_INPUT/$lane-run.json"
echo "${lane}_run=$run_id" >> "$GITHUB_OUTPUT"
done
- name: Download control by artifact ID
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
github-token: ${{ github.token }}
run-id: ${{ steps.artifacts.outputs.control_run }}
artifact-ids: ${{ inputs.pgso_control_artifact }}
merge-multiple: true
path: ${{ env.FX_PGSO_COMPARE_INPUT }}/control
- name: Download candidate by artifact ID
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
github-token: ${{ github.token }}
run-id: ${{ steps.artifacts.outputs.candidate_run }}
artifact-ids: ${{ inputs.pgso_candidate_artifact }}
merge-multiple: true
path: ${{ env.FX_PGSO_COMPARE_INPUT }}/candidate
- name: Retain source commit relationships
env:
GH_TOKEN: ${{ github.token }}
run: |
for lane in control candidate; do
source_sha=$(jq -er .evidence.identity.source_sha "$FX_PGSO_COMPARE_INPUT/$lane/manifest.json")
[[ "$source_sha" =~ ^[a-f0-9]{40}$ ]]
gh api "repos/$GITHUB_REPOSITORY/git/commits/$source_sha" > "$FX_PGSO_COMPARE_INPUT/$lane-source.json"
done
if jq -e '.event == "workflow_dispatch"' "$FX_PGSO_COMPARE_INPUT/candidate-run.json" > /dev/null; then
control_sha=$(jq -er .sha "$FX_PGSO_COMPARE_INPUT/control-source.json")
candidate_sha=$(jq -er .sha "$FX_PGSO_COMPARE_INPUT/candidate-source.json")
gh api "repos/$GITHUB_REPOSITORY/compare/$control_sha...$candidate_sha" --jq '
{status, ahead_by, behind_by, total_commits,
base_commit: {sha: .base_commit.sha}, merge_base_commit: {sha: .merge_base_commit.sha},
commits: [.commits[] | {sha}]}
' > "$FX_PGSO_COMPARE_INPUT/candidate-ancestry.json"
fi
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.14"
- name: Compare calibrated native memory and latency
run: |
bun test --max-concurrency=1 benchmarks/pgso_artifacts.test.ts
jq -e '.status == "passed" and (.cohorts | length == 5) and all(.cohorts[]; .passed)' "$FX_PGSO_COMPARE_OUTPUT/results.json"
- name: Retain complete comparison evidence
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: pgso-artifact-comparison-${{ github.run_id }}-attempt-${{ github.run_attempt }}
path: |
${{ env.FX_PGSO_COMPARE_INPUT }}/*.json
${{ env.FX_PGSO_COMPARE_OUTPUT }}/*.json
${{ env.FX_PGSO_COMPARE_OUTPUT }}/*/samples.json
${{ env.FX_PGSO_COMPARE_OUTPUT }}/*/*/*.json
if-no-files-found: error
retention-days: 14