Skip to content

Commit 10b07e8

Browse files
committed
perf(benchmarks-website): trim inline JSON to LANDING_INLINE_N=100
Saves bytes on the cold landing page; chart-init.js refetches with a wider window when the user actually zooms past the inlined range. Signed-off-by: Claude <noreply@anthropic.com>
1 parent c52d934 commit 10b07e8

6 files changed

Lines changed: 234 additions & 12 deletions

File tree

benchmarks-website/server/src/html.rs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,13 @@ const CHART_INIT_JS: &[u8] = include_bytes!("../static/chart-init.js");
8080
const STYLE_CSS: &[u8] = include_bytes!("../static/style.css");
8181
const VORTEX_BLACK_SVG: &[u8] = include_bytes!("../../public/vortex_black_nobg.svg");
8282
const VORTEX_WHITE_SVG: &[u8] = include_bytes!("../../public/vortex_white_nobg.svg");
83-
const STATIC_ASSET_VERSION: &str = "bench-v3-ui-15";
83+
const STATIC_ASSET_VERSION: &str = "bench-v3-ui-16";
84+
85+
/// Commits to inline for the open-by-default group. The chart's
86+
/// initial visible window is ~100 commits; bigger windows just bloat
87+
/// the cold-page HTML. Users who zoom out trigger a refetch with
88+
/// `?n=all` via `chart-init.js`.
89+
const LANDING_INLINE_N: u32 = 100;
8490

8591
/// HTML routes mounted under `/`.
8692
pub fn router() -> Router<AppState> {
@@ -164,10 +170,15 @@ fn parse_csv(raw: Option<&str>) -> Vec<String> {
164170
}
165171

166172
async fn landing(State(state): State<AppState>, Query(ui): Query<UiQuery>) -> Response {
167-
let window = ui.fetch_window();
173+
// The landing page intentionally ignores `?n=` for the inline payloads —
174+
// they are always capped at [`LANDING_INLINE_N`] commits (see
175+
// [`collect_landing_groups`]) so the cold HTML stays small. Power users
176+
// with `?n=all` in the URL still get the unbounded view: `chart-init.js`
177+
// refetches via `/api/chart/{slug}?n=all` when they zoom past the
178+
// inlined range.
168179
let filter = ui.filter_state();
169180
let result = db::run_blocking(&state.db, move |conn| {
170-
let groups = collect_landing_groups(conn, &window)?;
181+
let groups = collect_landing_groups(conn)?;
171182
let universe = api::collect_filter_universe(conn)?;
172183
Ok::<_, anyhow::Error>((groups, universe))
173184
})
@@ -218,13 +229,22 @@ struct LandingGroup {
218229
/// Build a landing-page view: every group, with the first group's payloads
219230
/// inlined and the rest left as shells. Groups whose discovery query
220231
/// returns no data are dropped, but a group whose charts simply have no data
221-
/// inside the requested window is preserved as a shell so the user can see
232+
/// inside the inlined window is preserved as a shell so the user can see
222233
/// it (and the lazy-fetch retry path can populate it on toggle).
223-
fn collect_landing_groups(conn: &Connection, window: &CommitWindow) -> Result<Vec<LandingGroup>> {
234+
///
235+
/// Inline payloads are always capped at [`LANDING_INLINE_N`] commits — the
236+
/// chart's initial visible range is ~100 commits anyway, and the bytes
237+
/// saved on the cold-page HTML matter much more than a slightly different
238+
/// fully-zoomed view that the user only sees if they zoom out (at which
239+
/// point `chart-init.js` refetches `?n=all` from the API).
240+
fn collect_landing_groups(conn: &Connection) -> Result<Vec<LandingGroup>> {
224241
let groups = api::collect_groups(conn)?;
225242
if groups.is_empty() {
226243
return Ok(Vec::new());
227244
}
245+
let inline_window = CommitWindow::Last(
246+
std::num::NonZeroU32::new(LANDING_INLINE_N).expect("LANDING_INLINE_N is non-zero"),
247+
);
228248
let mut out = Vec::with_capacity(groups.len());
229249
for (i, group) in groups.into_iter().enumerate() {
230250
let inlined = if i == 0 {
@@ -234,7 +254,7 @@ fn collect_landing_groups(conn: &Connection, window: &CommitWindow) -> Result<Ve
234254
let mut v = Vec::with_capacity(group.charts.len());
235255
for link in &group.charts {
236256
let key = ChartKey::from_slug(&link.slug)?;
237-
let payload = api::chart_payload(conn, &key, window)?;
257+
let payload = api::chart_payload(conn, &key, &inline_window)?;
238258
v.push(payload.map(|chart| NamedChartResponse {
239259
name: link.name.clone(),
240260
slug: link.slug.clone(),

benchmarks-website/server/static/chart-init.js

Lines changed: 141 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@
4040
var PAN_THROTTLE_MS = 50; // pan/zoom throttle — looser than slider
4141
var FETCH_N = "all"; // lazy-fetch the entire raw history
4242
var DEFAULT_VISIBLE = 100; // initial visible window (last 100 of fetched)
43+
// Mirror of `LANDING_INLINE_N` in `server/src/html.rs`. The first group's
44+
// inline JSON is capped at this many commits to keep the cold landing
45+
// page small. When the user zooms wider than what's inlined we lazy-fetch
46+
// `?n=all` and replace the payload in place. If you change this, update
47+
// the server too — the comparison `commits.length >= LANDING_INLINE_N`
48+
// is what tells us the inline payload was potentially trimmed.
49+
var LANDING_INLINE_N = 100;
4350
// Hard cap on how many points a single series can render at once. When
4451
// the visible commit range has more raw non-null points than this, we
4552
// LTTB-downsample to exactly this number; below it we render raw. So
@@ -617,6 +624,120 @@
617624
chart.update("none");
618625
syncSliderFromRange(card, visibleCommits);
619626
syncDownsampleBadge(card, keptCommits, visibleCommits, anyDownsampled);
627+
// If the user has zoomed out to cover everything we have inlined and the
628+
// server might have more commits, fetch the full history in the
629+
// background. The `__bench_full_loaded` / `__bench_full_fetch_pending`
630+
// flags dedupe so this fires once per chart even when called every
631+
// pan frame.
632+
maybeRefetchFullPayload(card, min, max, n);
633+
}
634+
635+
// -----------------------------------------------------------------------
636+
// Lazy-upgrade an inline-trimmed payload to the full history.
637+
//
638+
// The landing page inlines at most `LANDING_INLINE_N` commits per chart
639+
// (server: `html.rs::LANDING_INLINE_N`) so the cold HTML body stays small.
640+
// The first time the user zooms wide enough to ask for everything we have
641+
// loaded we replace the payload with the unbounded view from
642+
// `/api/chart/{slug}?n=all`. The chart's pan/zoom limits and the toolbar
643+
// slider's max grow to match, so subsequent zoom-out passes can scroll
644+
// back through the older commits the inline payload didn't include.
645+
// -----------------------------------------------------------------------
646+
function maybeRefetchFullPayload(card, min, max, loadedCount) {
647+
var canvas = card.querySelector("canvas");
648+
if (!canvas) return;
649+
if (!canvas.__bench_inline_trimmed) return;
650+
if (canvas.__bench_full_loaded || canvas.__bench_full_fetch_pending) return;
651+
// Trigger only when the visible range covers (effectively) every loaded
652+
// commit. Anything narrower means the user hasn't asked for "more"
653+
// yet — there's no reason to spend bandwidth on a refetch they don't
654+
// need.
655+
if (loadedCount <= 0) return;
656+
var coversAll = (max - min + 1) >= loadedCount;
657+
if (!coversAll) return;
658+
canvas.__bench_full_fetch_pending = true;
659+
var slug = card.getAttribute("data-chart-slug");
660+
if (!slug) {
661+
canvas.__bench_full_fetch_pending = false;
662+
return;
663+
}
664+
var url = "/api/chart/" + encodeURIComponent(slug)
665+
+ "?n=" + encodeURIComponent(FETCH_N);
666+
fetch(url, { headers: { "accept": "application/json" } })
667+
.then(function (r) {
668+
if (r.status === 404) return null;
669+
if (!r.ok) throw new Error("HTTP " + r.status);
670+
return r.json();
671+
})
672+
.then(function (full) {
673+
if (!full) return;
674+
replaceChartPayload(card, full);
675+
canvas.__bench_full_loaded = true;
676+
canvas.__bench_inline_trimmed = false;
677+
})
678+
.catch(function (err) {
679+
// Quiet — the inline payload is still rendered, the user just
680+
// can't zoom past it. Surface to the console for debugging.
681+
if (window && window.console) {
682+
window.console.warn("bench: full history refetch failed", err);
683+
}
684+
})
685+
.then(function () {
686+
canvas.__bench_full_fetch_pending = false;
687+
});
688+
}
689+
690+
// Swap the chart's labels + datasets to a freshly fetched, unbounded
691+
// payload while keeping the user's currently visible commit window
692+
// anchored on the *newest* commit. The pan/zoom limits and toolbar
693+
// slider bounds are extended to the new total commit count.
694+
function replaceChartPayload(card, payload) {
695+
var canvas = card.querySelector("canvas");
696+
var chart = canvas && canvas.__bench_chart;
697+
if (!chart || !payload) return;
698+
canvas.__bench_payload = payload;
699+
var newLabels = (payload.commits || []).map(function (c) {
700+
return shortSha(c.sha);
701+
});
702+
var newDatasets = buildDatasets(payload);
703+
// Re-apply per-card legend overrides + global filter to the new datasets,
704+
// matching the visibility state the user had before the refetch.
705+
var overrides = canvas.__bench_overrides || {};
706+
for (var i = 0; i < newDatasets.length; i++) {
707+
var ds = newDatasets[i];
708+
if (overrides[ds.label]) {
709+
// Honour any explicit legend toggle the user had made already.
710+
var prev = chart.data.datasets.find(function (p) {
711+
return p.label === ds.label;
712+
});
713+
if (prev) ds.hidden = !!prev.hidden;
714+
}
715+
}
716+
chart.data.labels = newLabels;
717+
chart.data.datasets = newDatasets;
718+
var newMaxIdx = Math.max(0, newLabels.length - 1);
719+
var zoomLimits = chart.options.plugins
720+
&& chart.options.plugins.zoom
721+
&& chart.options.plugins.zoom.limits
722+
&& chart.options.plugins.zoom.limits.x;
723+
if (zoomLimits) {
724+
zoomLimits.max = newMaxIdx;
725+
}
726+
syncSliderBounds(card, newLabels.length);
727+
// Keep the user's "scope" (number of visible commits) but anchor the
728+
// window on the newest commit so they don't drift backwards in time
729+
// unexpectedly. Without this anchoring, the visible range would still
730+
// be `[0, oldN-1]` — i.e., the *oldest* `oldN` commits of the new
731+
// payload — which is the opposite of what the user wanted when they
732+
// zoomed out.
733+
var sx = chart.options.scales.x;
734+
var prevMin = Number.isFinite(sx.min) ? sx.min : 0;
735+
var prevMax = Number.isFinite(sx.max) ? sx.max : 0;
736+
var prevVisible = Math.max(1, prevMax - prevMin + 1);
737+
sx.max = newMaxIdx;
738+
sx.min = Math.max(0, newMaxIdx - (prevVisible - 1));
739+
rebuildVisibleAndUpdate(card, chart, sx.min, sx.max);
740+
if (canvas.__bench_strip_render) canvas.__bench_strip_render();
620741
}
621742

622743
// Mirror the chart's current visible commit count onto the toolbar
@@ -664,19 +785,36 @@
664785

665786
// -----------------------------------------------------------------------
666787
// Per-card construction. State lives on the canvas:
667-
// canvas.__bench_chart — Chart.js instance
668-
// canvas.__bench_payload — last-fetched ChartResponse (raw)
669-
// canvas.__bench_state — { y, scope } (per-chart toolbar state)
788+
// canvas.__bench_chart — Chart.js instance
789+
// canvas.__bench_payload — last-fetched ChartResponse (raw)
790+
// canvas.__bench_state — { y, scope } (per-chart toolbar state)
791+
// canvas.__bench_inline_trimmed — true if the payload came from an
792+
// inline `<script id="chart-data-N">`
793+
// and may have been capped at
794+
// LANDING_INLINE_N commits server-side
795+
// canvas.__bench_full_loaded — true once a `?n=all` refetch has
796+
// replaced the payload
797+
// canvas.__bench_full_fetch_pending — in-flight refetch flag (dedupe)
670798
// -----------------------------------------------------------------------
671799
function constructChart(card) {
672800
var idx = card.getAttribute("data-chart-index");
673801
var canvas = card.querySelector('canvas[data-chart-index="' + idx + '"]');
674802
if (!canvas || typeof Chart === "undefined") return null;
675803
if (canvas.__bench_chart) return canvas.__bench_chart;
676804

805+
var payloadFromInline = !canvas.__bench_payload;
677806
var payload = canvas.__bench_payload || readInlinePayload(idx);
678807
if (!payload) return null;
679808
canvas.__bench_payload = payload;
809+
// Server caps inline payloads at LANDING_INLINE_N commits. Reaching that
810+
// count means there might be more on the server; if we got fewer, we
811+
// have the whole history already and never need to refetch.
812+
if (canvas.__bench_full_loaded === undefined) {
813+
var inlineN = (payload.commits || []).length;
814+
canvas.__bench_inline_trimmed =
815+
payloadFromInline && inlineN >= LANDING_INLINE_N;
816+
canvas.__bench_full_loaded = !canvas.__bench_inline_trimmed;
817+
}
680818

681819
var state = canvas.__bench_state || { y: "linear", scope: DEFAULT_VISIBLE };
682820
canvas.__bench_state = state;

0 commit comments

Comments
 (0)