From c04ea46e82907b8843d27633b132da27923c0375 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier <160007691+Flotapponnier@users.noreply.github.com> Date: Mon, 27 Jul 2026 11:08:03 +0200 Subject: [PATCH] =?UTF-8?q?rpc=20hub=20(D):=20archive=20depth=20column=20?= =?UTF-8?q?=E2=80=94=20reads=20rpc=5Farchive=5Fdepth=5Fsupported=20into=20?= =?UTF-8?q?bench=20blob,=20exposes=20bestArchiveDepthBlocks=20in=20snapsho?= =?UTF-8?q?t,=20displays=20as=20sortable=20column?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/rpc-chains-leaderboard.tsx | 39 +++++++++++++++++++- src/lib/materialize/load.ts | 45 +++++++++++++++++++++++ src/lib/rpc-hub-stats.ts | 14 +++++++ src/types/benchmark.ts | 9 +++++ 4 files changed, 106 insertions(+), 1 deletion(-) diff --git a/src/components/rpc-chains-leaderboard.tsx b/src/components/rpc-chains-leaderboard.tsx index 5568ce46..747823bb 100644 --- a/src/components/rpc-chains-leaderboard.tsx +++ b/src/components/rpc-chains-leaderboard.tsx @@ -22,6 +22,7 @@ type SortKey = | "name" | "bestP50" | "bestP90" + | "archive" | "us-east" | "eu-west" | "sgp" @@ -37,6 +38,7 @@ function sortValue(r: RpcHubChain, k: SortKey): number | string | null { if (k === "name") return r.name.toLowerCase(); if (k === "bestP50") return r.best?.p50Ms ?? null; if (k === "bestP90") return r.bestP90Ms ?? null; + if (k === "archive") return r.bestArchiveDepthBlocks ?? null; if (k === "providerCount") return r.providerCount; return r.regions[k]?.p50Ms ?? null; } @@ -129,6 +131,15 @@ export function RpcChainsLeaderboard({ rows }: { rows: RpcHubChain[] }) { Best p90 + setSort("archive")} + > + + Archive + + {REGION_COLS.map((c) => ( - )} + + {r.bestArchiveDepthBlocks + ? fmtArchive(r.bestArchiveDepthBlocks) + : -} + {REGION_COLS.map((c) => { const b = r.regions[c.key]; return ( @@ -273,7 +296,7 @@ export function RpcChainsLeaderboard({ rows }: { rows: RpcHubChain[] }) { {filtered.length === 0 && ( No chain matches “{q}”. @@ -354,3 +377,17 @@ function fmtMs(v: number | null | undefined): string { if (v < 1000) return `${Math.round(v)} ms`; return `${(v / 1000).toFixed(2)} s`; } + +// Compact archive-depth display. Maps the harness's discrete depthBuckets +// to short labels: 300 blocks = "pruned" (Geth default), 7.2k blocks ≈ +// 24h on 12s-block chains, 216k ≈ 30d, 1.3M ≈ 6mo, 5M ≈ 2yr. On sub-second +// chains (Arbitrum, Sei) these translate to shorter wall-clock windows; +// the raw block count in the tooltip disambiguates. +function fmtArchive(blocks: number): string { + if (blocks >= 5_000_000) return "5M+"; + if (blocks >= 1_296_000) return "1.3M"; + if (blocks >= 216_000) return "216k"; + if (blocks >= 7200) return "7.2k"; + if (blocks >= 300) return "300"; + return blocks.toLocaleString(); +} diff --git a/src/lib/materialize/load.ts b/src/lib/materialize/load.ts index 4aeb6b4d..9f211881 100644 --- a/src/lib/materialize/load.ts +++ b/src/lib/materialize/load.ts @@ -1014,6 +1014,51 @@ async function tryLoadLive( }); } + // Archive depth augmentation for `-rpc` benches (bench cluster + // 044+). Reads `rpc_archive_depth_supported{chain=""}` from + // Prom (one instant query, no per-provider fan-out), aggregates + // "supported if ANY region reports 1", and attaches the ascending + // list of supported block depths to each provider row so the /rpc + // hub can render an archive column without a second Prom trip. + // Silent no-op when the query returns empty (chains where the + // archive probe is skipped: Solana / Substrate) or when the spec + // isn't a -rpc bench. + if (spec.slug.endsWith("-rpc")) { + try { + const chainForArchive = spec.slug.replace(/-rpc$/, ""); + const archiveQuery = `max by(provider, depth)(rpc_archive_depth_supported{chain="${chainForArchive}"} == 1)`; + const res = await prom.query(archiveQuery); + if (res.resultType === "vector") { + const byProvider = new Map(); + for (const row of res.result) { + const provider = row.metric.provider; + const depthStr = row.metric.depth; + if (!provider || !depthStr) continue; + const d = Number(depthStr); + if (!Number.isFinite(d) || d <= 0) continue; + const arr = byProvider.get(provider) ?? []; + if (!arr.includes(d)) arr.push(d); + byProvider.set(provider, arr); + } + for (const r of liveResults) { + const supported = byProvider.get(r.slug); + if (supported && supported.length > 0) { + supported.sort((a, b) => a - b); + r.archiveDepth = { supportedBlocks: supported }; + } + } + } + } catch (e) { + // archive is a nice-to-have; a failure here must never + // collapse the bench. Log and continue. + console.warn( + `[load/${spec.slug}] archive-depth augmentation failed: ${ + e instanceof Error ? e.message : String(e) + }`, + ); + } + } + // Derive lastRunAt from the actual Prom data freshness. We probe the // first provider that has a p50 query and ask Prom for the age of // its underlying metric. This is consistent across pages (Prom is the diff --git a/src/lib/rpc-hub-stats.ts b/src/lib/rpc-hub-stats.ts index dd520984..e63b1b26 100644 --- a/src/lib/rpc-hub-stats.ts +++ b/src/lib/rpc-hub-stats.ts @@ -60,6 +60,11 @@ export type RpcHubProvider = { sampleSize?: number; /** p50 per probe region, keyed by region value. */ regions: Partial>; + /** Archive block depths this provider supports (subset of the + * harness's `depthBuckets` = {300, 7200, 216000, 1296000, 5000000}). + * Absent = not measured (Solana / Substrate chains skip archive) + * or not yet propagated by the worker. */ + archiveDepth?: { supportedBlocks: number[] }; }; export type RpcHubUnresponsiveProvider = { @@ -91,6 +96,10 @@ export type RpcHubChain = { best: RpcRegionBest | null; /** p90 of the overall best provider (same row as `best`). Optional so old blobs stay parseable. */ bestP90Ms?: number; + /** Max archive block depth supported by the overall best provider + * (0 or absent = archive not measured / provider Geth-pruned). Used + * by the hub as a compact single-cell 'archive support' badge. */ + bestArchiveDepthBlocks?: number; /** Best provider per probe region. */ regions: Partial>; /** Full live provider field, sorted fastest first. */ @@ -269,6 +278,10 @@ async function buildChain(spec: Spec): Promise { : {}), best: { provider: leader.slug, providerName: leader.name, p50Ms: round1(leader.ms.p50) }, bestP90Ms: Number.isFinite(leader.ms.p90) && leader.ms.p90 > 0 ? round1(leader.ms.p90) : undefined, + bestArchiveDepthBlocks: + leader.archiveDepth && leader.archiveDepth.supportedBlocks.length > 0 + ? Math.max(...leader.archiveDepth.supportedBlocks) + : undefined, regions, providers: rows.map((r) => ({ provider: r.slug, @@ -276,6 +289,7 @@ async function buildChain(spec: Spec): Promise { p50Ms: round1(r.ms.p50), p90Ms: Number.isFinite(r.ms.p90) && r.ms.p90 > 0 ? round1(r.ms.p90) : undefined, p99Ms: Number.isFinite(r.ms.p99) && r.ms.p99 > 0 ? round1(r.ms.p99) : undefined, + archiveDepth: r.archiveDepth, successPct: Number.isFinite(r.successRate) && r.successRate > 0 ? round2(r.successRate) diff --git a/src/types/benchmark.ts b/src/types/benchmark.ts index ac5ed019..b542d1a7 100644 --- a/src/types/benchmark.ts +++ b/src/types/benchmark.ts @@ -43,6 +43,15 @@ export type ProviderResult = { successRate: number; /** Per-provider sample count over the run window. */ sampleSize?: number; + /** RPC-cluster only: which archive depths this provider supports + * (values from the harness `depthBuckets` set: 300 / 7200 / 216000 + * / 1296000 / 5000000 blocks). Populated by the loader for + * `-rpc` benches from `rpc_archive_depth_supported`. A provider + * that returns `[300]` is Geth-default-pruned; `[300, 7200, + * 216000, 1296000, 5000000]` is full archive back to genesis-era. + * Absent on non-RPC benches and on chains where the archive probe + * is skipped (Solana slot model, Substrate state model). */ + archiveDepth?: { supportedBlocks: number[] }; /** * Sample-health classification derived from sampleSize / expectedN. *