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
43 changes: 13 additions & 30 deletions .github/workflows/ci3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,22 +165,14 @@ jobs:
--data "$(jq -n --arg c "#backports" --arg t "$TEXT" '{channel:$c, text:$t}')"
fi

# Uses ci3/upload_benchmarks rather than github-action-benchmark: the action full-clones
# the multi-GB data repo per upload; the script does a shallow sparse clone instead.
- name: Upload benchmarks
if: env.BENCH_UPLOAD == '1'
uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1
with: &ci_benchmark_args
name: Aztec Benchmarks
benchmark-data-dir-path: "bench/${{ env.BENCH_BRANCH }}"
tool: "customSmallerIsBetter"
output-file-path: ./bench-out/bench.json
gh-repository: github.com/AztecProtocol/benchmark-page-data
github-token: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }}
auto-push: true
ref: ${{ github.event.pull_request.head.sha || github.sha }}
alert-threshold: "105%"
comment-on-alert: false
fail-on-alert: false
max-items-in-chart: 100
env: &ci_benchmark_env
GITHUB_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }}
COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: &ci_benchmark_run ./ci3/upload_benchmarks "Aztec Benchmarks" "bench/${BENCH_BRANCH:-}" ./bench-out/bench.json "$COMMIT_SHA"

# Validates that a nightly tag's embedded date matches today's UTC date.
# Prevents stale or replayed nightly tags from triggering scenario tests.
Expand Down Expand Up @@ -323,8 +315,8 @@ jobs:

- name: Upload benchmarks
if: env.ENABLE_DEPLOY_BENCH == '1'
uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1
with: *ci_benchmark_args
env: *ci_benchmark_env
run: *ci_benchmark_run

- name: Notify Slack and dispatch ClaudeBox on failure
if: failure() && startsWith(github.ref, 'refs/tags/v')
Expand Down Expand Up @@ -422,20 +414,11 @@ jobs:

- name: Upload benchmarks
if: always() && env.ENABLE_DEPLOY_BENCH == '1'
uses: benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba # v1.22.1
with:
name: Spartan
benchmark-data-dir-path: "bench/pr-${{ github.event.pull_request.number }}"
tool: "customSmallerIsBetter"
output-file-path: ./bench-out/bench.json
gh-repository: github.com/AztecProtocol/benchmark-page-data
github-token: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }}
auto-push: true
ref: ${{ github.event.pull_request.head.sha || github.sha }}
alert-threshold: "120%"
comment-on-alert: false
fail-on-alert: false
max-items-in-chart: 100
env:
GITHUB_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }}
COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: ./ci3/upload_benchmarks "Spartan" "bench/pr-$PR_NUMBER" ./bench-out/bench.json "$COMMIT_SHA"

# KIND-based e2e tests that run on a local Kubernetes cluster.
# One-time use: label is removed after the job runs.
Expand Down
126 changes: 126 additions & 0 deletions ci3/upload_benchmarks
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/usr/bin/env bash
set -euo pipefail
# Upload a benchmark datapoint to the benchmark dashboard data repo, writing the same
# window.BENCHMARK_DATA format as benchmark-action/github-action-benchmark.
#
# Replaces that action for uploads: the action full-clones the data repo (multiple GB of
# append-only history) to add a single datapoint. This script does a shallow, blob-filtered,
# sparse clone of just the target data dir instead.
#
# Usage: upload_benchmarks <entry-name> <data-dir> <bench-json> <commit-sha>
# entry-name Chart entry name, e.g. "Aztec Benchmarks"
# data-dir Directory in the data repo, e.g. "bench/next"
# bench-json Path to a customSmallerIsBetter JSON file: [{name, value, unit}, ...]
# commit-sha The source-repo commit the results belong to
#
# Env:
# GITHUB_TOKEN Token with push access to the data repo (required unless BENCH_REPO_URL).
# BENCH_REPO Data repo slug (default: AztecProtocol/benchmark-page-data).
# BENCH_REPO_URL Full clone URL override; bypasses token auth (used by tests).
# SOURCE_REPO_URL Repo the commit sha belongs to (default: https://github.com/AztecProtocol/aztec-packages).
# MAX_ITEMS Datapoints kept per entry series (default: 100).
# RETRY_SLEEP Base seconds between push retries (default: 5; tests set 0).

entry_name=$1
data_dir=${2%/}
bench_json=$3
commit_sha=$4

bench_repo=${BENCH_REPO:-AztecProtocol/benchmark-page-data}
repo_url=${BENCH_REPO_URL:-https://x-access-token:${GITHUB_TOKEN:-}@github.com/$bench_repo}
source_repo_url=${SOURCE_REPO_URL:-https://github.com/AztecProtocol/aztec-packages}
max_items=${MAX_ITEMS:-100}
retry_sleep=${RETRY_SLEEP:-5}

[ -f "$bench_json" ] || { echo "upload_benchmarks: bench json not found: $bench_json" >&2; exit 1; }

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

# Build the datapoint entry. Commit metadata comes from the GitHub API when available so the
# dashboard can render author/link info; degrade to bare sha metadata otherwise.
build_entry() {
local commit_info
if ! commit_info=$(curl -sf --max-time 15 \
${GITHUB_TOKEN:+-H "Authorization: Bearer $GITHUB_TOKEN"} \
"https://api.github.com/repos/${source_repo_url##*github.com/}/commits/$commit_sha" 2>/dev/null); then
commit_info='{}'
fi
jq -n \
--argjson info "$commit_info" \
--arg id "$commit_sha" \
--arg url "$source_repo_url/commit/$commit_sha" \
--arg now_iso "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--slurpfile benches "$bench_json" \
'{
commit: {
author: { name: ($info.commit.author.name // "unknown"), username: ($info.author.login // "") },
committer: { name: ($info.commit.committer.name // "unknown"), username: ($info.committer.login // "") },
id: $id,
message: ($info.commit.message // "" | split("\n")[0]),
timestamp: ($info.commit.author.date // $now_iso),
url: ($info.html_url // $url)
},
date: (now * 1000 | floor),
tool: "customSmallerIsBetter",
benches: $benches[0]
}'
}

# Append the entry to <data-dir>/data.js in a fresh shallow sparse clone and push.
# Called inside an `if`, which suppresses errexit for the whole body — every step that must
# not fail silently carries an explicit `|| return 1` so a failure aborts before the push.
append_and_push() {
local dir="$tmp/append"
rm -rf "$dir"
git clone -q --depth=1 --single-branch -b gh-pages --filter=blob:none --sparse "$repo_url" "$dir" || return 1
git -C "$dir" sparse-checkout set "$data_dir" >/dev/null || return 1

local data_js="$dir/$data_dir/data.js"
mkdir -p "$dir/$data_dir"
local current_json="$tmp/current.json"
echo '{"lastUpdate":0,"repoUrl":"","entries":{}}' > "$current_json"
if [ -f "$data_js" ]; then
# Strip the "window.BENCHMARK_DATA = " prefix (and any trailing semicolon/newline).
sed '1s/^window\.BENCHMARK_DATA *= *//' "$data_js" > "$current_json" || return 1
else
# New data dir: give it the same dashboard page as an existing dir.
local template
template=$(git -C "$dir" ls-tree -r --name-only HEAD | grep '/index.html$' | head -1 || true)
if [ -n "$template" ]; then
git -C "$dir" show "HEAD:$template" > "$dir/$data_dir/index.html"
fi
fi

# The entry and the existing data are passed to jq as files, never argv: Linux caps a single
# argv element at 128KB and a full CI entry exceeds that.
jq --arg name "$entry_name" \
--arg repo_url "$source_repo_url" \
--slurpfile entry "$tmp/entry.json" \
--argjson max "$max_items" \
'.lastUpdate = (now * 1000 | floor)
| .repoUrl = $repo_url
| .entries[$name] = ((.entries[$name] // []) + $entry | if length > $max then .[length - $max:] else . end)' \
"$current_json" > "$tmp/data.json" || return 1
[ -s "$tmp/data.json" ] || return 1
{ printf 'window.BENCHMARK_DATA = '; cat "$tmp/data.json"; } > "$data_js"

git -C "$dir" add "$data_dir" || return 1

git -C "$dir" -c user.name="aztec-bot" -c user.email="tech@aztecprotocol.com" \
commit -qm "$entry_name ($data_dir) @ $commit_sha" || return 1
git -C "$dir" push -q origin HEAD:gh-pages || return 1
}

build_entry > "$tmp/entry.json"

for attempt in 1 2 3; do
if append_and_push; then
echo "Benchmark datapoint uploaded to $bench_repo:$data_dir (entry: $entry_name)"
exit 0
fi
echo "Push attempt $attempt failed (concurrent update?); retrying..." >&2
sleep $((attempt * retry_sleep))
done
echo "upload_benchmarks: failed to push after 3 attempts" >&2
exit 1
108 changes: 108 additions & 0 deletions ci3/upload_benchmarks_test
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/usr/bin/env bash
set -euo pipefail
# Self-contained test for ci3/upload_benchmarks against a local fixture data repo. Run directly.
SCRIPT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/upload_benchmarks"
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
cd "$WORK"

fail() { echo "FAIL: $1" >&2; exit 1; }
pass() { echo "PASS: $1"; }

# --- Build fixture: bare repo with 8 months of weekly commits ---
git init -q --bare origin.git -b gh-pages
git -C origin.git config uploadpack.allowFilter true
git -C origin.git config uploadpack.allowAnySHA1InWant true

git clone -q origin.git seed 2>/dev/null
cd seed
git checkout -q -b gh-pages
mkdir -p bench/next bench/stale bench/data-top
echo "<html>dashboard</html>" > bench/next/index.html
echo "<html>dashboard</html>" > bench/stale/index.html
echo "top-level" > bench/index.html

datapoint() { # sha value
echo "{\"commit\":{\"id\":\"$1\"},\"date\":0,\"tool\":\"customSmallerIsBetter\",\"benches\":[{\"name\":\"t\",\"value\":$2,\"unit\":\"ms\"}]}"
}

commit_at() { # date msg
GIT_AUTHOR_DATE="$1" GIT_COMMITTER_DATE="$1" git -c user.name=fixture -c user.email=f@x commit -qam "$2"
}

# 41 weekly commits, from 40 weeks ago (~9 months) to now. bench/stale stops being updated at
# week -28, exercising append behavior alongside untouched sibling dirs.
for i in $(seq 40 -1 0); do
when=$(date -d "-$i weeks" --iso-8601=seconds)
echo "window.BENCHMARK_DATA = $(jq -n --argjson e "$(datapoint c$i $i)" '{lastUpdate:0,repoUrl:"r",entries:{"Aztec Benchmarks":[$e]}}')" > bench/next/data.js
if [ "$i" -ge 28 ]; then
cp bench/next/data.js bench/stale/data.js
fi
git add -A
commit_at "$when" "bench update week -$i"
done
git push -q origin gh-pages
cd "$WORK"
total_commits=$(git -C origin.git rev-list --count gh-pages)
[ "$total_commits" -eq 41 ] || fail "fixture should have 41 commits, has $total_commits"

# bench.json input
echo '[{"name":"proof_time","value":123.4,"unit":"ms"},{"name":"mem","value":9,"unit":"MB"}]' > bench.json

run_script() {
BENCH_REPO_URL="file://$WORK/origin.git" GITHUB_TOKEN= SOURCE_REPO_URL=https://github.com/AztecProtocol/aztec-packages \
"$SCRIPT" "$@"
}

check_tip() { # path -> stdout data.js json
git -C origin.git show "gh-pages:$1" | sed '1s/^window\.BENCHMARK_DATA *= *//'
}

# --- Test 1: plain append ---
run_script "Aztec Benchmarks" bench/next bench.json deadbeef1 > /dev/null
n=$(check_tip bench/next/data.js | jq '.entries["Aztec Benchmarks"] | length')
[ "$n" -eq 2 ] || fail "test1: expected 2 entries, got $n"
last=$(check_tip bench/next/data.js | jq -r '.entries["Aztec Benchmarks"][-1].commit.id')
[ "$last" = "deadbeef1" ] || fail "test1: last entry commit id: $last"
benchname=$(check_tip bench/next/data.js | jq -r '.entries["Aztec Benchmarks"][-1].benches[0].name')
[ "$benchname" = "proof_time" ] || fail "test1: bench name: $benchname"
git -C origin.git show gh-pages:bench/stale/data.js > /dev/null || fail "test1: stale dir should be untouched"
[ "$(git -C origin.git rev-list --count gh-pages)" -eq 42 ] || fail "test1: expected 42 commits"
pass "test1: append"

# --- Test 2: append to a brand-new data dir creates data.js + index.html ---
run_script "Spartan" bench/pr-1234 bench.json cafef00d > /dev/null
n=$(check_tip bench/pr-1234/data.js | jq '.entries["Spartan"] | length')
[ "$n" -eq 1 ] || fail "test2: new dir entry count: $n"
git -C origin.git show gh-pages:bench/pr-1234/index.html > /dev/null || fail "test2: index.html not created"
pass "test2: new data dir initialized"

# --- Test 3: MAX_ITEMS trims the series ---
for i in 1 2 3; do
MAX_ITEMS=3 BENCH_REPO_URL="file://$WORK/origin.git" GITHUB_TOKEN= "$SCRIPT" "Spartan" bench/pr-1234 bench.json trim$i > /dev/null
done
n=$(check_tip bench/pr-1234/data.js | jq '.entries["Spartan"] | length')
[ "$n" -eq 3 ] || fail "test3: expected 3 entries after trim, got $n"
first=$(check_tip bench/pr-1234/data.js | jq -r '.entries["Spartan"][0].commit.id')
[ "$first" = "trim1" ] || fail "test3: oldest kept entry: $first"
pass "test3: MAX_ITEMS trim"

# --- Test 4: entry larger than the 128KB per-argv-element limit still uploads ---
# (regression: --argjson "$(cat entry.json)" died with "Argument list too long" in CI)
jq -n '[range(3000) | {name: ("bench_\(.)_" + ("x" * 60)), value: ., unit: "ms"}]' > big_bench.json
[ "$(wc -c < big_bench.json)" -gt 131072 ] || fail "test4: fixture not big enough"
run_script "Aztec Benchmarks" bench/next big_bench.json bigentry1 > /dev/null || fail "test4: upload failed"
n=$(check_tip bench/next/data.js | jq '.entries["Aztec Benchmarks"][-1].benches | length')
[ "$n" -eq 3000 ] || fail "test4: expected 3000 benches in entry, got $n"
pass "test4: >128KB entry uploads intact"

# --- Test 5: unreachable data repo fails loudly, pushes nothing ---
# (regression: a mid-append jq failure was swallowed by the if-suppressed errexit and a
# truncated data.js was committed and pushed as a "success")
if RETRY_SLEEP=0 BENCH_REPO_URL="file://$WORK/nonexistent.git" GITHUB_TOKEN= \
"$SCRIPT" "Aztec Benchmarks" bench/next bench.json deadbeef9 > /dev/null 2>&1; then
fail "test5: expected nonzero exit for unreachable repo"
fi
pass "test5: failure is loud"

echo "ALL TESTS PASSED"
Loading