diff --git a/.github/workflows/llgo-binary-size.yml b/.github/workflows/llgo-binary-size.yml
index 63f0513..167285a 100644
--- a/.github/workflows/llgo-binary-size.yml
+++ b/.github/workflows/llgo-binary-size.yml
@@ -51,35 +51,31 @@ permissions:
contents: read
actions: read
-concurrency:
- # Keep benchmark builds in their own queue. The Pages-only workflow has a
- # separate queue; sharing one group would let a newer pending Pages run
- # replace a pending binary-size run even with cancel-in-progress disabled.
- group: llgo-binary-size-build
- cancel-in-progress: false
-
jobs:
update-pin:
if: github.event_name == 'repository_dispatch'
permissions:
contents: write
- actions: write
runs-on: ubuntu-24.04
+ concurrency:
+ # Pin updates may be coalesced because the file only records the latest
+ # LLGo main revision. The benchmark job below is not part of this queue:
+ # every distinct dispatched LLGo commit still gets its own result.
+ group: llgo-binary-size-pin
+ cancel-in-progress: false
steps:
- uses: actions/checkout@v4
with:
- # A dispatch can wait behind a long binary-size build. Start from
- # the current branch tip instead of the SHA captured when the event
- # was created.
+ # A coalesced pin update can start after main has advanced. Always
+ # rebuild its one-file commit from the current branch tip.
ref: main
fetch-depth: 0
- - name: Update the committed LLGo pin and start the build
+ - name: Update the committed LLGo pin
env:
DISPATCH_SOURCE_REPOSITORY: ${{ github.event.client_payload.source_repository }}
DISPATCH_LLGO_REPOSITORY: ${{ github.event.client_payload.llgo_repository }}
DISPATCH_LLGO_COMMIT: ${{ github.event.client_payload.llgo_commit }}
- GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if [[ "$DISPATCH_SOURCE_REPOSITORY" != "xgo-dev/llgo" || \
@@ -92,6 +88,18 @@ jobs:
exit 1
fi
+ # A newer notification may replace an older pending pin update. Pin
+ # the current LLGo main tip so a delayed notification cannot move
+ # the committed default backwards.
+ target_commit="$(git ls-remote \
+ "https://github.com/${DISPATCH_LLGO_REPOSITORY}.git" \
+ refs/heads/main | awk 'NR == 1 { print $1 }')"
+ if [[ ! "$target_commit" =~ ^[0-9a-f]{40}$ ]]; then
+ echo "could not resolve ${DISPATCH_LLGO_REPOSITORY} main" >&2
+ exit 1
+ fi
+ echo "Pinning current LLGo main $target_commit (notification requested $DISPATCH_LLGO_COMMIT)"
+
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
@@ -104,16 +112,16 @@ jobs:
git checkout --detach origin/main
current_commit="$(awk -F= '$1 == "LLGO_COMMIT" { print $2 }' ci/llgo-size/llgo-version.env)"
- if [[ "$current_commit" == "$DISPATCH_LLGO_COMMIT" ]]; then
- echo "LLGo pin is already $DISPATCH_LLGO_COMMIT"
+ if [[ "$current_commit" == "$target_commit" ]]; then
+ echo "LLGo pin is already $target_commit"
pin_ready=true
break
fi
- sed -i "s/^LLGO_COMMIT=.*/LLGO_COMMIT=${DISPATCH_LLGO_COMMIT}/" \
+ sed -i "s/^LLGO_COMMIT=.*/LLGO_COMMIT=${target_commit}/" \
ci/llgo-size/llgo-version.env
git add ci/llgo-size/llgo-version.env
- git commit -m "ci: pin LLGo ${DISPATCH_LLGO_COMMIT:0:12}"
+ git commit -m "ci: pin LLGo ${target_commit:0:12}"
if git push origin HEAD:main; then
pin_ready=true
break
@@ -132,20 +140,6 @@ jobs:
exit 1
fi
- # Dispatch even when the pin was already current. A retry may be
- # recovering from a prior run that pushed the pin but failed before
- # it could start the actual binary-size build.
- payload="$(jq -cn \
- --arg ref main \
- --arg llgo_commit "$DISPATCH_LLGO_COMMIT" \
- '{ref: $ref, inputs: {llgo_commit: $llgo_commit}}')"
- curl --fail-with-body --location --request POST \
- --header 'Accept: application/vnd.github+json' \
- --header "Authorization: Bearer ${GH_TOKEN}" \
- --header 'X-GitHub-Api-Version: 2022-11-28' \
- "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/workflows/llgo-binary-size.yml/dispatches" \
- --data "$payload"
-
pr-scope:
if: github.event_name == 'pull_request'
permissions:
@@ -186,18 +180,29 @@ jobs:
binary-size:
if: >-
- always() && github.event_name != 'repository_dispatch' &&
- (github.event_name != 'pull_request' || needs.pr-scope.outputs.full_matrix == 'true')
+ always() &&
+ (github.event_name != 'pull_request' || needs.pr-scope.outputs.full_matrix == 'true') &&
+ !(github.event_name == 'push' && github.actor == 'github-actions[bot]' &&
+ startsWith(github.event.head_commit.message, 'ci: pin LLGo '))
needs: pr-scope
permissions:
contents: write
runs-on: ubuntu-24.04
timeout-minutes: 120
+ concurrency:
+ # Different LLGo revisions never share a pending slot. Duplicate
+ # notifications for the same revision may be coalesced safely because
+ # published history is keyed by the full LLGo commit.
+ group: llgo-binary-size-${{ github.event.client_payload.llgo_commit || inputs.llgo_commit || github.sha }}
+ cancel-in-progress: false
steps:
- uses: actions/checkout@v4
- name: Read pinned toolchain versions
env:
+ DISPATCH_SOURCE_REPOSITORY: ${{ github.event.client_payload.source_repository }}
+ DISPATCH_LLGO_REPOSITORY: ${{ github.event.client_payload.llgo_repository }}
+ DISPATCH_LLGO_COMMIT: ${{ github.event.client_payload.llgo_commit }}
MANUAL_LLGO_COMMIT: ${{ inputs.llgo_commit }}
run: |
set -euo pipefail
@@ -205,7 +210,19 @@ jobs:
source ci/llgo-size/llgo-version.env
set +a
- if [[ -n "$MANUAL_LLGO_COMMIT" ]]; then
+ if [[ "$GITHUB_EVENT_NAME" == "repository_dispatch" ]]; then
+ if [[ "$DISPATCH_SOURCE_REPOSITORY" != "xgo-dev/llgo" || \
+ "$DISPATCH_LLGO_REPOSITORY" != "xgo-dev/llgo" ]]; then
+ echo "refusing binary-size dispatch from ${DISPATCH_SOURCE_REPOSITORY:-unknown}" >&2
+ exit 1
+ fi
+ if [[ ! "$DISPATCH_LLGO_COMMIT" =~ ^[0-9a-f]{40}$ ]]; then
+ echo "invalid dispatched LLGo commit: ${DISPATCH_LLGO_COMMIT:-missing}" >&2
+ exit 1
+ fi
+ LLGO_REPOSITORY="$DISPATCH_LLGO_REPOSITORY"
+ LLGO_COMMIT="$DISPATCH_LLGO_COMMIT"
+ elif [[ -n "$MANUAL_LLGO_COMMIT" ]]; then
if [[ ! "$MANUAL_LLGO_COMMIT" =~ ^[0-9a-f]{40}$ ]]; then
echo "manual LLGo commit must be a full 40-character SHA" >&2
exit 1
@@ -263,6 +280,13 @@ jobs:
git -C .ci/llgo fetch --progress --no-tags --depth=1 origin "$LLGO_COMMIT"
time_command "Check out pinned LLGo commit" \
git -C .ci/llgo checkout --detach "$LLGO_COMMIT"
+ git -C .ci/llgo fetch --no-tags origin main
+ git -C .ci/llgo rev-list --first-parent --reverse origin/main > .ci/llgo-main-history.txt
+ llgo_main_index="$(awk -v commit="$LLGO_COMMIT" '$0 == commit { print NR; exit }' \
+ .ci/llgo-main-history.txt)"
+ llgo_committed_at="$(git -C .ci/llgo show -s --format=%cI "$LLGO_COMMIT")"
+ printf 'LLGO_MAIN_INDEX=%s\n' "$llgo_main_index" >> "$GITHUB_ENV"
+ printf 'LLGO_COMMITTED_AT=%s\n' "$llgo_committed_at" >> "$GITHUB_ENV"
- name: Build LLGo and the LTO plugin
run: |
set -euo pipefail
@@ -335,14 +359,27 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
- pages_dir="$GITHUB_WORKSPACE/.ci/pages"
- bash ci/llgo-size/prepare-pages-branch.sh \
- "$pages_dir" \
- "https://x-access-token:${PAGES_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
- ci/llgo-size/publish.sh \
- .ci/bent-run/results \
- "$pages_dir" \
- ci/llgo-size/site
+ published=false
+ for attempt in 1 2 3 4; do
+ pages_dir="$GITHUB_WORKSPACE/.ci/pages-$attempt"
+ bash ci/llgo-size/prepare-pages-branch.sh \
+ "$pages_dir" \
+ "https://x-access-token:${PAGES_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
+ if ci/llgo-size/publish.sh \
+ .ci/bent-run/results \
+ "$pages_dir" \
+ ci/llgo-size/site \
+ .ci/llgo-main-history.txt; then
+ published=true
+ break
+ fi
+ echo "Pages advanced during publication; retrying from its latest tip"
+ sleep $((attempt * 2))
+ done
+ if [[ "$published" != true ]]; then
+ echo "failed to publish binary-size history after 4 attempts" >&2
+ exit 1
+ fi
deploy-pages:
if: >-
always() && github.ref == 'refs/heads/main' &&
diff --git a/ci/llgo-size/README.md b/ci/llgo-size/README.md
index 58dea10..69cebaf 100644
--- a/ci/llgo-size/README.md
+++ b/ci/llgo-size/README.md
@@ -47,14 +47,14 @@ and a full-width separator marks the next benchmark. The row set is the union
of every published run, so a historical benchmark remains visible with `—` in
commit columns where it was not produced.
-The `llgo-main-updated` repository-dispatch event from `xgo-dev/llgo` first
-updates `LLGO_COMMIT` on the benchmarks `main` branch, then explicitly starts a
-`workflow_dispatch` build with that complete SHA. This makes the version-file
-update and the published result one ordered operation without relying on a
-`GITHUB_TOKEN`-created push to trigger another workflow. The exact commit is
-retained in `results.json` and the Pages history, so every LLGo `main` update
-has a comparable data point. GitHub requires the receiver workflow to be on
-the benchmarks repository's default branch before it can receive this event.
+The `llgo-main-updated` repository-dispatch event from `xgo-dev/llgo` starts a
+binary-size build directly with the complete dispatched SHA. A separate,
+coalescible job updates `LLGO_COMMIT` on the benchmarks `main` branch to the
+current LLGo main tip; coalescing that bookkeeping cannot discard a benchmark
+revision. Distinct LLGo commits use distinct build concurrency keys, so a burst
+of merges may run in parallel but every LLGo `main` update retains a comparable
+data point. GitHub requires the receiver workflow to be on the benchmarks
+repository's default branch before it can receive this event.
The workflow sources `timing.sh` for its shared CI step timing output.
Bent schedules benchmark/configuration builds serially; each LLGo invocation
uses the compiler's own package-level parallelism.
@@ -81,9 +81,12 @@ separate `llgo-binary-size-pages.yml` workflow. That path publishes the updated
site directly without rebuilding benchmarks, and its publication jobs are
restricted to `main`; pull-request builds cannot publish Pages.
-The benchmark and page-only workflows use separate concurrency queues. A page
-refresh can therefore wait independently without replacing a pending
-binary-size run.
+The benchmark and page-only workflows use separate concurrency keys. Pages
+publication retries from the latest `pages` tip if parallel benchmark runs
+finish together. The index records each result's position on LLGo's first-parent
+`main` history and displays commits in that order rather than build completion
+order; the dashboard opens on the newest page while keeping its columns oldest
+to newest.
Pull requests that change the committed LLGo version, Bent, the LLGo-size
benchmark/configuration files, or the suite definitions used by those cases
diff --git a/ci/llgo-size/enrich_pull_requests.py b/ci/llgo-size/enrich_pull_requests.py
index 1167195..ac973f4 100644
--- a/ci/llgo-size/enrich_pull_requests.py
+++ b/ci/llgo-size/enrich_pull_requests.py
@@ -110,6 +110,34 @@ def repository_from_result(run, data_dir):
return str(result_document(run, data_dir).get("run", {}).get("llgoRepository", ""))
+def load_main_history(path):
+ if path is None:
+ return []
+ with path.open(encoding="utf-8") as history_file:
+ commits = [line.strip().lower() for line in history_file if line.strip()]
+ invalid = [commit for commit in commits if not COMMIT_RE.fullmatch(commit)]
+ if invalid:
+ raise ValueError("invalid LLGo main commit in history: " + repr(invalid[0]))
+ return commits
+
+
+def order_runs(index, main_history):
+ positions = {commit: position for position, commit in enumerate(main_history, start=1)}
+ for run in index.get("runs", []):
+ commit = str(run.get("llgoCommit", "")).lower()
+ if commit in positions:
+ run["llgoMainIndex"] = positions[commit]
+
+ def order_key(run):
+ position = run.get("llgoMainIndex")
+ if isinstance(position, int) and not isinstance(position, bool):
+ return (0, position, "", str(run.get("key", "")))
+ committed_at = str(run.get("llgoCommittedAt") or run.get("createdAt") or "")
+ return (1, 0, committed_at, str(run.get("key", "")))
+
+ index.setdefault("runs", []).sort(key=order_key)
+
+
def legacy_wall_times(run, document, data_dir):
native_path = document.get("native", {}).get("buildTimes")
path = result_path(run, data_dir)
@@ -221,6 +249,11 @@ def parse_args(argv):
"--api-url",
default=os.environ.get("GITHUB_API_URL", "https://api.github.com"),
)
+ parser.add_argument(
+ "--main-history",
+ type=Path,
+ help="first-parent LLGo main commits, oldest first",
+ )
return parser.parse_args(argv)
@@ -232,6 +265,8 @@ def main(argv=None):
index = json.load(index_file)
token = os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN", "")
+ order_runs(index, load_main_history(args.main_history))
+
def lookup(repository, commit):
return github_pull_request_lookup(repository, commit, args.api_url, token)
diff --git a/ci/llgo-size/publish.sh b/ci/llgo-size/publish.sh
index df2dfaf..30c559b 100755
--- a/ci/llgo-size/publish.sh
+++ b/ci/llgo-size/publish.sh
@@ -5,8 +5,9 @@ script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
results_dir="$1"
pages_dir="$2"
site_dir="$3"
+main_history="${4:-}"
if [[ -z "$results_dir" || -z "$pages_dir" || -z "$site_dir" ]]; then
- echo "usage: publish.sh RESULTS_DIR PAGES_DIR SITE_DIR" >&2
+ echo "usage: publish.sh RESULTS_DIR PAGES_DIR SITE_DIR [LLGO_MAIN_HISTORY]" >&2
exit 2
fi
@@ -133,6 +134,8 @@ for path in glob.glob(os.path.join(data_dir, "runs", "*", "results.json")):
"ref": run.get("ref", ""),
"llgoRepository": run.get("llgoRepository", ""),
"llgoCommit": run.get("llgoCommit", ""),
+ "llgoMainIndex": run.get("llgoMainIndex"),
+ "llgoCommittedAt": run.get("llgoCommittedAt", ""),
"goVersion": run.get("goVersion", ""),
"llvmVersion": run.get("llvmVersion", ""),
"workflowUrl": run.get("workflowUrl", ""),
@@ -149,7 +152,6 @@ for path in glob.glob(os.path.join(data_dir, "runs", "*", "results.json")):
if field in previous:
item[field] = previous[field]
runs.append(item)
-runs.sort(key=lambda item: item["createdAt"], reverse=True)
index = {
"schemaVersion": 1,
@@ -160,7 +162,11 @@ with open(runs_index_path, "w", encoding="utf-8") as f:
json.dump(index, f, indent=2)
f.write("\n")
PY
-python3 "$script_dir/enrich_pull_requests.py" "$pages_dir/data/index.json"
+enrich_args=("$pages_dir/data/index.json")
+if [[ -n "$main_history" && -s "$main_history" ]]; then
+ enrich_args+=(--main-history "$main_history")
+fi
+python3 "$script_dir/enrich_pull_requests.py" "${enrich_args[@]}"
git -C "$pages_dir" config user.name "github-actions[bot]"
git -C "$pages_dir" config user.email "41898282+github-actions[bot]@users.noreply.github.com"
diff --git a/ci/llgo-size/report.sh b/ci/llgo-size/report.sh
index b1fdb06..55abc40 100755
--- a/ci/llgo-size/report.sh
+++ b/ci/llgo-size/report.sh
@@ -165,6 +165,8 @@ run = {
"ref": env("LLGO_SIZE_REF", "GITHUB_REF_NAME"),
"llgoRepository": os.environ.get("LLGO_REPOSITORY", ""),
"llgoCommit": os.environ.get("LLGO_COMMIT", ""),
+ "llgoMainIndex": number("LLGO_MAIN_INDEX", ""),
+ "llgoCommittedAt": os.environ.get("LLGO_COMMITTED_AT", ""),
"goVersion": os.environ.get("GO_VERSION", ""),
"llvmVersion": os.environ.get("LLVM_VERSION", ""),
"event": env("LLGO_SIZE_EVENT", "GITHUB_EVENT_NAME"),
diff --git a/ci/llgo-size/site/app.js b/ci/llgo-size/site/app.js
index 700ec05..87890ea 100644
--- a/ci/llgo-size/site/app.js
+++ b/ci/llgo-size/site/app.js
@@ -255,6 +255,11 @@ function findMeta(key) {
return state.index && state.index.runs.find(function (run) { return run.key === key; });
}
+function latestRun() {
+ const runs = state.index && state.index.runs || [];
+ return runs[runs.length - 1];
+}
+
function measureValue(benchmark, config, measure) {
if (!benchmark) return NaN;
if (measure === "size") return Number(benchmark.values && benchmark.values[config]);
@@ -271,7 +276,7 @@ function filteredRuns() {
const query = state.query.trim().toLowerCase();
if (!query) return state.index.runs;
return state.index.runs.filter(function (run) {
- return [commitLabel(run), run.llgoCommit, run.sourceCommit, run.ref, run.createdAt, run.key]
+ return [commitLabel(run), run.llgoCommit, run.sourceCommit, run.ref, run.llgoCommittedAt, run.createdAt, run.key]
.some(function (value) { return String(value || "").toLowerCase().includes(query); });
});
}
@@ -331,7 +336,7 @@ function headerHtml(run) {
const selected = state.selectedKeys.indexOf(run.key);
const marker = selected >= 0 ? '' + (selected === 0 ? "A" : "B") + "" : "";
const commit = commitLinkHtml(run, "" + escapeHtml(commitLabel(run)) + "", "commit-link", commitLinkTitle(run) + ": " + commitLabel(run));
- const date = escapeHtml(dateLabel(run.createdAt));
+ const date = escapeHtml(dateLabel(run.llgoCommittedAt || run.createdAt));
const detail = state.comparisonMode
? '"
: '' + date + "";
@@ -441,8 +446,9 @@ function goVersionLabel(value) {
}
async function renderEnvironment() {
- const key = state.selectedKeys.length === 2 ? state.selectedKeys[1] : state.index.runs[0].key;
- const meta = findMeta(key) || state.index.runs[0];
+ const newest = latestRun();
+ const key = state.selectedKeys.length === 2 ? state.selectedKeys[1] : newest.key;
+ const meta = findMeta(key) || newest;
const document = await loadRun(meta);
const run = document.run || {};
dom.envRunner.textContent = normalizeRunner(run);
@@ -463,8 +469,7 @@ async function renderTables() {
function chartRuns() {
const limit = Number(dom.historyRange.value);
- const newest = limit > 0 ? state.index.runs.slice(0, limit) : state.index.runs.slice();
- return newest.reverse();
+ return limit > 0 ? state.index.runs.slice(-limit) : state.index.runs.slice();
}
function chartBand(documents, metas, benchmarkName, measure, title) {
@@ -635,9 +640,10 @@ async function main() {
if (!response.ok) throw new Error("Cannot load the run index");
state.index = await response.json();
if (!state.index.runs || !state.index.runs.length) throw new Error("No benchmark runs are available");
+ state.page = Math.max(1, Math.ceil(state.index.runs.length / state.pageSize));
state.benchmarkNames = sortedBenchmarkNames(state.index.benchmarkNames);
if (!state.benchmarkNames.length) {
- state.benchmarkNames = benchmarkNamesFromDocuments([await loadRun(state.index.runs[0])]);
+ state.benchmarkNames = benchmarkNamesFromDocuments([await loadRun(latestRun())]);
}
state.activeBenchmark = state.benchmarkNames[0] || "";
configs.forEach(function (config) { state.activeConfigs.add(config); });
@@ -646,7 +652,7 @@ async function main() {
renderConfigFilter();
attachEvents();
await refreshAll();
- dom.status.textContent = "Updated " + dateLabel(state.index.generatedAt || state.index.runs[0].createdAt);
+ dom.status.textContent = "Updated " + dateLabel(state.index.generatedAt || latestRun().createdAt);
} catch (error) {
dom.status.textContent = error.message;
dom.status.classList.add("error");
diff --git a/ci/llgo-size/test_enrich_pull_requests.py b/ci/llgo-size/test_enrich_pull_requests.py
index 1467e27..f4eef28 100644
--- a/ci/llgo-size/test_enrich_pull_requests.py
+++ b/ci/llgo-size/test_enrich_pull_requests.py
@@ -79,6 +79,43 @@ def test_retries_public_lookup_when_repository_token_is_scoped(self):
class IndexEnrichmentTest(unittest.TestCase):
+ def test_orders_runs_by_llgo_main_history_instead_of_build_completion(self):
+ first = "a" * 40
+ second = "b" * 40
+ index = {
+ "runs": [
+ {
+ "key": second,
+ "llgoCommit": second,
+ "createdAt": "2026-08-21T01:00:00Z",
+ },
+ {
+ "key": first,
+ "llgoCommit": first,
+ "createdAt": "2026-08-21T02:00:00Z",
+ },
+ ]
+ }
+
+ MODULE.order_runs(index, [first, second])
+
+ self.assertEqual([run["key"] for run in index["runs"]], [first, second])
+ self.assertEqual([run["llgoMainIndex"] for run in index["runs"]], [1, 2])
+
+ def test_places_non_main_runs_after_topological_history(self):
+ main = "a" * 40
+ manual = "c" * 40
+ index = {
+ "runs": [
+ {"key": manual, "llgoCommit": manual, "createdAt": "2026-01-01T00:00:00Z"},
+ {"key": main, "llgoCommit": main, "createdAt": "2026-08-01T00:00:00Z"},
+ ]
+ }
+
+ MODULE.order_runs(index, [main])
+
+ self.assertEqual([run["key"] for run in index["runs"]], [main, manual])
+
def test_builds_compact_trends_and_historical_benchmark_union(self):
with tempfile.TemporaryDirectory() as temporary:
data_dir = Path(temporary)