diff --git a/.github/workflows/llgo-binary-size.yml b/.github/workflows/llgo-binary-size.yml
index 4f48fef..8c9c497 100644
--- a/.github/workflows/llgo-binary-size.yml
+++ b/.github/workflows/llgo-binary-size.yml
@@ -99,31 +99,37 @@ jobs:
contents: read
runs-on: ubuntu-24.04
outputs:
- version_changed: ${{ steps.changed.outputs.version_changed }}
+ full_matrix: ${{ steps.changed.outputs.full_matrix }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- - name: Detect a committed LLGo version change
+ - name: Detect an LLGo pin or benchmark-case change
id: changed
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
PR_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
- if git diff --name-only "$BASE_SHA" "$PR_SHA" | grep -Fxq 'ci/llgo-size/llgo-version.env'; then
- echo 'version_changed=true' >> "$GITHUB_OUTPUT"
- echo 'The PR changes the committed LLGo version; the full matrix will run.'
+ if git diff --quiet "$BASE_SHA" "$PR_SHA" -- \
+ ci/llgo-size/llgo-version.env \
+ ci/llgo-size/bin/go \
+ cmd/bent \
+ cmd/bent/configs/benchmarks-llgo-size.toml \
+ cmd/bent/configs/configurations-llgo-size.toml \
+ cmd/bent/configs/suites.toml; then
+ echo 'full_matrix=false' >> "$GITHUB_OUTPUT"
+ echo 'The PR does not change the LLGo pin, Bent, or benchmark cases; only Go validation will run.'
else
- echo 'version_changed=false' >> "$GITHUB_OUTPUT"
- echo 'The PR does not change the committed LLGo version; only Go validation will run.'
+ echo 'full_matrix=true' >> "$GITHUB_OUTPUT"
+ echo 'The PR changes the LLGo pin, Bent, or benchmark cases; the full matrix will run without publishing.'
fi
binary-size:
if: >-
always() && github.event_name != 'repository_dispatch' &&
- (github.event_name != 'pull_request' || needs.pr-scope.outputs.version_changed == 'true')
+ (github.event_name != 'pull_request' || needs.pr-scope.outputs.full_matrix == 'true')
needs: pr-scope
permissions:
contents: write
@@ -297,7 +303,7 @@ jobs:
pr-validation:
if: >-
always() && github.event_name == 'pull_request' &&
- needs.pr-scope.result == 'success' && needs.pr-scope.outputs.version_changed != 'true'
+ needs.pr-scope.result == 'success' && needs.pr-scope.outputs.full_matrix != 'true'
needs: pr-scope
permissions:
contents: read
diff --git a/ci/llgo-size/README.md b/ci/llgo-size/README.md
index da9810d..5a1164c 100644
--- a/ci/llgo-size/README.md
+++ b/ci/llgo-size/README.md
@@ -62,12 +62,12 @@ The benchmark and page-only workflows use separate concurrency queues. A page
refresh can therefore wait independently without replacing a pending
binary-size run.
-Pull requests are split by whether they change `llgo-version.env`. A normal PR
-uses a separate Go-only validation job: dependency acquisition, compilation,
-and execution are checked without building LLGo or the four LLGo binary-size
-variants. A PR that changes the committed LLGo version runs the full five-way
-matrix and uploads the `llgo-binary-size` artifact for review, but still does
-not publish history.
+Pull requests that change the committed LLGo version, Bent, the LLGo-size
+benchmark/configuration files, or the suite definitions used by those cases
+run the full five-way matrix and upload the `llgo-binary-size` artifact for
+review. They do not publish history. A PR outside that scope uses the separate
+Go-only validation job, so dependency acquisition, compilation, and execution
+remain checked without rebuilding LLGo or the four LLGo binary-size variants.
Published history is keyed by the full LLGo commit, so rerunning one commit
updates its existing entry instead of adding another build-round entry.
diff --git a/ci/llgo-size/site/app.js b/ci/llgo-size/site/app.js
index 8f2915a..b40aa71 100644
--- a/ci/llgo-size/site/app.js
+++ b/ci/llgo-size/site/app.js
@@ -1,7 +1,23 @@
-const state = { index: null, runs: new Map() };
+const state = {
+ index: null,
+ runs: new Map(),
+ benchmarkNames: [],
+ activeBenchmarks: new Set(),
+ activeConfigs: new Set(),
+ page: 1,
+ pageSize: 20,
+ query: "",
+};
+
const newerSelect = document.querySelector("#newer-run");
const baselineSelect = document.querySelector("#baseline-run");
const status = document.querySelector("#status");
+const comparisonGrid = document.querySelector("#comparison-grid");
+const pagination = document.querySelector("#pagination");
+const filterInput = document.querySelector("#commit-filter");
+const pageSizeSelect = document.querySelector("#page-size");
+const historyMetric = document.querySelector("#history-metric");
+const historyRange = document.querySelector("#history-range");
const configs = [
"Go",
@@ -27,7 +43,8 @@ const compactConfigLabels = {
LLGoFullLTOGlobalDCEPlugin: "LTO + DCE + P",
};
-const seriesColors = ["#315efb", "#7c3aed", "#d97706", "#0f766e", "#c92a2a"];
+const seriesColors = ["#2457d6", "#7c3aed", "#d97706", "#0f766e", "#c92a2a"];
+const seriesDashes = ["", "7 3", "2 3"];
function escapeHtml(value) {
return String(value == null ? "" : value).replace(/[&<>"']/g, function (char) {
@@ -48,8 +65,9 @@ function formatBytes(value) {
return (number < 0 ? "-" : "") + scaled.toFixed(3) + " " + units[unit];
}
-function deltaClass(delta) {
- return delta > 0 ? "bad" : delta < 0 ? "good" : "flat";
+function formatPercent(value, digits) {
+ if (!Number.isFinite(value)) return "—";
+ return (value > 0 ? "+" : "") + value.toFixed(digits == null ? 3 : digits) + "%";
}
function percentDelta(value, base) {
@@ -57,18 +75,12 @@ function percentDelta(value, base) {
return ((Number(value) - Number(base)) / Number(base)) * 100;
}
-function formatDelta(delta, label, prominent) {
- if (!Number.isFinite(delta)) return "";
- const sign = delta > 0 ? "+" : "";
- const className = deltaClass(delta);
- if (prominent) {
- return '' + sign + delta.toFixed(3) + '% ' + escapeHtml(label) + " ";
- }
- return '' + sign + delta.toFixed(3) + "% " + escapeHtml(label) + " ";
+function deltaClass(delta) {
+ return delta > 0 ? "bad" : delta < 0 ? "good" : "flat";
}
function shortSha(value) {
- return value ? value.slice(0, 10) : "—";
+ return value ? String(value).slice(0, 10) : "—";
}
function dateLabel(value) {
@@ -88,7 +100,12 @@ function runLabel(run) {
return commitLabel(run) + " · " + dateLabel(run.createdAt);
}
+function benchmarkMap(document) {
+ return new Map((document.benchmarks || []).map(function (item) { return [item.name, item]; }));
+}
+
async function loadRun(meta) {
+ if (!meta) return null;
if (!state.runs.has(meta.key)) {
const response = await fetch("data/" + meta.path, { cache: "no-store" });
if (!response.ok) throw new Error("Cannot load " + meta.path);
@@ -97,196 +114,343 @@ async function loadRun(meta) {
return state.runs.get(meta.key);
}
-function fillSelects(runs) {
- newerSelect.replaceChildren(...runs.map(function (run) {
- const option = document.createElement("option");
- option.value = run.key;
- option.textContent = runLabel(run);
- return option;
- }));
- baselineSelect.replaceChildren();
- const none = document.createElement("option");
- none.value = "";
- none.textContent = "No baseline";
- baselineSelect.appendChild(none);
- for (const run of runs) {
- const option = document.createElement("option");
- option.value = run.key;
- option.textContent = runLabel(run);
- baselineSelect.appendChild(option);
+function findMeta(key) {
+ return state.index.runs.find(function (run) { return run.key === key; });
+}
+
+function selection() {
+ const baselineMeta = findMeta(baselineSelect.value);
+ const newerMeta = findMeta(newerSelect.value);
+ return { baselineMeta: baselineMeta, newerMeta: newerMeta };
+}
+
+function fillRunSelects(runs) {
+ function options() {
+ return runs.map(function (run) {
+ return '' + escapeHtml(runLabel(run)) + " ";
+ }).join("");
}
- newerSelect.selectedIndex = 0;
- baselineSelect.value = runs.length > 1 ? runs[1].key : "";
+ baselineSelect.innerHTML = options();
+ newerSelect.innerHTML = options();
+ baselineSelect.value = runs.length > 1 ? runs[1].key : runs[0].key;
+ newerSelect.value = runs[0].key;
}
-function renderMeta(newer, baseline) {
- const run = newer.run;
- const workflow = run.workflowUrl
- ? 'workflow ' + escapeHtml(runNumber(run)) + " "
- : "run " + escapeHtml(runNumber(run));
+function renderRunMeta(baseline, newer) {
+ const item = function (label, run, extra) {
+ if (!run) return "";
+ const workflow = run.workflowUrl
+ ? 'workflow ' + escapeHtml(runNumber(run)) + " "
+ : "run " + escapeHtml(runNumber(run));
+ return '
' + label + " " + escapeHtml(commitLabel(run)) + " " +
+ '' + escapeHtml(dateLabel(run.createdAt)) + (extra ? " · " + extra : "") + " " +
+ '' + workflow + "
";
+ };
document.querySelector("#run-meta").innerHTML =
- 'Newer ' + escapeHtml(dateLabel(run.createdAt)) + "
" +
- 'LLGo ' + escapeHtml(shortSha(run.llgoCommit)) + "
" +
- 'Toolchain ' + escapeHtml(run.goVersion || "—") + " / LLVM " + escapeHtml(run.llvmVersion || "—") + "
" +
- 'Link ' + workflow + "
" +
- 'Baseline ' + (baseline ? escapeHtml(dateLabel(baseline.run.createdAt)) : "none") + "
";
+ item("A · baseline", baseline, baseline ? escapeHtml(baseline.ref || "—") : "") +
+ item("B · selected", newer, newer ? escapeHtml(newer.ref || "—") : "") +
+ 'Toolchain ' + escapeHtml(newer && newer.goVersion || "—") + " / LLVM " + escapeHtml(newer && newer.llvmVersion || "—") + " " +
+ 'Each LLGo cell remains relative to Go from the same commit.
';
}
-function benchmarkMap(document) {
- return new Map((document.benchmarks || []).map(function (item) { return [item.name, item]; }));
+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]
+ .some(function (value) { return String(value || "").toLowerCase().includes(query); });
+ });
}
-function comparisonCell(benchmark, config, otherBenchmark) {
- if (!benchmark || !benchmark.values || benchmark.values[config] == null) {
- return '— ';
- }
- const value = benchmark.values[config];
- const goValue = benchmark.values.Go;
+function cellHtml(benchmark, config, baselineBenchmark, showComparison) {
+ if (!benchmark || !benchmark.values || benchmark.values[config] == null) return '— ';
+ const value = Number(benchmark.values[config]);
+ const goValue = Number(benchmark.values.Go);
const relative = config === "Go"
? 'reference '
- : formatDelta(percentDelta(value, goValue), "vs Go", true);
- const previous = otherBenchmark && otherBenchmark.values && otherBenchmark.values[config] != null
- ? formatDelta(percentDelta(value, otherBenchmark.values[config]), "vs old", false)
+ : '' + formatPercent(percentDelta(value, goValue), 1) + " vs Go ";
+ const oldValue = baselineBenchmark && baselineBenchmark.values ? Number(baselineBenchmark.values[config]) : NaN;
+ const comparison = showComparison && Number.isFinite(oldValue)
+ ? 'Δ A ' + formatPercent(percentDelta(value, oldValue), 1) + " "
: "";
- return '' + formatBytes(value) + " " +
- '' + relative + "
" + (previous ? '' + previous + "
" : "") + " ";
-}
-
-function renderComparison(newer, baseline) {
- const grid = document.querySelector("#comparison-grid");
- const newerByName = benchmarkMap(newer);
- const baselineByName = baseline ? benchmarkMap(baseline) : new Map();
- const benchmarkNames = Array.from(new Set(Array.from(newerByName.keys()).concat(Array.from(baselineByName.keys())))).sort();
-
- const newerHeading = "Newer · " + commitLabel(newer.run);
- const baselineHeading = baseline ? "Older · " + commitLabel(baseline.run) : "Older";
- grid.innerHTML = benchmarkNames.map(function (name) {
- const newerBenchmark = newerByName.get(name);
- const baselineBenchmark = baselineByName.get(name);
- const rows = configs.map(function (config) {
- const label = configLabels[config] || config;
- return "" +
- escapeHtml(compactConfigLabels[config] || label) + " " +
- comparisonCell(baselineBenchmark, config, null) +
- comparisonCell(newerBenchmark, config, baselineBenchmark) + " ";
+ return '' + formatBytes(value) + " " + relative + " " + comparison + " ";
+}
+
+function pageNumbers(page, pageCount) {
+ const values = new Set([1, pageCount, page - 1, page, page + 1]);
+ return Array.from(values).filter(function (value) { return value >= 1 && value <= pageCount; }).sort(function (a, b) { return a - b; });
+}
+
+function renderPagination(runCount) {
+ const pageCount = Math.max(1, Math.ceil(runCount / state.pageSize));
+ state.page = Math.min(state.page, pageCount);
+ const numbers = pageNumbers(state.page, pageCount);
+ let previous = 0;
+ const pageButtons = numbers.map(function (number) {
+ const gap = number - previous > 1 ? '… ' : "";
+ previous = number;
+ return gap + '' + number + " ";
+ }).join("");
+ pagination.innerHTML = 'Previous " +
+ pageButtons + 'Next ";
+}
+
+async function renderComparison() {
+ const selected = selection();
+ const baseline = await loadRun(selected.baselineMeta);
+ const newer = await loadRun(selected.newerMeta);
+ renderRunMeta(selected.baselineMeta, selected.newerMeta);
+
+ const runs = filteredRuns();
+ renderPagination(runs.length);
+ const start = (state.page - 1) * state.pageSize;
+ const pageRuns = runs.slice(start, start + state.pageSize);
+ const documents = await Promise.all(pageRuns.map(loadRun));
+ const baselineByName = benchmarkMap(baseline || {});
+ const columns = state.benchmarkNames.length ? state.benchmarkNames : [];
+ const span = columns.length * configs.length;
+ const headerGroups = columns.map(function (name) {
+ return '' + escapeHtml(name) + " ";
+ }).join("");
+ const headerModes = columns.map(function () {
+ return configs.map(function (config) {
+ return '' + escapeHtml(compactConfigLabels[config]) + " ";
}).join("");
- return '' + escapeHtml(name) + " Mode " +
- escapeHtml(baselineHeading) + " " + escapeHtml(newerHeading) + " " + rows + "
";
}).join("");
- document.querySelector("#table-note").textContent = baseline
- ? "Older results are on the left. Newer LLGo results show the prominent difference from Go and the smaller difference from the selected older run."
- : "Each LLGo result shows its difference from Go in the same run.";
-}
-
-function renderHistoryTable(runs) {
- const body = document.querySelector("#history-body");
- body.replaceChildren();
- for (const run of runs) {
- const row = document.createElement("tr");
- row.innerHTML =
- "" + (run.workflowUrl ? '' : "") + escapeHtml(commitLabel(run)) + (run.workflowUrl ? " " : "") + " " +
- "" + escapeHtml(dateLabel(run.createdAt)) + " " +
- "" + escapeHtml(run.goVersion || "—") + " " +
- "" + escapeHtml(run.llvmVersion || "—") + " " +
- "" + escapeHtml(run.ref || "—") + " ";
- body.appendChild(row);
- }
+
+ const rows = documents.map(function (document, index) {
+ const meta = pageRuns[index];
+ const byName = benchmarkMap(document || {});
+ const isBaseline = selected.baselineMeta && meta.key === selected.baselineMeta.key;
+ const isNewer = selected.newerMeta && meta.key === selected.newerMeta.key;
+ const rowClass = (isBaseline ? " baseline-row" : "") + (isNewer ? " selected-row" : "");
+ const roleButtons = 'A ' +
+ 'B
';
+ const values = columns.map(function (name) {
+ const benchmark = byName.get(name);
+ const baselineBenchmark = baselineByName.get(name);
+ return configs.map(function (config) {
+ return cellHtml(benchmark, config, baselineBenchmark, isNewer && !isBaseline);
+ }).join("");
+ }).join("");
+ const workflow = meta.workflowUrl ? '' + escapeHtml(commitLabel(meta)) + " " : '' + escapeHtml(commitLabel(meta)) + "";
+ return '' + roleButtons + ' ' + workflow + " " +
+ '' + escapeHtml(dateLabel(meta.createdAt)) + ' ' + escapeHtml(meta.ref || "—") + " " + values + " ";
+ }).join("");
+
+ comparisonGrid.style.minWidth = (560 + span * 124) + "px";
+ comparisonGrid.innerHTML = 'Pick Commit Created Branch ' + headerGroups + '' + headerModes + " " +
+ (rows || 'No commits match this filter. ') + " ";
+ document.querySelector("#commit-count").textContent = runs.length + " commit" + (runs.length === 1 ? "" : "s") + " · showing " + (runs.length ? (start + 1) + "–" + Math.min(start + state.pageSize, runs.length) : "0");
+ document.querySelector("#table-note").textContent = "A/B selections persist while you page or filter commits; use the horizontal scrollbar for benchmark columns.";
}
-function chartPath(points, x, y) {
- return points.map(function (point, index) {
- return (index === 0 ? "M" : "L") + x(point.index).toFixed(2) + " " + y(point.value).toFixed(2);
- }).join(" ");
-}
-
-function renderHistoryChart(name, documents) {
- const values = [];
- for (const document of documents) {
- const benchmark = benchmarkMap(document).get(name);
- if (!benchmark) continue;
- for (const config of configs) {
- if (Number.isFinite(Number(benchmark.values[config]))) values.push(Number(benchmark.values[config]));
+function renderChoiceControls() {
+ const benchmarks = document.querySelector("#benchmark-filter");
+ benchmarks.innerHTML = state.benchmarkNames.map(function (name) {
+ return '' + escapeHtml(name) + " ";
+ }).join("");
+ const configFilter = document.querySelector("#config-filter");
+ configFilter.innerHTML = configs.map(function (config, index) {
+ return '' + escapeHtml(compactConfigLabels[config]) + " ";
+ }).join("");
+}
+
+function metricValue(documents, benchmarkName, config, metric) {
+ let previous = null;
+ return documents.map(function (document, index) {
+ const benchmark = benchmarkMap(document).get(benchmarkName);
+ const value = benchmark && benchmark.values ? Number(benchmark.values[config]) : NaN;
+ const goValue = benchmark && benchmark.values ? Number(benchmark.values.Go) : NaN;
+ let plotted = value;
+ if (metric === "vs-go") plotted = config === "Go" ? 0 : percentDelta(value, goValue);
+ if (metric === "commit-delta") {
+ plotted = previous == null ? NaN : percentDelta(value, previous);
+ previous = Number.isFinite(value) ? value : previous;
}
+ return { index: index, value: Number(plotted), document: document, raw: value, go: goValue };
+ }).filter(function (point) { return Number.isFinite(point.value); });
+}
+
+function chartMetricLabel(metric) {
+ if (metric === "vs-go") return "vs Go (%)";
+ if (metric === "commit-delta") return "change from previous commit (%)";
+ return "binary size";
+}
+
+function chartFormat(value, metric) {
+ return metric === "absolute" ? formatBytes(value) : formatPercent(value, 1);
+}
+
+function chartPath(points, x, y) {
+ return points.map(function (point, index) { return (index === 0 ? "M" : "L") + x(point.index).toFixed(2) + " " + y(point.value).toFixed(2); }).join(" ");
+}
+
+function runValue(document, benchmarkName, config) {
+ const benchmark = benchmarkMap(document || {}).get(benchmarkName);
+ return benchmark && benchmark.values ? Number(benchmark.values[config]) : NaN;
+}
+
+function renderInspector(baseline, newer) {
+ const name = Array.from(state.activeBenchmarks)[0];
+ const config = Array.from(state.activeConfigs)[0];
+ const inspector = document.querySelector("#history-inspector");
+ if (!name || !config || !newer) {
+ inspector.innerHTML = 'Select at least one benchmark and build mode to inspect a series.
';
+ return;
}
- if (!values.length) return "";
- const width = 700;
- const height = 260;
- const left = 62;
- const right = 14;
- const top = 18;
- const bottom = 42;
+ const newerValue = runValue(newer, name, config);
+ const newerGo = runValue(newer, name, "Go");
+ const baselineValue = runValue(baseline, name, config);
+ const relative = config === "Go" ? 0 : percentDelta(newerValue, newerGo);
+ const comparison = percentDelta(newerValue, baselineValue);
+ inspector.innerHTML = 'Selected series
' + escapeHtml(name) + " · " + escapeHtml(compactConfigLabels[config]) + ' ' + formatBytes(newerValue) + " " +
+ '' + (config === "Go" ? "Go reference" : formatPercent(relative, 1) + " vs Go") + "
" +
+ '
A → B ' + formatPercent(comparison, 1) + "
Baseline " + escapeHtml(commitLabel(baseline.run || {})) + "
Selected " + escapeHtml(commitLabel(newer.run || {})) + " ";
+}
+
+async function renderHistory() {
+ const range = Number(historyRange.value);
+ const metas = (range ? state.index.runs.slice(0, range) : state.index.runs).slice().reverse();
+ const documents = await Promise.all(metas.map(loadRun));
+ const metric = historyMetric.value;
+ const series = [];
+ Array.from(state.activeBenchmarks).forEach(function (name, benchmarkIndex) {
+ Array.from(state.activeConfigs).forEach(function (config) {
+ const points = metricValue(documents, name, config, metric);
+ if (points.length) series.push({ name: name, config: config, benchmarkIndex: benchmarkIndex, points: points });
+ });
+ });
+ const chart = document.querySelector("#history-chart");
+ const selected = selection();
+ const baseline = await loadRun(selected.baselineMeta);
+ const newer = await loadRun(selected.newerMeta);
+ renderInspector(baseline, newer);
+ if (!series.length || !documents.length) {
+ chart.innerHTML = 'No matching history is available for this selection.
';
+ return;
+ }
+ const values = series.flatMap(function (item) { return item.points.map(function (point) { return point.value; }); });
+ const width = 1080;
+ const height = 380;
+ const left = 72;
+ const right = 22;
+ const top = 30;
+ const bottom = 58;
const minimum = Math.min.apply(null, values);
const maximum = Math.max.apply(null, values);
- const range = maximum === minimum ? Math.max(1, maximum * 0.05) : maximum - minimum;
- const yMin = Math.max(0, minimum - range * 0.08);
- const yMax = maximum + range * 0.08;
+ const spread = maximum === minimum ? Math.max(1, Math.abs(maximum) * 0.1) : maximum - minimum;
+ const yMin = metric === "absolute" ? Math.max(0, minimum - spread * 0.08) : minimum - spread * 0.12;
+ const yMax = maximum + spread * 0.12;
const x = function (index) { return documents.length === 1 ? (left + width - right) / 2 : left + index * (width - left - right) / (documents.length - 1); };
const y = function (value) { return top + (yMax - value) * (height - top - bottom) / (yMax - yMin); };
- const grid = [0, 0.5, 1].map(function (ratio) {
+ const grid = [0, 0.25, 0.5, 0.75, 1].map(function (ratio) {
const value = yMin + (yMax - yMin) * ratio;
const position = y(value);
- return ' ' +
- '' + escapeHtml(formatBytes(value)) + " ";
+ return '' + escapeHtml(chartFormat(value, metric)) + " ";
}).join("");
- const series = configs.map(function (config, configIndex) {
- const points = [];
- documents.forEach(function (document, index) {
- const benchmark = benchmarkMap(document).get(name);
- const value = benchmark && benchmark.values ? Number(benchmark.values[config]) : NaN;
- if (Number.isFinite(value)) points.push({ index: index, value: value, document: document });
- });
- if (!points.length) return "";
- const color = seriesColors[configIndex % seriesColors.length];
- return ' ' + points.map(function (point) {
- return '' +
- escapeHtml(name + " · " + config + " · " + commitLabel(point.document.run) + ": " + formatBytes(point.value)) + " ";
+ const markers = [[selected.baselineMeta, "A"], [selected.newerMeta, "B"]].map(function (item) {
+ const index = metas.findIndex(function (meta) { return item[0] && meta.key === item[0].key; });
+ if (index < 0) return "";
+ const position = x(index);
+ return '' + item[1] + " ";
+ }).join("");
+ const lines = series.map(function (item) {
+ const configIndex = configs.indexOf(item.config);
+ const color = seriesColors[configIndex];
+ const dash = seriesDashes[item.benchmarkIndex % seriesDashes.length];
+ const label = item.name + " · " + compactConfigLabels[item.config];
+ return ' ' + item.points.map(function (point) {
+ return '' + escapeHtml(label + " · " + commitLabel(point.document.run) + ": " + chartFormat(point.value, metric)) + " ";
}).join("");
}).join("");
const labels = documents.map(function (document, index) {
- return '' + escapeHtml(commitLabel(document.run)) + " ";
+ if (documents.length > 12 && index % Math.ceil(documents.length / 10) !== 0 && index !== documents.length - 1) return "";
+ return '' + escapeHtml(commitLabel(document.run)) + " ";
}).join("");
- const legend = configs.map(function (config, index) {
- return '' + escapeHtml(configLabels[config] || config) + " ";
+ const legend = series.map(function (item) {
+ const configIndex = configs.indexOf(item.config);
+ return ' ' + escapeHtml(item.name + " · " + compactConfigLabels[item.config]) + " ";
}).join("");
- return '' + escapeHtml(name) + " ' + grid + series + labels + " " + legend + "
";
-}
-
-async function renderHistoryCharts(runs) {
- const container = document.querySelector("#history-charts");
- const documents = await Promise.all(runs.slice().reverse().map(loadRun));
- const names = Array.from(new Set(documents.flatMap(function (document) {
- return (document.benchmarks || []).map(function (benchmark) { return benchmark.name; });
- }))).sort();
- container.innerHTML = names.map(function (name) { return renderHistoryChart(name, documents); }).join("") || 'No benchmark history is available yet.
';
+ chart.innerHTML = '' + escapeHtml(chartMetricLabel(metric)) + ' ' + documents.length + " commits
' + grid + markers + lines + labels + ' ' + legend + "
";
}
-async function update() {
+async function refresh() {
try {
- const newerMeta = state.index.runs.find(function (run) { return run.key === newerSelect.value; });
- const baselineMeta = state.index.runs.find(function (run) { return run.key === baselineSelect.value; });
- const newer = await loadRun(newerMeta);
- const baseline = baselineMeta && baselineMeta.key !== newerMeta.key ? await loadRun(baselineMeta) : null;
- renderMeta(newer, baseline);
- renderComparison(newer, baseline);
- status.textContent = state.index.runs.length + " run" + (state.index.runs.length === 1 ? "" : "s") + " published";
+ await Promise.all([renderComparison(), renderHistory()]);
+ status.textContent = state.index.runs.length + " published commit" + (state.index.runs.length === 1 ? "" : "s");
+ status.className = "status";
} catch (error) {
status.textContent = error.message;
status.className = "status error";
}
}
+function attachEvents() {
+ newerSelect.addEventListener("change", refresh);
+ baselineSelect.addEventListener("change", function () {
+ if (baselineSelect.value === newerSelect.value && state.index.runs.length > 1) newerSelect.value = state.index.runs[0].key === baselineSelect.value ? state.index.runs[1].key : state.index.runs[0].key;
+ refresh();
+ });
+ filterInput.addEventListener("input", function () { state.query = filterInput.value; state.page = 1; renderComparison(); });
+ pageSizeSelect.addEventListener("change", function () { state.pageSize = Number(pageSizeSelect.value); state.page = 1; renderComparison(); });
+ pagination.addEventListener("click", function (event) {
+ const button = event.target.closest("button[data-page]");
+ if (!button || button.disabled) return;
+ state.page = Number(button.dataset.page);
+ renderComparison();
+ });
+ comparisonGrid.addEventListener("click", function (event) {
+ const button = event.target.closest("button[data-select-role]");
+ if (!button) return;
+ const role = button.dataset.selectRole;
+ const key = button.dataset.runKey;
+ if (role === "baseline") baselineSelect.value = key;
+ else newerSelect.value = key;
+ if (baselineSelect.value === newerSelect.value && state.index.runs.length > 1) {
+ const alternative = state.index.runs.find(function (run) { return run.key !== key; });
+ if (role === "baseline") newerSelect.value = alternative.key;
+ else baselineSelect.value = alternative.key;
+ }
+ refresh();
+ });
+ document.querySelector("#benchmark-filter").addEventListener("click", function (event) {
+ const button = event.target.closest("button[data-benchmark]");
+ if (!button) return;
+ const name = button.dataset.benchmark;
+ if (state.activeBenchmarks.has(name) && state.activeBenchmarks.size > 1) state.activeBenchmarks.delete(name);
+ else state.activeBenchmarks.add(name);
+ renderChoiceControls();
+ renderHistory();
+ });
+ document.querySelector("#config-filter").addEventListener("click", function (event) {
+ const button = event.target.closest("button[data-config]");
+ if (!button) return;
+ const config = button.dataset.config;
+ if (state.activeConfigs.has(config) && state.activeConfigs.size > 1) state.activeConfigs.delete(config);
+ else state.activeConfigs.add(config);
+ renderChoiceControls();
+ renderHistory();
+ });
+ historyMetric.addEventListener("change", renderHistory);
+ historyRange.addEventListener("change", renderHistory);
+}
+
async function main() {
try {
const response = await fetch("data/index.json", { cache: "no-store" });
if (!response.ok) throw new Error("No published benchmark results yet");
state.index = await response.json();
- const runs = state.index.runs || [];
- if (!runs.length) throw new Error("No published benchmark results yet");
- fillSelects(runs);
- renderHistoryTable(runs);
- newerSelect.addEventListener("change", update);
- baselineSelect.addEventListener("change", update);
- await Promise.all([update(), renderHistoryCharts(runs)]);
+ state.index.runs = (state.index.runs || []).slice().sort(function (a, b) { return String(b.createdAt).localeCompare(String(a.createdAt)); });
+ if (!state.index.runs.length) throw new Error("No published benchmark results yet");
+ const latest = await loadRun(state.index.runs[0]);
+ state.benchmarkNames = Array.from(benchmarkMap(latest).keys()).sort();
+ state.activeBenchmarks = new Set(state.benchmarkNames.slice(0, Math.min(3, state.benchmarkNames.length)));
+ state.activeConfigs = new Set(configs);
+ fillRunSelects(state.index.runs);
+ renderChoiceControls();
+ attachEvents();
+ await refresh();
} catch (error) {
status.textContent = error.message;
status.className = "status error";
diff --git a/ci/llgo-size/site/index.html b/ci/llgo-size/site/index.html
index a0f44fd..2ac2c8c 100644
--- a/ci/llgo-size/site/index.html
+++ b/ci/llgo-size/site/index.html
@@ -4,24 +4,27 @@
LLGo binary-size history
-
+
-
Compare runs
-
The newer and baseline runs become adjacent columns, grouped by use case and build mode.
+
commit comparison
+
Choose a baseline and selected commit
+
The selected commit row displays its change from the baseline, while every LLGo value keeps its own difference from Go.
- Newer run
- Baseline run
+ Baseline (A)
+ Selected (B)
@@ -29,27 +32,38 @@ Compare runs
-
total size · LLGo relative to Go
-
Size comparison
+
commits as rows · benchmark modes as columns
+
Commit comparison matrix
-
+
+
+
Rows per page10 20 50
+
+
Filter commits
+
+
-
+
-
one chart per benchmark
-
Size history
+
interactive trends
+
Explore history
+
History rangeAll commits Latest 10 Latest 20 Latest 50
+
+
+
+
+
MetricAbsolute size vs Go Commit delta
-
-
-
- LLGo commit Created Go LLVM Workflow
-
-
+
@@ -59,6 +73,6 @@
Size history
Raw run index
-
+