From 208b11c6f1e00e57388ea9c16f4b117f887ca348 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 28 Oct 2026 15:37:31 -0400 Subject: [PATCH 1/8] feat(scaling): show block gas limit and compare Glamsterdam runs Co-authored-by: Toshi --- .../components/GlamsterdamComparison.test.tsx | 9 ++ .../components/GlamsterdamComparison.tsx | 96 +++++++++++++++++++ app/scaling/components/Scorecard.tsx | 2 + app/scaling/components/scorecard.test.tsx | 35 ++++++- app/scaling/page.test.tsx | 3 +- app/scaling/page.tsx | 10 +- app/scaling/proven/capacity.test.ts | 45 +++++++++ app/scaling/proven/capacity.ts | 92 +++++++++++++++--- 8 files changed, 274 insertions(+), 18 deletions(-) create mode 100644 app/scaling/components/GlamsterdamComparison.test.tsx create mode 100644 app/scaling/components/GlamsterdamComparison.tsx diff --git a/app/scaling/components/GlamsterdamComparison.test.tsx b/app/scaling/components/GlamsterdamComparison.test.tsx new file mode 100644 index 0000000..8402c3c --- /dev/null +++ b/app/scaling/components/GlamsterdamComparison.test.tsx @@ -0,0 +1,9 @@ +import { renderToStaticMarkup } from 'react-dom/server'; +import { expect, it } from 'vitest'; + +import { GlamsterdamComparisonSection } from './GlamsterdamComparison'; + +it('shows the benchmark source setup message when comparisons are unavailable', () => { + const html = renderToStaticMarkup(); + expect(html, 'the section should explain why comparison data is unavailable').toContain('Benchmark source is not configured.'); +}); diff --git a/app/scaling/components/GlamsterdamComparison.tsx b/app/scaling/components/GlamsterdamComparison.tsx new file mode 100644 index 0000000..4a18288 --- /dev/null +++ b/app/scaling/components/GlamsterdamComparison.tsx @@ -0,0 +1,96 @@ +import { Text } from '../../components/ui/Text'; +import { DA_WORKLOADS, fmt } from '../da/workloads'; +import { runHref, successfulTps, type GlamsterdamPair } from '../proven/capacity'; + +type Props = { + configured: boolean; + comparisons?: GlamsterdamPair[]; + comparisonErrors?: number; + error?: unknown; +}; + +const TH = 'px-3 py-2 text-left font-[600] text-bds-gray-60'; +const TD = 'border-t border-bds-gray-10 px-3 py-2 align-top dark:border-white/10'; + +/** Shows matched Glamsterdam and main benchmark jobs side by side. */ +export function GlamsterdamComparisonSection({ configured, comparisons, comparisonErrors = 0, error }: Props) { + const message = !configured + ? 'Benchmark source is not configured.' + : error + ? 'Could not load Glamsterdam benchmark data.' + : !comparisons + ? 'Loading Glamsterdam comparisons…' + : comparisons.length === 0 + ? comparisonErrors > 0 + ? 'A matching Glamsterdam run could not be loaded; its comparison is omitted.' + : 'No matching Glamsterdam and main jobs in the last 14 days.' + : null; + + return ( +
+ + Glamsterdam + + + Compared with main at the same workload and target Mgas/s. TPS counts successful transactions. + + {message ? ( + + {message} + + ) : ( + <> + {comparisonErrors > 0 ? ( + + Some matching Glamsterdam runs could not be loaded; affected comparisons are omitted. + + ) : null} +
+ + + + + + + + + + + {comparisons!.map((comparison) => { + const workload = DA_WORKLOADS.find((item) => item.payload === comparison.workload)?.label ?? comparison.workload; + return ( + + + + + + + ); + })} + +
WorkloadTargetGlamsterdam jobMain job
{workload}{comparison.mgas.toLocaleString('en-US')} Mgas/s + + {comparison.glamsterdam.id} + + + {fmt(successfulTps(comparison.glamsterdam))} TPS ·{' '} + {(comparison.glamsterdam.gps / 1e6).toLocaleString('en-US', { maximumFractionDigits: 1 })} Mgas/s + + + + {comparison.main.id} + + + {fmt(successfulTps(comparison.main))} TPS ·{' '} + {(comparison.main.gps / 1e6).toLocaleString('en-US', { + maximumFractionDigits: 1, + })}{' '} + Mgas/s + +
+
+ + )} +
+ ); +} diff --git a/app/scaling/components/Scorecard.tsx b/app/scaling/components/Scorecard.tsx index eb01a8f..3881d96 100644 --- a/app/scaling/components/Scorecard.tsx +++ b/app/scaling/components/Scorecard.tsx @@ -9,6 +9,7 @@ import { Ladder, type ProvenMark } from './Ladder'; import { day, useProvenCapacity } from './ProvenCapacity'; import { Work } from './Work'; import { subsystemCards, WorkloadSubsystems } from './WorkloadSubsystems'; +import { GlamsterdamComparisonSection } from './GlamsterdamComparison'; function benchMark({ mgas, atTop, run }: Proven): ProvenMark { const top = atTop ? ` ≥ because ${mgas} Mgas/s is the highest rate the bench runs.` : ''; @@ -51,6 +52,7 @@ export function Scorecard({ snapshot, benchUrl }: { snapshot: Snapshot; benchUrl + ); diff --git a/app/scaling/components/scorecard.test.tsx b/app/scaling/components/scorecard.test.tsx index e8c6edc..7e99bdd 100644 --- a/app/scaling/components/scorecard.test.tsx +++ b/app/scaling/components/scorecard.test.tsx @@ -54,6 +54,8 @@ const storage = (currentBytes: number | null = null): NonNullable = {}): ProvenCapacity => ({ current: { ...none, mix: { mgas, atTop, run: run(mgas) } }, da: none, + comparisons: [], + comparisonErrors: 0, history: [ { at: T0, mgas: 40, commit: 'aaaaaaaaaaaaaaaa', previousCommit: null, compareUrl: null }, { at: T0 + 5 * DAY, mgas, commit: 'bbbbbbbbbbbbbbbb', previousCommit: 'aaaaaaaaaaaaaaaa', compareUrl: 'https://github.com/base/base/compare/aaaa...bbbb' }, @@ -66,8 +68,8 @@ const fixture = (): Snapshot => JSON.parse(readFileSync(new URL('../fixtures/syn // In page order: the freshness line, then headline, ladder, ranked limits, workloads, work. const render = (props: { benchUrl?: string } = { benchUrl: BENCH }, snapshot = fixture()) => { const html = renderToStaticMarkup(); - const [fresh, head, ladder, ranked, subsystems, work] = html.split(' { @@ -124,6 +126,35 @@ describe('freshness and headline', () => { }); +describe('Glamsterdam comparisons', () => { + it('shows matched jobs with their TPS, delivered Mgas/s and run links', () => { + const main = { ...run(200), id: 'main-mix-200', outputDir: 'main-mix-200' }; + const glamsterdam = { ...run(200), id: 'glam-mix-200', outputDir: 'glam-mix-200', gps: 197e6, tps: 2200 }; + swr.result = { data: bench(400, true, { comparisons: [{ workload: 'mix', mgas: 200, main, glamsterdam }] }) }; + const section = render().glamsterdam; + + expect(text(section), 'the comparison section should identify its workload and target rate').toContain('Mainnet mix200 Mgas/s'); + expect(text(section), 'the Glamsterdam run should show its measured TPS and delivered gas rate').toContain('2,200 TPS · 197 Mgas/s'); + expect(text(section), 'the main run should show its measured TPS and delivered gas rate').toContain('2,100 TPS · 195 Mgas/s'); + expect(section, 'the Glamsterdam run ID should link to its benchmark details').toContain('href="/scaling/runs/bench/glam-mix-200"'); + expect(section, 'the main run ID should link to its benchmark details').toContain('href="/scaling/runs/bench/main-mix-200"'); + }); + + it('distinguishes unconfigured, loading, error and no-match states', () => { + expect(text(render({}).glamsterdam), 'an unconfigured benchmark host should be named').toContain('Benchmark source is not configured.'); + expect(text(render().glamsterdam), 'a configured host without data should show a loading state').toContain('Loading Glamsterdam comparisons…'); + + swr.result = { error: new Error('offline') }; + expect(text(render().glamsterdam), 'a failed benchmark request should show an error state').toContain('Could not load Glamsterdam benchmark data.'); + + swr.result = { data: bench(400, true) }; + expect(text(render().glamsterdam), 'an empty result should state that no matching pairs were found').toContain('No matching Glamsterdam and main jobs in the last 14 days.'); + swr.result = { data: bench(400, true, { comparisonErrors: 1 }) }; + expect(text(render().glamsterdam), 'a failed latest job should explain why its comparison is missing').toContain('A matching Glamsterdam run could not be loaded; its comparison is omitted.'); + }); +}); + + describe('workload tiles', () => { it('takes the lowest of the proven, DA and gas-limit ceilings, preferring DA then the gas limit on ties', () => { expect(sustainable(400, 82, 200)).toEqual({ mgas: 82, cap: 'DA' }); diff --git a/app/scaling/page.test.tsx b/app/scaling/page.test.tsx index 159770f..5ab78e3 100644 --- a/app/scaling/page.test.tsx +++ b/app/scaling/page.test.tsx @@ -22,6 +22,7 @@ const area = async (id: string) => renderToStaticMarkup(await ScalingAreaPage({ it('ends the first screen with the DA link, and no guardrails or benchmark links', async () => { const html = renderToStaticMarkup(await ScalingPage()); expect(html).toContain('DA by workload'); + expect(html).toContain('Block Gas Limit: 400 Mgas'); // The guardrails page stays, unlinked, while the first screen leaves guardrails out. expect(html).not.toMatch(/href="\/scaling\/guardrails"|Guardrails<\/h2>|SLOs/); expect(html).not.toMatch(/href="\/benchmark|href="\/scaling\/networking"/); @@ -33,7 +34,7 @@ it('ends the first screen with the DA link, and no guardrails or benchmark links it('orders the first screen: freshness, headline, ladder, ranked limits, subsystems by workload, work, links', async () => { const html = renderToStaticMarkup(await ScalingPage()); - const order = ['Usage to 2026-01-02', 'Proven capacity', 'How close are we, and to what?', 'What breaks first', 'Subsystems by workload', 'Work log', 'DA by workload'].map((h) => html.indexOf(h)); + const order = ['Block Gas Limit: 400 Mgas', 'Usage to 2026-01-02', 'Proven capacity', 'How close are we, and to what?', 'What breaks first', 'Subsystems by workload', 'Glamsterdam', 'Work log', 'DA by workload'].map((h) => html.indexOf(h)); expect(order.every((i, k) => i > (order[k - 1] ?? -1))).toBe(true); expect(html).not.toMatch(/Subsystem detail|>Progress<|Ceilings by bottleneck|Usage data as of/); }); diff --git a/app/scaling/page.tsx b/app/scaling/page.tsx index 0fec28a..03c9cf3 100644 --- a/app/scaling/page.tsx +++ b/app/scaling/page.tsx @@ -9,6 +9,9 @@ import { Header, loadSnapshot, Unavailable } from './loadSnapshot'; // The snapshot is a runtime file, so render per request rather than at build. export const dynamic = 'force-dynamic'; +// Base L2 blocks are spaced two seconds apart; the snapshot stores this limit in Mgas/s. +const BASE_BLOCK_TIME_SECONDS = 2; + export default async function ScalingPage() { const loaded = await loadSnapshot(); if (loaded.kind !== 'ok') return ; @@ -17,7 +20,12 @@ export default async function ScalingPage() { const dashboardUrl = process.env.SCALING_DATADOG_DASHBOARD_URL; return (
-
+
+
+ + Block Gas Limit: {snapshot.ladder.configuredLimit.value === null ? '—' : `${(snapshot.ladder.configuredLimit.value * BASE_BLOCK_TIME_SECONDS).toLocaleString('en-US', { maximumFractionDigits: 1 })} Mgas`} + +
diff --git a/app/scaling/proven/capacity.test.ts b/app/scaling/proven/capacity.test.ts index c4c0a6a..4218644 100644 --- a/app/scaling/proven/capacity.test.ts +++ b/app/scaling/proven/capacity.test.ts @@ -171,6 +171,51 @@ it('ignores alias copies and non-sustained scenarios', async () => { expect(unknown).toEqual([]); }); +it('pairs the newest Glamsterdam jobs with main at matching workloads and rates without changing main capacity', async () => { + const { comparisons, comparisonErrors, current, history, latestAt } = await load([ + { dir: 'main-mix-200-old', mgas: 200, daysAgo: 3 }, + { dir: 'main-mix-200-new', mgas: 200, daysAgo: 1 }, + { dir: 'main-payment-200', mgas: 200, daysAgo: 2, workload: 'payment' }, + { dir: 'glam-mix-200-old', mgas: 200, daysAgo: 2, scenario: 'glamsterdam-sustained-200mgas', ref: 'glamsterdam' }, + { dir: 'glam-mix-200-latest', mgas: 200, daysAgo: 0, scenario: 'glamsterdam-sustained-200mgas', ref: 'feature', comparable: false }, + { dir: 'glam-payment-200', mgas: 200, daysAgo: 1, workload: 'payment', scenario: 'glamsterdam-sustained-200mgas', ref: 'glamsterdam' }, + { dir: 'glam-mix-300', mgas: 300, daysAgo: 1, scenario: 'glamsterdam-sustained-300mgas', ref: 'glamsterdam' }, + { dir: 'main-swap-200', mgas: 200, daysAgo: 1, workload: 'swap' }, + ]); + + expect(comparisons.map(({ workload, mgas, main, glamsterdam }) => [workload, mgas, main.outputDir, glamsterdam.outputDir])).toEqual([ + ['mix', 200, 'main-mix-200-new', 'glam-mix-200-latest'], + ['payment', 200, 'main-payment-200', 'glam-payment-200'], + ], 'only the newest exact workload-and-rate matches should appear'); + expect(comparisons[0].glamsterdam.passed).toBe(false, 'the newest run should remain visible even when it does not pass'); + expect(current.mix).toMatchObject({ mgas: 200, run: { outputDir: 'main-mix-200-new' } }, 'Glamsterdam runs must not affect main proven capacity'); + expect(history.map(({ mgas }) => mgas)).toEqual([200], 'Glamsterdam runs must not enter main capacity history'); + expect(latestAt).toBe(NOW - DAY, 'the main freshness timestamp must exclude the newer Glamsterdam run'); + expect(comparisonErrors).toBe(0, 'successful comparisons should have no load errors'); +}); + +it('omits an older comparison when the newest Glamsterdam run cannot be loaded', async () => { + const { comparisons, comparisonErrors } = await load([ + { dir: 'failed-latest-main-200', mgas: 200, daysAgo: 1 }, + { dir: 'failed-latest-glam-old-200', mgas: 200, daysAgo: 3, scenario: 'glamsterdam-sustained-200mgas' }, + { dir: 'failed-latest-glam-new-200', mgas: 200, daysAgo: 0, scenario: 'glamsterdam-sustained-200mgas', fail: 'load-test-result.json' }, + ]); + + expect(comparisons).toEqual([], 'a failed latest job must not leave an older pair that looks current'); + expect(comparisonErrors).toBe(1, 'the omitted workload-and-rate comparison should be reported once'); +}); + +it('keeps a newer loaded pair when only an older Glamsterdam run fails', async () => { + const { comparisons, comparisonErrors } = await load([ + { dir: 'older-failure-main-200', mgas: 200, daysAgo: 1 }, + { dir: 'older-failure-glam-old-200', mgas: 200, daysAgo: 3, scenario: 'glamsterdam-sustained-200mgas', fail: 'load-test-result.json' }, + { dir: 'older-failure-glam-new-200', mgas: 200, daysAgo: 0, scenario: 'glamsterdam-sustained-200mgas' }, + ]); + + expect(comparisons.map(({ glamsterdam }) => glamsterdam.outputDir)).toEqual(['older-failure-glam-new-200'], 'the latest loaded Glamsterdam run should remain paired'); + expect(comparisonErrors).toBe(0, 'an older failed job should not mark the latest loaded comparison unavailable'); +}); + it('fails runs that are not comparable or deliver under 95% of target', async () => { const { current } = await load([ { dir: 'fail-100', mgas: 100, daysAgo: 3 }, diff --git a/app/scaling/proven/capacity.ts b/app/scaling/proven/capacity.ts index 30faab2..39989ac 100644 --- a/app/scaling/proven/capacity.ts +++ b/app/scaling/proven/capacity.ts @@ -91,19 +91,26 @@ export type Step = { // `history` is the mainnet mix's; `da` is the latest counting run per workload in the window whose DA // step passed; `latestAt` is when the newest counting run was created. Replay, proof-node and DA results are loaded only for the runs in `current` and `da`: // replay files list every block, and every other run would download them for nothing. + export type ProvenCapacity = { current: Record; da: Record; + comparisons: GlamsterdamPair[]; + comparisonErrors: number; history: Step[]; latestAt: number | null; unknown: string[]; }; +/** A matched Glamsterdam and main run at the same workload and target gas rate. */ +export type GlamsterdamPair = { workload: Workload; mgas: number; main: Run; glamsterdam: Run }; + const WINDOW_MS = 14 * 24 * 60 * 60 * 1000; const PASS_RATIO = 0.95; const CONCURRENCY = 6; -const SCENARIO = /^sustained-(\d+)mgas$/; +const SCENARIO = /^(glamsterdam-)?sustained-(\d+)mgas$/; const WORKLOADS = Object.keys(PAYLOADS) as Workload[]; +type Candidate = { meta: BenchmarkRun; kind: 'main' | 'glamsterdam'; mgas: number; workload: Workload }; // Run outputs never change once written. `null` marks a run that does not count: not built from // main, or written before the step pipeline. @@ -122,14 +129,24 @@ export async function loadProvenCapacity( const res = await fetchImpl(`${root}metadata.json`, { cache: 'no-cache' }); if (!res.ok) throw new Error(`Failed to fetch metadata: ${res.status} ${res.statusText}`); const runs: Run[] = []; + const glamRuns: Run[] = []; const unknown: string[] = []; - await forEachLimit(candidates(await res.json()), CONCURRENCY, async (meta) => { + const failedGlamRuns = new Map(); + await forEachLimit(candidates(await res.json()), CONCURRENCY, async (candidate) => { + const { meta, kind } = candidate; try { - if (!runCache.has(meta.outputDir)) runCache.set(meta.outputDir, await loadRun(root, meta, fetchImpl)); + if (!runCache.has(meta.outputDir)) runCache.set(meta.outputDir, await loadRun(root, candidate, fetchImpl)); const run = runCache.get(meta.outputDir); - if (run) runs.push(run); + if (run) (kind === 'glamsterdam' ? glamRuns : runs).push(run); } catch { - unknown.push(meta.outputDir); + if (kind === 'main') { + unknown.push(meta.outputDir); + } else { + const key = `${candidate.workload}:${candidate.mgas}`; + const failedAt = Date.parse(meta.createdAt); + const previous = failedGlamRuns.get(key); + if (previous === undefined || failedAt > previous) failedGlamRuns.set(key, failedAt); + } } }); const inWindow = (r: Run) => r.createdAt > now - WINDOW_MS && r.createdAt <= now; @@ -149,27 +166,36 @@ export async function loadProvenCapacity( const run = latestDa(of(full, w)); return run?.da ? run : null; }; + const comparison = glamsterdamComparisons(full, glamRuns, now, failedGlamRuns); return { current: Object.fromEntries(WORKLOADS.map((w) => [w, provenAt(of(full, w), now)])) as ProvenCapacity['current'], da: Object.fromEntries(WORKLOADS.map((w) => [w, daRun(w)])) as ProvenCapacity['da'], + comparisons: comparison.pairs, + comparisonErrors: comparison.errors, history: history(of(full, 'mix'), now), latestAt: runs.length ? Math.max(...runs.map((r) => r.createdAt)) : null, unknown, }; } -function candidates({ runs }: BenchmarkRuns): BenchmarkRun[] { +function candidates({ runs }: BenchmarkRuns): Candidate[] { const payloads: string[] = Object.values(PAYLOADS); - const byDir = new Map(); + const byDir = new Map(); for (const run of runs) { const c = run.testConfig; + const scenario = SCENARIO.exec(String(c.Scenario)); if ( c.ChainId === 8453 && payloads.includes(String(c.TransactionPayload)) && - SCENARIO.test(String(c.Scenario)) && + scenario && !String(c.BenchmarkRun).endsWith('--latest') ) { - byDir.set(run.outputDir, run); + byDir.set(run.outputDir, { + meta: run, + kind: scenario[1] ? 'glamsterdam' : 'main', + workload: WORKLOADS.find((w) => PAYLOADS[w] === c.TransactionPayload)!, + mgas: Number(scenario[2]), + }); } } return [...byDir.values()]; @@ -182,25 +208,26 @@ async function getJson(fetchImpl: typeof fetch, url: string) { return res.json().catch(() => null); } -async function loadRun(root: string, meta: BenchmarkRun, fetchImpl: typeof fetch): Promise { +async function loadRun(root: string, candidate: Candidate, fetchImpl: typeof fetch): Promise { + const { meta, kind, mgas } = candidate; const get = (file: string) => getJson(fetchImpl, `${root}${meta.outputDir}/${file}`); const [pipeline, provenance] = await Promise.all([get('pipeline-result.json'), get('provenance.json')]); - if (!pipeline || provenance?.source?.ref !== 'main') return null; + const source = provenance?.source; + if (!pipeline || !source || (kind === 'main' && source.ref !== 'main')) return null; const [validation, load] = await Promise.all([get('validation.json'), get('load-test-result.json')]); if (!validation || !load) throw new Error(`Missing results for ${meta.outputDir}`); - const mgas = Number(SCENARIO.exec(String(meta.testConfig.Scenario))![1]); const gps: number = load.throughput.gps; return { id: String(meta.testConfig.BenchmarkRun), outputDir: meta.outputDir, - workload: WORKLOADS.find((w) => PAYLOADS[w] === meta.testConfig.TransactionPayload)!, + workload: candidate.workload, mgas, gps, tps: load.throughput.tps, reverted: load.throughput.total_submitted ? (load.throughput.total_reverted ?? 0) / load.throughput.total_submitted : 0, blockLatency: load.block_latency?.p50 && load.block_latency?.p99 ? { p50Ms: durationMs(load.block_latency.p50), p99Ms: durationMs(load.block_latency.p99) } : null, passed: validation.comparable === true && gps >= PASS_RATIO * mgas * 1e6, - commit: provenance.source.revision, + commit: source.revision, createdAt: Date.parse(meta.createdAt), steps: pipeline.steps, replay: null, @@ -228,6 +255,43 @@ async function loadDetails(root: string, run: Run, fetchImpl: typeof fetch): Pro }; } +/** Keeps the latest in-window run for each workload and target gas rate. */ +function latestByWorkloadAndRate(runs: Run[], now: number): Map { + const latest = new Map(); + for (const run of runs) { + if (run.createdAt <= now - WINDOW_MS || run.createdAt > now) continue; + const key = `${run.workload}:${run.mgas}`; + const previous = latest.get(key); + if (!previous || run.createdAt > previous.createdAt) latest.set(key, run); + } + return latest; +} + +/** Pairs the newest Glamsterdam run with its main baseline at the same workload and rate. */ +function glamsterdamComparisons( + mainRuns: Run[], + glamRuns: Run[], + now: number, + failedGlamRuns: Map, + ): { pairs: GlamsterdamPair[]; errors: number } { + const main = latestByWorkloadAndRate(mainRuns, now); + const latestGlam = latestByWorkloadAndRate(glamRuns, now); + const blocked = new Set( + [...failedGlamRuns].flatMap(([key, failedAt]) => { + const loaded = latestGlam.get(key); + return main.has(key) && failedAt > now - WINDOW_MS && failedAt <= now && (!loaded || failedAt >= loaded.createdAt) ? [key] : []; + }), + ); + const pairs = [...latestGlam.values()] + .flatMap((glamsterdam) => { + const key = `${glamsterdam.workload}:${glamsterdam.mgas}`; + const baseline = main.get(key); + return baseline && !blocked.has(key) ? [{ workload: glamsterdam.workload, mgas: glamsterdam.mgas, main: baseline, glamsterdam }] : []; + }) + .sort((a, b) => WORKLOADS.indexOf(a.workload) - WORKLOADS.indexOf(b.workload) || a.mgas - b.mgas); + return { pairs, errors: blocked.size }; +} + // Rust Durations serialize as { secs, nanos }. const durationMs = (d: { secs: number; nanos: number }) => d.secs * 1e3 + d.nanos / 1e6; From 84501ecec63f8012ee945de21143a1d0344174f3 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 28 Oct 2026 15:47:17 -0400 Subject: [PATCH 2/8] fix(scaling): use 200ms block gas budget Co-authored-by: Toshi --- app/scaling/page.test.tsx | 4 ++-- app/scaling/page.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/scaling/page.test.tsx b/app/scaling/page.test.tsx index 5ab78e3..771fefa 100644 --- a/app/scaling/page.test.tsx +++ b/app/scaling/page.test.tsx @@ -22,7 +22,7 @@ const area = async (id: string) => renderToStaticMarkup(await ScalingAreaPage({ it('ends the first screen with the DA link, and no guardrails or benchmark links', async () => { const html = renderToStaticMarkup(await ScalingPage()); expect(html).toContain('DA by workload'); - expect(html).toContain('Block Gas Limit: 400 Mgas'); + expect(html).toContain('Block Gas Limit: 40 Mgas'); // The guardrails page stays, unlinked, while the first screen leaves guardrails out. expect(html).not.toMatch(/href="\/scaling\/guardrails"|Guardrails<\/h2>|SLOs/); expect(html).not.toMatch(/href="\/benchmark|href="\/scaling\/networking"/); @@ -34,7 +34,7 @@ it('ends the first screen with the DA link, and no guardrails or benchmark links it('orders the first screen: freshness, headline, ladder, ranked limits, subsystems by workload, work, links', async () => { const html = renderToStaticMarkup(await ScalingPage()); - const order = ['Block Gas Limit: 400 Mgas', 'Usage to 2026-01-02', 'Proven capacity', 'How close are we, and to what?', 'What breaks first', 'Subsystems by workload', 'Glamsterdam', 'Work log', 'DA by workload'].map((h) => html.indexOf(h)); + const order = ['Block Gas Limit: 40 Mgas', 'Usage to 2026-01-02', 'Proven capacity', 'How close are we, and to what?', 'What breaks first', 'Subsystems by workload', 'Glamsterdam', 'Work log', 'DA by workload'].map((h) => html.indexOf(h)); expect(order.every((i, k) => i > (order[k - 1] ?? -1))).toBe(true); expect(html).not.toMatch(/Subsystem detail|>Progress<|Ceilings by bottleneck|Usage data as of/); }); diff --git a/app/scaling/page.tsx b/app/scaling/page.tsx index 03c9cf3..4bfa4ea 100644 --- a/app/scaling/page.tsx +++ b/app/scaling/page.tsx @@ -9,8 +9,8 @@ import { Header, loadSnapshot, Unavailable } from './loadSnapshot'; // The snapshot is a runtime file, so render per request rather than at build. export const dynamic = 'force-dynamic'; -// Base L2 blocks are spaced two seconds apart; the snapshot stores this limit in Mgas/s. -const BASE_BLOCK_TIME_SECONDS = 2; +// Convert the configured Mgas/s rate to gas per 200ms block. +const BASE_BLOCK_TIME_SECONDS = 0.2; export default async function ScalingPage() { const loaded = await loadSnapshot(); From b9d744b4eb6ef6124202ebd9869a0c86cb4a01ee Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 28 Oct 2026 16:12:39 -0400 Subject: [PATCH 3/8] fix(scaling): calculate successful TPS from confirmed runs Co-authored-by: Toshi --- .../components/GlamsterdamComparison.tsx | 9 ++++---- app/scaling/components/scorecard.test.tsx | 8 +++---- app/scaling/proven/capacity.test.ts | 23 +++++++++++++++---- app/scaling/proven/capacity.ts | 6 ++--- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/app/scaling/components/GlamsterdamComparison.tsx b/app/scaling/components/GlamsterdamComparison.tsx index 4a18288..03cfb77 100644 --- a/app/scaling/components/GlamsterdamComparison.tsx +++ b/app/scaling/components/GlamsterdamComparison.tsx @@ -1,6 +1,6 @@ import { Text } from '../../components/ui/Text'; -import { DA_WORKLOADS, fmt } from '../da/workloads'; -import { runHref, successfulTps, type GlamsterdamPair } from '../proven/capacity'; +import { DA_WORKLOADS } from '../da/workloads'; +import { runHref, successfulTps, type GlamsterdamPair, type Run } from '../proven/capacity'; type Props = { configured: boolean; @@ -11,6 +11,7 @@ type Props = { const TH = 'px-3 py-2 text-left font-[600] text-bds-gray-60'; const TD = 'border-t border-bds-gray-10 px-3 py-2 align-top dark:border-white/10'; +const tps = (run: Run) => successfulTps(run).toLocaleString('en-US', { maximumFractionDigits: 1 }); /** Shows matched Glamsterdam and main benchmark jobs side by side. */ export function GlamsterdamComparisonSection({ configured, comparisons, comparisonErrors = 0, error }: Props) { @@ -67,7 +68,7 @@ export function GlamsterdamComparisonSection({ configured, comparisons, comparis {comparison.glamsterdam.id} - {fmt(successfulTps(comparison.glamsterdam))} TPS ·{' '} + {tps(comparison.glamsterdam)} TPS ·{' '} {(comparison.glamsterdam.gps / 1e6).toLocaleString('en-US', { maximumFractionDigits: 1 })} Mgas/s @@ -76,7 +77,7 @@ export function GlamsterdamComparisonSection({ configured, comparisons, comparis {comparison.main.id} - {fmt(successfulTps(comparison.main))} TPS ·{' '} + {tps(comparison.main)} TPS ·{' '} {(comparison.main.gps / 1e6).toLocaleString('en-US', { maximumFractionDigits: 1, })}{' '} diff --git a/app/scaling/components/scorecard.test.tsx b/app/scaling/components/scorecard.test.tsx index 7e99bdd..cff65b4 100644 --- a/app/scaling/components/scorecard.test.tsx +++ b/app/scaling/components/scorecard.test.tsx @@ -128,14 +128,14 @@ describe('freshness and headline', () => { describe('Glamsterdam comparisons', () => { it('shows matched jobs with their TPS, delivered Mgas/s and run links', () => { - const main = { ...run(200), id: 'main-mix-200', outputDir: 'main-mix-200' }; - const glamsterdam = { ...run(200), id: 'glam-mix-200', outputDir: 'glam-mix-200', gps: 197e6, tps: 2200 }; + const main = { ...run(200), id: 'main-mix-200', outputDir: 'main-mix-200', tps: 2200.24 }; + const glamsterdam = { ...run(200), id: 'glam-mix-200', outputDir: 'glam-mix-200', gps: 197e6, tps: 2100.14 }; swr.result = { data: bench(400, true, { comparisons: [{ workload: 'mix', mgas: 200, main, glamsterdam }] }) }; const section = render().glamsterdam; expect(text(section), 'the comparison section should identify its workload and target rate').toContain('Mainnet mix200 Mgas/s'); - expect(text(section), 'the Glamsterdam run should show its measured TPS and delivered gas rate').toContain('2,200 TPS · 197 Mgas/s'); - expect(text(section), 'the main run should show its measured TPS and delivered gas rate').toContain('2,100 TPS · 195 Mgas/s'); + expect(text(section), 'the Glamsterdam run should show its measured TPS to a decimal and delivered gas rate').toContain('2,100.1 TPS · 197 Mgas/s'); + expect(text(section), 'the main run should show its measured TPS to a decimal and delivered gas rate').toContain('2,200.2 TPS · 195 Mgas/s'); expect(section, 'the Glamsterdam run ID should link to its benchmark details').toContain('href="/scaling/runs/bench/glam-mix-200"'); expect(section, 'the main run ID should link to its benchmark details').toContain('href="/scaling/runs/bench/main-mix-200"'); }); diff --git a/app/scaling/proven/capacity.test.ts b/app/scaling/proven/capacity.test.ts index 4218644..5528921 100644 --- a/app/scaling/proven/capacity.test.ts +++ b/app/scaling/proven/capacity.test.ts @@ -1,6 +1,6 @@ import { expect, it } from 'vitest'; -import { loadProvenCapacity, PAYLOADS, type Workload } from './capacity'; +import { loadProvenCapacity, PAYLOADS, successfulTps, type Workload } from './capacity'; const NOW = Date.parse('2026-02-01T00:00:00Z'); const DAY = 86_400_000; @@ -10,9 +10,12 @@ type Spec = { dir: string; mgas: number; daysAgo: number; + comparable?: boolean; workload?: Workload; ref?: string | null; - comparable?: boolean; + confirmed?: number; + reverted?: number; + tps?: number; delivered?: number; scenario?: string; benchmarkRun?: string; @@ -73,7 +76,7 @@ function load(specs: Spec[]) { switch (file) { case 'validation.json': return Response.json({ comparable: s.comparable ?? true, reasons: [] }); case 'load-test-result.json': return Response.json({ - throughput: { gps: delivered * 1e6, tps: delivered * 10, total_submitted: 1000, total_reverted: 155 }, + throughput: { gps: delivered * 1e6, tps: s.tps ?? delivered * 10, total_submitted: 1000, total_confirmed: s.confirmed ?? 1000, total_reverted: s.reverted ?? 155 }, ...(s.noLatency ? {} : { block_latency: { p50: { secs: 0, nanos: 205_382_593 }, p99: { secs: 1, nanos: 6_265_374 } } }), }); case 'pipeline-result.json': return Response.json({ suite: 'synthetic', comparable: s.comparable ?? true, steps: STEPS(s) }); @@ -174,10 +177,10 @@ it('ignores alias copies and non-sustained scenarios', async () => { it('pairs the newest Glamsterdam jobs with main at matching workloads and rates without changing main capacity', async () => { const { comparisons, comparisonErrors, current, history, latestAt } = await load([ { dir: 'main-mix-200-old', mgas: 200, daysAgo: 3 }, - { dir: 'main-mix-200-new', mgas: 200, daysAgo: 1 }, + { dir: 'main-mix-200-new', mgas: 200, daysAgo: 1, tps: 2200, reverted: 100 }, { dir: 'main-payment-200', mgas: 200, daysAgo: 2, workload: 'payment' }, { dir: 'glam-mix-200-old', mgas: 200, daysAgo: 2, scenario: 'glamsterdam-sustained-200mgas', ref: 'glamsterdam' }, - { dir: 'glam-mix-200-latest', mgas: 200, daysAgo: 0, scenario: 'glamsterdam-sustained-200mgas', ref: 'feature', comparable: false }, + { dir: 'glam-mix-200-latest', mgas: 200, daysAgo: 0, scenario: 'glamsterdam-sustained-200mgas', ref: 'feature', comparable: false, tps: 2100, reverted: 100 }, { dir: 'glam-payment-200', mgas: 200, daysAgo: 1, workload: 'payment', scenario: 'glamsterdam-sustained-200mgas', ref: 'glamsterdam' }, { dir: 'glam-mix-300', mgas: 300, daysAgo: 1, scenario: 'glamsterdam-sustained-300mgas', ref: 'glamsterdam' }, { dir: 'main-swap-200', mgas: 200, daysAgo: 1, workload: 'swap' }, @@ -187,6 +190,8 @@ it('pairs the newest Glamsterdam jobs with main at matching workloads and rates ['mix', 200, 'main-mix-200-new', 'glam-mix-200-latest'], ['payment', 200, 'main-payment-200', 'glam-payment-200'], ], 'only the newest exact workload-and-rate matches should appear'); + expect(successfulTps(comparisons[0].main)).toBe(1980, 'the selected main run should retain its measured successful TPS'); + expect(successfulTps(comparisons[0].glamsterdam)).toBe(1890, 'the selected Glamsterdam run should retain its distinct measured successful TPS'); expect(comparisons[0].glamsterdam.passed).toBe(false, 'the newest run should remain visible even when it does not pass'); expect(current.mix).toMatchObject({ mgas: 200, run: { outputDir: 'main-mix-200-new' } }, 'Glamsterdam runs must not affect main proven capacity'); expect(history.map(({ mgas }) => mgas)).toEqual([200], 'Glamsterdam runs must not enter main capacity history'); @@ -216,6 +221,14 @@ it('keeps a newer loaded pair when only an older Glamsterdam run fails', async ( expect(comparisonErrors).toBe(0, 'an older failed job should not mark the latest loaded comparison unavailable'); }); +it('computes successful TPS by excluding reverts from confirmed transactions, not all submissions', async () => { + const { current } = await load([{ dir: 'success-tps-denominator', mgas: 100, daysAgo: 1, confirmed: 800, reverted: 100 }]); + + expect(current.mix?.run.reverted).toBe(0.125, 'reverts should be a share of confirmed transactions'); + expect(successfulTps(current.mix!.run)).toBe(875, 'successful TPS should exclude confirmed reverts from measured TPS'); +}); + + it('fails runs that are not comparable or deliver under 95% of target', async () => { const { current } = await load([ { dir: 'fail-100', mgas: 100, daysAgo: 3 }, diff --git a/app/scaling/proven/capacity.ts b/app/scaling/proven/capacity.ts index 39989ac..eab4521 100644 --- a/app/scaling/proven/capacity.ts +++ b/app/scaling/proven/capacity.ts @@ -61,7 +61,7 @@ export type Run = { mgas: number; gps: number; tps: number; - // Share of submitted transactions that reverted; they still use gas. + // Share of confirmed transactions that reverted; they still use gas. reverted: number; passed: boolean; commit: string; @@ -75,7 +75,7 @@ export type Run = { blockLatency: { p50Ms: number; p99Ms: number } | null; }; -// Reverted transactions count toward gas/s but not toward transactions per second. +// The load result's tps includes confirmed transactions that reverted, so exclude them for successful TPS. export const successfulTps = (run: Run) => run.tps * (1 - run.reverted); export type Proven = { mgas: number; atTop: boolean; run: Run }; @@ -224,7 +224,7 @@ async function loadRun(root: string, candidate: Candidate, fetchImpl: typeof fet mgas, gps, tps: load.throughput.tps, - reverted: load.throughput.total_submitted ? (load.throughput.total_reverted ?? 0) / load.throughput.total_submitted : 0, + reverted: load.throughput.total_confirmed ? (load.throughput.total_reverted ?? 0) / load.throughput.total_confirmed : 0, blockLatency: load.block_latency?.p50 && load.block_latency?.p99 ? { p50Ms: durationMs(load.block_latency.p50), p99Ms: durationMs(load.block_latency.p99) } : null, passed: validation.comparable === true && gps >= PASS_RATIO * mgas * 1e6, commit: source.revision, From 6626f71890387e75d9a03ccc868ff47b47427084 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 28 Oct 2026 18:05:29 -0400 Subject: [PATCH 4/8] feat(scaling): match Glamsterdam runs by TPS Co-authored-by: Toshi --- .../components/GlamsterdamComparison.test.tsx | 37 +++++++++++++ .../components/GlamsterdamComparison.tsx | 55 +++++++++---------- app/scaling/components/scorecard.test.tsx | 12 ++-- app/scaling/proven/capacity.test.ts | 32 +++++++++-- app/scaling/proven/capacity.ts | 40 ++++++++++---- 5 files changed, 125 insertions(+), 51 deletions(-) diff --git a/app/scaling/components/GlamsterdamComparison.test.tsx b/app/scaling/components/GlamsterdamComparison.test.tsx index 8402c3c..70f8d79 100644 --- a/app/scaling/components/GlamsterdamComparison.test.tsx +++ b/app/scaling/components/GlamsterdamComparison.test.tsx @@ -2,8 +2,45 @@ import { renderToStaticMarkup } from 'react-dom/server'; import { expect, it } from 'vitest'; import { GlamsterdamComparisonSection } from './GlamsterdamComparison'; +import type { GlamsterdamPair, Run } from '../proven/capacity'; + +const run = (id: string, mgas: number, tps: number, gps: number): Run => ({ + id, + outputDir: id, + workload: 'mix', + mgas, + gps, + tps, + reverted: 0, + passed: true, + commit: 'abc123', + createdAt: 1, + steps: [], + replay: null, + proofNode: null, + da: null, + storage: null, + blockLatency: null, +}); it('shows the benchmark source setup message when comparisons are unavailable', () => { const html = renderToStaticMarkup(); expect(html, 'the section should explain why comparison data is unavailable').toContain('Benchmark source is not configured.'); }); + +it('shows independent targets and measured results for the closest-TPS pair', () => { + const comparison: GlamsterdamPair = { + workload: 'mix', + glamsterdam: run('glamsterdam-200', 200, 2238.2, 204.5e6), + main: run('main-150', 150, 2000, 150e6), + }; + const html = renderToStaticMarkup(); + + expect(html).toContain('Glamsterdam job'); + expect(html).toContain('Main job · closest TPS'); + expect(html).toContain('Target: 200 Mgas/s'); + expect(html).toContain('Target: 150 Mgas/s'); + expect(html).toContain('2,238.2 TPS · 204.5 Mgas/s actual'); + expect(html).toContain('2,000 TPS · 150 Mgas/s actual'); + expect(html).toContain('238.2 TPS'); +}); diff --git a/app/scaling/components/GlamsterdamComparison.tsx b/app/scaling/components/GlamsterdamComparison.tsx index 03cfb77..5930b17 100644 --- a/app/scaling/components/GlamsterdamComparison.tsx +++ b/app/scaling/components/GlamsterdamComparison.tsx @@ -12,8 +12,13 @@ type Props = { const TH = 'px-3 py-2 text-left font-[600] text-bds-gray-60'; const TD = 'border-t border-bds-gray-10 px-3 py-2 align-top dark:border-white/10'; const tps = (run: Run) => successfulTps(run).toLocaleString('en-US', { maximumFractionDigits: 1 }); +const rate = (run: Run) => (run.gps / 1e6).toLocaleString('en-US', { maximumFractionDigits: 1 }); +const tpsGap = (comparison: GlamsterdamPair) => + Math.abs(successfulTps(comparison.main) - successfulTps(comparison.glamsterdam)).toLocaleString('en-US', { + maximumFractionDigits: 1, + }); -/** Shows matched Glamsterdam and main benchmark jobs side by side. */ +/** Shows Glamsterdam runs beside the closest-TPS main run for the same workload. */ export function GlamsterdamComparisonSection({ configured, comparisons, comparisonErrors = 0, error }: Props) { const message = !configured ? 'Benchmark source is not configured.' @@ -24,7 +29,7 @@ export function GlamsterdamComparisonSection({ configured, comparisons, comparis : comparisons.length === 0 ? comparisonErrors > 0 ? 'A matching Glamsterdam run could not be loaded; its comparison is omitted.' - : 'No matching Glamsterdam and main jobs in the last 14 days.' + : 'No Glamsterdam and main jobs for the same workload in the last 14 days.' : null; return ( @@ -33,7 +38,7 @@ export function GlamsterdamComparisonSection({ configured, comparisons, comparis Glamsterdam - Compared with main at the same workload and target Mgas/s. TPS counts successful transactions. + Latest run per target in the last 14 days; each Glamsterdam run is paired with the closest successful-TPS main run for the same workload. Targets are independent; TPS excludes reverted transactions. {message ? ( @@ -43,7 +48,7 @@ export function GlamsterdamComparisonSection({ configured, comparisons, comparis <> {comparisonErrors > 0 ? ( - Some matching Glamsterdam runs could not be loaded; affected comparisons are omitted. + Some Glamsterdam runs could not be loaded; affected comparisons are omitted. ) : null}
@@ -51,39 +56,31 @@ export function GlamsterdamComparisonSection({ configured, comparisons, comparis Workload - Target Glamsterdam job - Main job + Main job · closest TPS + TPS gap {comparisons!.map((comparison) => { const workload = DA_WORKLOADS.find((item) => item.payload === comparison.workload)?.label ?? comparison.workload; return ( - + {workload} - {comparison.mgas.toLocaleString('en-US')} Mgas/s - - - {comparison.glamsterdam.id} - - - {tps(comparison.glamsterdam)} TPS ·{' '} - {(comparison.glamsterdam.gps / 1e6).toLocaleString('en-US', { maximumFractionDigits: 1 })} Mgas/s - - - - - {comparison.main.id} - - - {tps(comparison.main)} TPS ·{' '} - {(comparison.main.gps / 1e6).toLocaleString('en-US', { - maximumFractionDigits: 1, - })}{' '} - Mgas/s - - + {[comparison.glamsterdam, comparison.main].map((run) => ( + + + {run.id} + + + Target: {run.mgas.toLocaleString('en-US')} Mgas/s + + + {tps(run)} TPS · {rate(run)} Mgas/s actual + + + ))} + {tpsGap(comparison)} TPS ); })} diff --git a/app/scaling/components/scorecard.test.tsx b/app/scaling/components/scorecard.test.tsx index cff65b4..f230012 100644 --- a/app/scaling/components/scorecard.test.tsx +++ b/app/scaling/components/scorecard.test.tsx @@ -130,12 +130,14 @@ describe('Glamsterdam comparisons', () => { it('shows matched jobs with their TPS, delivered Mgas/s and run links', () => { const main = { ...run(200), id: 'main-mix-200', outputDir: 'main-mix-200', tps: 2200.24 }; const glamsterdam = { ...run(200), id: 'glam-mix-200', outputDir: 'glam-mix-200', gps: 197e6, tps: 2100.14 }; - swr.result = { data: bench(400, true, { comparisons: [{ workload: 'mix', mgas: 200, main, glamsterdam }] }) }; + swr.result = { data: bench(400, true, { comparisons: [{ workload: 'mix', main, glamsterdam }] }) }; const section = render().glamsterdam; - expect(text(section), 'the comparison section should identify its workload and target rate').toContain('Mainnet mix200 Mgas/s'); - expect(text(section), 'the Glamsterdam run should show its measured TPS to a decimal and delivered gas rate').toContain('2,100.1 TPS · 197 Mgas/s'); - expect(text(section), 'the main run should show its measured TPS to a decimal and delivered gas rate').toContain('2,200.2 TPS · 195 Mgas/s'); + expect(text(section), 'the comparison section should identify its workload').toContain('Mainnet mix'); + expect(text(section).match(/Target: 200 Mgas\/s/g), 'the two jobs should each show their own target').toHaveLength(2); + expect(text(section), 'the Glamsterdam run should show its target and measured successful TPS and gas rate').toContain('Target: 200 Mgas/s2,100.1 TPS · 197 Mgas/s actual'); + expect(text(section), 'the main run should show its target and measured successful TPS and gas rate').toContain('Target: 200 Mgas/s2,200.2 TPS · 195 Mgas/s actual'); + expect(text(section), 'the table should show the gap between successful TPS results').toContain('100.1 TPS'); expect(section, 'the Glamsterdam run ID should link to its benchmark details').toContain('href="/scaling/runs/bench/glam-mix-200"'); expect(section, 'the main run ID should link to its benchmark details').toContain('href="/scaling/runs/bench/main-mix-200"'); }); @@ -148,7 +150,7 @@ describe('Glamsterdam comparisons', () => { expect(text(render().glamsterdam), 'a failed benchmark request should show an error state').toContain('Could not load Glamsterdam benchmark data.'); swr.result = { data: bench(400, true) }; - expect(text(render().glamsterdam), 'an empty result should state that no matching pairs were found').toContain('No matching Glamsterdam and main jobs in the last 14 days.'); + expect(text(render().glamsterdam), 'an empty result should state that no same-workload pairs were found').toContain('No Glamsterdam and main jobs for the same workload in the last 14 days.'); swr.result = { data: bench(400, true, { comparisonErrors: 1 }) }; expect(text(render().glamsterdam), 'a failed latest job should explain why its comparison is missing').toContain('A matching Glamsterdam run could not be loaded; its comparison is omitted.'); }); diff --git a/app/scaling/proven/capacity.test.ts b/app/scaling/proven/capacity.test.ts index 5528921..d5d2de9 100644 --- a/app/scaling/proven/capacity.test.ts +++ b/app/scaling/proven/capacity.test.ts @@ -174,7 +174,28 @@ it('ignores alias copies and non-sustained scenarios', async () => { expect(unknown).toEqual([]); }); -it('pairs the newest Glamsterdam jobs with main at matching workloads and rates without changing main capacity', async () => { +it('pairs each Glamsterdam target with the closest-TPS main run at any target for the same workload', async () => { + const { comparisons, comparisonErrors } = await load([ + { dir: 'nearest-main-mix-150', mgas: 150, daysAgo: 2, tps: 2000, reverted: 0 }, + { dir: 'nearest-main-mix-200', mgas: 200, daysAgo: 1, tps: 2970, reverted: 0 }, + { dir: 'nearest-main-payment-300', mgas: 300, daysAgo: 1, workload: 'payment', tps: 1500, reverted: 0 }, + { dir: 'nearest-glam-mix-200', mgas: 200, daysAgo: 0, scenario: 'glamsterdam-sustained-200mgas', ref: 'glamsterdam', tps: 2238.2, reverted: 0 }, + { dir: 'nearest-glam-payment-250', mgas: 250, daysAgo: 0, workload: 'payment', scenario: 'glamsterdam-sustained-250mgas', ref: 'glamsterdam', tps: 1520, reverted: 0 }, + ]); + + expect( + comparisons.map(({ workload, main, glamsterdam }) => [workload, glamsterdam.mgas, main.outputDir, main.mgas]), + ).toEqual( + [ + ['mix', 200, 'nearest-main-mix-150', 150], + ['payment', 250, 'nearest-main-payment-300', 300], + ], + 'each Glamsterdam run should show its independent target and the nearest main TPS match for that workload', + ); + expect(comparisonErrors).toBe(0); +}); + +it('pairs the newest Glamsterdam jobs with main at matching workloads without changing main capacity', async () => { const { comparisons, comparisonErrors, current, history, latestAt } = await load([ { dir: 'main-mix-200-old', mgas: 200, daysAgo: 3 }, { dir: 'main-mix-200-new', mgas: 200, daysAgo: 1, tps: 2200, reverted: 100 }, @@ -186,10 +207,11 @@ it('pairs the newest Glamsterdam jobs with main at matching workloads and rates { dir: 'main-swap-200', mgas: 200, daysAgo: 1, workload: 'swap' }, ]); - expect(comparisons.map(({ workload, mgas, main, glamsterdam }) => [workload, mgas, main.outputDir, glamsterdam.outputDir])).toEqual([ - ['mix', 200, 'main-mix-200-new', 'glam-mix-200-latest'], - ['payment', 200, 'main-payment-200', 'glam-payment-200'], - ], 'only the newest exact workload-and-rate matches should appear'); + expect(comparisons.map(({ workload, main, glamsterdam }) => [workload, glamsterdam.mgas, main.outputDir])).toEqual([ + ['mix', 200, 'main-mix-200-new'], + ['mix', 300, 'main-mix-200-new'], + ['payment', 200, 'main-payment-200'], + ]); expect(successfulTps(comparisons[0].main)).toBe(1980, 'the selected main run should retain its measured successful TPS'); expect(successfulTps(comparisons[0].glamsterdam)).toBe(1890, 'the selected Glamsterdam run should retain its distinct measured successful TPS'); expect(comparisons[0].glamsterdam.passed).toBe(false, 'the newest run should remain visible even when it does not pass'); diff --git a/app/scaling/proven/capacity.ts b/app/scaling/proven/capacity.ts index eab4521..9eb3782 100644 --- a/app/scaling/proven/capacity.ts +++ b/app/scaling/proven/capacity.ts @@ -102,8 +102,8 @@ export type ProvenCapacity = { unknown: string[]; }; -/** A matched Glamsterdam and main run at the same workload and target gas rate. */ -export type GlamsterdamPair = { workload: Workload; mgas: number; main: Run; glamsterdam: Run }; +/** A Glamsterdam run paired with the closest-TPS main run for the same workload. */ +export type GlamsterdamPair = { workload: Workload; main: Run; glamsterdam: Run }; const WINDOW_MS = 14 * 24 * 60 * 60 * 1000; const PASS_RATIO = 0.95; @@ -267,28 +267,44 @@ function latestByWorkloadAndRate(runs: Run[], now: number): Map { return latest; } -/** Pairs the newest Glamsterdam run with its main baseline at the same workload and rate. */ +/** Pairs each latest Glamsterdam run with the closest-TPS main run for the same workload. */ function glamsterdamComparisons( mainRuns: Run[], glamRuns: Run[], now: number, failedGlamRuns: Map, - ): { pairs: GlamsterdamPair[]; errors: number } { - const main = latestByWorkloadAndRate(mainRuns, now); +): { pairs: GlamsterdamPair[]; errors: number } { + const mainByWorkload = new Map(); + for (const run of latestByWorkloadAndRate(mainRuns, now).values()) { + mainByWorkload.set(run.workload, [...(mainByWorkload.get(run.workload) ?? []), run]); + } const latestGlam = latestByWorkloadAndRate(glamRuns, now); const blocked = new Set( [...failedGlamRuns].flatMap(([key, failedAt]) => { const loaded = latestGlam.get(key); - return main.has(key) && failedAt > now - WINDOW_MS && failedAt <= now && (!loaded || failedAt >= loaded.createdAt) ? [key] : []; + const workload = key.split(':')[0] as Workload; + const hasMainRun = (mainByWorkload.get(workload)?.length ?? 0) > 0; + return hasMainRun && failedAt > now - WINDOW_MS && failedAt <= now && (!loaded || failedAt >= loaded.createdAt) ? [key] : []; }), ); - const pairs = [...latestGlam.values()] - .flatMap((glamsterdam) => { - const key = `${glamsterdam.workload}:${glamsterdam.mgas}`; - const baseline = main.get(key); - return baseline && !blocked.has(key) ? [{ workload: glamsterdam.workload, mgas: glamsterdam.mgas, main: baseline, glamsterdam }] : []; + const pairs = [...latestGlam.entries()] + .flatMap(([key, glamsterdam]) => { + const baselines = mainByWorkload.get(glamsterdam.workload) ?? []; + if (blocked.has(key) || baselines.length === 0) return []; + const main = baselines.reduce((closest, candidate) => { + const candidateGap = Math.abs(successfulTps(candidate) - successfulTps(glamsterdam)); + const closestGap = Math.abs(successfulTps(closest) - successfulTps(glamsterdam)); + return candidateGap < closestGap || (candidateGap === closestGap && candidate.createdAt > closest.createdAt) + ? candidate + : closest; + }); + return [{ workload: glamsterdam.workload, main, glamsterdam }]; }) - .sort((a, b) => WORKLOADS.indexOf(a.workload) - WORKLOADS.indexOf(b.workload) || a.mgas - b.mgas); + .sort( + (a, b) => + WORKLOADS.indexOf(a.workload) - WORKLOADS.indexOf(b.workload) || + a.glamsterdam.mgas - b.glamsterdam.mgas, + ); return { pairs, errors: blocked.size }; } From 0c1adedc2a21da6a68f2af4051813b1214ac0158 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 28 Oct 2026 18:17:20 -0400 Subject: [PATCH 5/8] feat(scaling): show all main comparison candidates Co-authored-by: Toshi --- .../components/GlamsterdamComparison.test.tsx | 10 ++++- .../components/GlamsterdamComparison.tsx | 39 ++++++++++++++++++- app/scaling/components/Scorecard.tsx | 2 +- app/scaling/proven/capacity.test.ts | 9 ++++- app/scaling/proven/capacity.ts | 13 +++++-- 5 files changed, 65 insertions(+), 8 deletions(-) diff --git a/app/scaling/components/GlamsterdamComparison.test.tsx b/app/scaling/components/GlamsterdamComparison.test.tsx index 70f8d79..ced25a3 100644 --- a/app/scaling/components/GlamsterdamComparison.test.tsx +++ b/app/scaling/components/GlamsterdamComparison.test.tsx @@ -34,7 +34,10 @@ it('shows independent targets and measured results for the closest-TPS pair', () glamsterdam: run('glamsterdam-200', 200, 2238.2, 204.5e6), main: run('main-150', 150, 2000, 150e6), }; - const html = renderToStaticMarkup(); + const candidates = [run('main-200', 200, 2970.6, 203e6), run('main-400', 400, 5942.5, 406.5e6)]; + const html = renderToStaticMarkup( + , + ); expect(html).toContain('Glamsterdam job'); expect(html).toContain('Main job · closest TPS'); @@ -43,4 +46,9 @@ it('shows independent targets and measured results for the closest-TPS pair', () expect(html).toContain('2,238.2 TPS · 204.5 Mgas/s actual'); expect(html).toContain('2,000 TPS · 150 Mgas/s actual'); expect(html).toContain('238.2 TPS'); + + expect(html).toContain('Main candidates · latest eligible run per workload and target'); + expect(html).toContain('main-200'); + expect(html).toContain('main-400'); + expect(html).toContain('5,942.5 TPS'); }); diff --git a/app/scaling/components/GlamsterdamComparison.tsx b/app/scaling/components/GlamsterdamComparison.tsx index 5930b17..cad2ad9 100644 --- a/app/scaling/components/GlamsterdamComparison.tsx +++ b/app/scaling/components/GlamsterdamComparison.tsx @@ -5,6 +5,7 @@ import { runHref, successfulTps, type GlamsterdamPair, type Run } from '../prove type Props = { configured: boolean; comparisons?: GlamsterdamPair[]; + mainCandidates?: Run[]; comparisonErrors?: number; error?: unknown; }; @@ -19,7 +20,7 @@ const tpsGap = (comparison: GlamsterdamPair) => }); /** Shows Glamsterdam runs beside the closest-TPS main run for the same workload. */ -export function GlamsterdamComparisonSection({ configured, comparisons, comparisonErrors = 0, error }: Props) { +export function GlamsterdamComparisonSection({ configured, comparisons, comparisonErrors = 0, mainCandidates, error }: Props) { const message = !configured ? 'Benchmark source is not configured.' : error @@ -87,6 +88,42 @@ export function GlamsterdamComparisonSection({ configured, comparisons, comparis
+ {mainCandidates?.length ? ( +
+ Main candidates · latest eligible run per workload and target +
+ + + + + + + + + + + + {mainCandidates.map((run) => { + const workload = DA_WORKLOADS.find((item) => item.payload === run.workload)?.label ?? run.workload; + return ( + + + + + + + + ); + })} + +
WorkloadMain jobTargetSuccessful TPSActual Mgas/s
{workload} + + {run.id} + + {run.mgas.toLocaleString('en-US')} Mgas/s{tps(run)} TPS{rate(run)} Mgas/s
+
+
+ ) : null} )} diff --git a/app/scaling/components/Scorecard.tsx b/app/scaling/components/Scorecard.tsx index 3881d96..8ecdb16 100644 --- a/app/scaling/components/Scorecard.tsx +++ b/app/scaling/components/Scorecard.tsx @@ -52,7 +52,7 @@ export function Scorecard({ snapshot, benchUrl }: { snapshot: Snapshot; benchUrl - + ); diff --git a/app/scaling/proven/capacity.test.ts b/app/scaling/proven/capacity.test.ts index d5d2de9..5854c9e 100644 --- a/app/scaling/proven/capacity.test.ts +++ b/app/scaling/proven/capacity.test.ts @@ -174,8 +174,8 @@ it('ignores alias copies and non-sustained scenarios', async () => { expect(unknown).toEqual([]); }); -it('pairs each Glamsterdam target with the closest-TPS main run at any target for the same workload', async () => { - const { comparisons, comparisonErrors } = await load([ +it('pairs each Glamsterdam target with the closest-TPS main run at any target for the same workload and lists all latest main candidates', async () => { + const { comparisons, comparisonErrors, mainCandidates } = await load([ { dir: 'nearest-main-mix-150', mgas: 150, daysAgo: 2, tps: 2000, reverted: 0 }, { dir: 'nearest-main-mix-200', mgas: 200, daysAgo: 1, tps: 2970, reverted: 0 }, { dir: 'nearest-main-payment-300', mgas: 300, daysAgo: 1, workload: 'payment', tps: 1500, reverted: 0 }, @@ -193,6 +193,11 @@ it('pairs each Glamsterdam target with the closest-TPS main run at any target fo 'each Glamsterdam run should show its independent target and the nearest main TPS match for that workload', ); expect(comparisonErrors).toBe(0); + expect(mainCandidates.map(({ workload, mgas, outputDir }) => [workload, mgas, outputDir])).toEqual([ + ['mix', 150, 'nearest-main-mix-150'], + ['mix', 200, 'nearest-main-mix-200'], + ['payment', 300, 'nearest-main-payment-300'], + ]); }); it('pairs the newest Glamsterdam jobs with main at matching workloads without changing main capacity', async () => { diff --git a/app/scaling/proven/capacity.ts b/app/scaling/proven/capacity.ts index 9eb3782..673e4cf 100644 --- a/app/scaling/proven/capacity.ts +++ b/app/scaling/proven/capacity.ts @@ -96,6 +96,8 @@ export type ProvenCapacity = { current: Record; da: Record; comparisons: GlamsterdamPair[]; + /** Latest eligible main run per workload and target in the comparison window. */ + mainCandidates?: Run[]; comparisonErrors: number; history: Step[]; latestAt: number | null; @@ -172,6 +174,7 @@ export async function loadProvenCapacity( da: Object.fromEntries(WORKLOADS.map((w) => [w, daRun(w)])) as ProvenCapacity['da'], comparisons: comparison.pairs, comparisonErrors: comparison.errors, + mainCandidates: comparison.mainCandidates, history: history(of(full, 'mix'), now), latestAt: runs.length ? Math.max(...runs.map((r) => r.createdAt)) : null, unknown, @@ -273,9 +276,13 @@ function glamsterdamComparisons( glamRuns: Run[], now: number, failedGlamRuns: Map, -): { pairs: GlamsterdamPair[]; errors: number } { +): { pairs: GlamsterdamPair[]; errors: number; mainCandidates: Run[] } { + const latestMain = latestByWorkloadAndRate(mainRuns, now); + const mainCandidates = [...latestMain.values()].sort( + (a, b) => WORKLOADS.indexOf(a.workload) - WORKLOADS.indexOf(b.workload) || a.mgas - b.mgas, + ); const mainByWorkload = new Map(); - for (const run of latestByWorkloadAndRate(mainRuns, now).values()) { + for (const run of latestMain.values()) { mainByWorkload.set(run.workload, [...(mainByWorkload.get(run.workload) ?? []), run]); } const latestGlam = latestByWorkloadAndRate(glamRuns, now); @@ -305,7 +312,7 @@ function glamsterdamComparisons( WORKLOADS.indexOf(a.workload) - WORKLOADS.indexOf(b.workload) || a.glamsterdam.mgas - b.glamsterdam.mgas, ); - return { pairs, errors: blocked.size }; + return { pairs, errors: blocked.size, mainCandidates }; } // Rust Durations serialize as { secs, nanos }. From fe3f50ae368244068bdb02c0bf0f3747178e1d58 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 28 Oct 2026 19:40:42 -0400 Subject: [PATCH 6/8] feat(scaling): list Glamsterdam and main jobs side by side Co-authored-by: Toshi --- .../components/GlamsterdamComparison.test.tsx | 45 +++-- .../components/GlamsterdamComparison.tsx | 161 ++++++++---------- app/scaling/components/Scorecard.tsx | 2 +- app/scaling/components/scorecard.test.tsx | 44 ++--- app/scaling/proven/capacity.test.ts | 85 ++++----- app/scaling/proven/capacity.ts | 62 +++---- 6 files changed, 176 insertions(+), 223 deletions(-) diff --git a/app/scaling/components/GlamsterdamComparison.test.tsx b/app/scaling/components/GlamsterdamComparison.test.tsx index ced25a3..25a7ca0 100644 --- a/app/scaling/components/GlamsterdamComparison.test.tsx +++ b/app/scaling/components/GlamsterdamComparison.test.tsx @@ -2,12 +2,12 @@ import { renderToStaticMarkup } from 'react-dom/server'; import { expect, it } from 'vitest'; import { GlamsterdamComparisonSection } from './GlamsterdamComparison'; -import type { GlamsterdamPair, Run } from '../proven/capacity'; +import type { Run } from '../proven/capacity'; -const run = (id: string, mgas: number, tps: number, gps: number): Run => ({ +const run = (id: string, mgas: number, tps: number, gps: number, workload: Run['workload'] = 'mix'): Run => ({ id, outputDir: id, - workload: 'mix', + workload, mgas, gps, tps, @@ -23,32 +23,27 @@ const run = (id: string, mgas: number, tps: number, gps: number): Run => ({ blockLatency: null, }); -it('shows the benchmark source setup message when comparisons are unavailable', () => { +it('shows the benchmark source setup message when jobs are unavailable', () => { const html = renderToStaticMarkup(); - expect(html, 'the section should explain why comparison data is unavailable').toContain('Benchmark source is not configured.'); + expect(html).toContain('Benchmark source is not configured.'); }); -it('shows independent targets and measured results for the closest-TPS pair', () => { - const comparison: GlamsterdamPair = { - workload: 'mix', - glamsterdam: run('glamsterdam-200', 200, 2238.2, 204.5e6), - main: run('main-150', 150, 2000, 150e6), - }; - const candidates = [run('main-200', 200, 2970.6, 203e6), run('main-400', 400, 5942.5, 406.5e6)]; +it('shows Glamsterdam and Main jobs side by side without pairing or TPS-gap calculations', () => { + const glamsterdamRuns = [run('glam-mix-200', 200, 2238.2, 204.5e6), run('glam-pay-300', 300, 1520, 302e6, 'payment')]; + const mainRuns = [run('main-mix-200', 200, 2970.6, 203e6), run('main-mix-400', 400, 5942.5, 406.5e6)]; const html = renderToStaticMarkup( - , + , ); - expect(html).toContain('Glamsterdam job'); - expect(html).toContain('Main job · closest TPS'); - expect(html).toContain('Target: 200 Mgas/s'); - expect(html).toContain('Target: 150 Mgas/s'); - expect(html).toContain('2,238.2 TPS · 204.5 Mgas/s actual'); - expect(html).toContain('2,000 TPS · 150 Mgas/s actual'); - expect(html).toContain('238.2 TPS'); - - expect(html).toContain('Main candidates · latest eligible run per workload and target'); - expect(html).toContain('main-200'); - expect(html).toContain('main-400'); - expect(html).toContain('5,942.5 TPS'); + expect(html).toContain('Glamsterdam jobs'); + expect(html).toContain('Main jobs'); + expect(html).toContain('glam-mix-200'); + expect(html).toContain('glam-pay-300'); + expect(html).toContain('main-mix-200'); + expect(html).toContain('main-mix-400'); + expect(html).toContain('2,238.2'); + expect(html).toContain('2,970.6'); + expect(html).toContain('5,942.5'); + expect(html).not.toContain('closest TPS'); + expect(html).not.toContain('TPS gap'); }); diff --git a/app/scaling/components/GlamsterdamComparison.tsx b/app/scaling/components/GlamsterdamComparison.tsx index cad2ad9..d9d9cf9 100644 --- a/app/scaling/components/GlamsterdamComparison.tsx +++ b/app/scaling/components/GlamsterdamComparison.tsx @@ -1,12 +1,12 @@ import { Text } from '../../components/ui/Text'; import { DA_WORKLOADS } from '../da/workloads'; -import { runHref, successfulTps, type GlamsterdamPair, type Run } from '../proven/capacity'; +import { runHref, successfulTps, type Run } from '../proven/capacity'; type Props = { configured: boolean; - comparisons?: GlamsterdamPair[]; - mainCandidates?: Run[]; - comparisonErrors?: number; + glamsterdamRuns?: Run[]; + mainRuns?: Run[]; + glamsterdamErrors?: number; error?: unknown; }; @@ -14,32 +14,75 @@ const TH = 'px-3 py-2 text-left font-[600] text-bds-gray-60'; const TD = 'border-t border-bds-gray-10 px-3 py-2 align-top dark:border-white/10'; const tps = (run: Run) => successfulTps(run).toLocaleString('en-US', { maximumFractionDigits: 1 }); const rate = (run: Run) => (run.gps / 1e6).toLocaleString('en-US', { maximumFractionDigits: 1 }); -const tpsGap = (comparison: GlamsterdamPair) => - Math.abs(successfulTps(comparison.main) - successfulTps(comparison.glamsterdam)).toLocaleString('en-US', { - maximumFractionDigits: 1, - }); -/** Shows Glamsterdam runs beside the closest-TPS main run for the same workload. */ -export function GlamsterdamComparisonSection({ configured, comparisons, comparisonErrors = 0, mainCandidates, error }: Props) { +function RunTable({ title, runs }: { title: string; runs: Run[] }) { + return ( +
+ + {title} + + {runs.length === 0 ? ( + No eligible jobs in the last 14 days. + ) : ( +
+ + + + + + + + + + + + {runs.map((run) => { + const workload = DA_WORKLOADS.find((item) => item.payload === run.workload)?.label ?? run.workload; + return ( + + + + + + + + ); + })} + +
WorkloadTargetJobSuccessful TPSActual Mgas/s
{workload}{run.mgas.toLocaleString('en-US')} Mgas/s + + {run.id} + + {tps(run)}{rate(run)}
+
+ )} +
+ ); +} + +/** Shows the latest Glamsterdam and Main jobs in separate side-by-side lists. */ +export function GlamsterdamComparisonSection({ + configured, + glamsterdamRuns, + mainRuns, + glamsterdamErrors = 0, + error, +}: Props) { const message = !configured ? 'Benchmark source is not configured.' : error - ? 'Could not load Glamsterdam benchmark data.' - : !comparisons - ? 'Loading Glamsterdam comparisons…' - : comparisons.length === 0 - ? comparisonErrors > 0 - ? 'A matching Glamsterdam run could not be loaded; its comparison is omitted.' - : 'No Glamsterdam and main jobs for the same workload in the last 14 days.' - : null; + ? 'Could not load benchmark data.' + : !glamsterdamRuns || !mainRuns + ? 'Loading benchmark runs…' + : null; return ( -
+
Glamsterdam - Latest run per target in the last 14 days; each Glamsterdam run is paired with the closest successful-TPS main run for the same workload. Targets are independent; TPS excludes reverted transactions. + Latest run per workload and target in the last 14 days. TPS excludes reverted transactions; gas throughput is measured. {message ? ( @@ -47,83 +90,15 @@ export function GlamsterdamComparisonSection({ configured, comparisons, comparis ) : ( <> - {comparisonErrors > 0 ? ( + {glamsterdamErrors > 0 ? ( - Some Glamsterdam runs could not be loaded; affected comparisons are omitted. + {glamsterdamErrors} latest Glamsterdam job{glamsterdamErrors === 1 ? '' : 's'} could not be loaded. ) : null} -
- - - - - - - - - - - {comparisons!.map((comparison) => { - const workload = DA_WORKLOADS.find((item) => item.payload === comparison.workload)?.label ?? comparison.workload; - return ( - - - {[comparison.glamsterdam, comparison.main].map((run) => ( - - ))} - - - ); - })} - -
WorkloadGlamsterdam jobMain job · closest TPSTPS gap
{workload} - - {run.id} - - - Target: {run.mgas.toLocaleString('en-US')} Mgas/s - - - {tps(run)} TPS · {rate(run)} Mgas/s actual - - {tpsGap(comparison)} TPS
+
+ +
- {mainCandidates?.length ? ( -
- Main candidates · latest eligible run per workload and target -
- - - - - - - - - - - - {mainCandidates.map((run) => { - const workload = DA_WORKLOADS.find((item) => item.payload === run.workload)?.label ?? run.workload; - return ( - - - - - - - - ); - })} - -
WorkloadMain jobTargetSuccessful TPSActual Mgas/s
{workload} - - {run.id} - - {run.mgas.toLocaleString('en-US')} Mgas/s{tps(run)} TPS{rate(run)} Mgas/s
-
-
- ) : null} )}
diff --git a/app/scaling/components/Scorecard.tsx b/app/scaling/components/Scorecard.tsx index 8ecdb16..3d1bf18 100644 --- a/app/scaling/components/Scorecard.tsx +++ b/app/scaling/components/Scorecard.tsx @@ -52,7 +52,7 @@ export function Scorecard({ snapshot, benchUrl }: { snapshot: Snapshot; benchUrl - + ); diff --git a/app/scaling/components/scorecard.test.tsx b/app/scaling/components/scorecard.test.tsx index f230012..7f9ede0 100644 --- a/app/scaling/components/scorecard.test.tsx +++ b/app/scaling/components/scorecard.test.tsx @@ -54,8 +54,9 @@ const storage = (currentBytes: number | null = null): NonNullable = {}): ProvenCapacity => ({ current: { ...none, mix: { mgas, atTop, run: run(mgas) } }, da: none, - comparisons: [], - comparisonErrors: 0, + glamsterdamRuns: [], + mainRuns: [], + glamsterdamErrors: 0, history: [ { at: T0, mgas: 40, commit: 'aaaaaaaaaaaaaaaa', previousCommit: null, compareUrl: null }, { at: T0 + 5 * DAY, mgas, commit: 'bbbbbbbbbbbbbbbb', previousCommit: 'aaaaaaaaaaaaaaaa', compareUrl: 'https://github.com/base/base/compare/aaaa...bbbb' }, @@ -126,33 +127,36 @@ describe('freshness and headline', () => { }); -describe('Glamsterdam comparisons', () => { - it('shows matched jobs with their TPS, delivered Mgas/s and run links', () => { - const main = { ...run(200), id: 'main-mix-200', outputDir: 'main-mix-200', tps: 2200.24 }; +describe('Glamsterdam jobs', () => { + it('shows Glamsterdam and Main jobs side by side with measured rates and run links', () => { + const main = { ...run(400), id: 'main-mix-400', outputDir: 'main-mix-400', tps: 2200.24 }; const glamsterdam = { ...run(200), id: 'glam-mix-200', outputDir: 'glam-mix-200', gps: 197e6, tps: 2100.14 }; - swr.result = { data: bench(400, true, { comparisons: [{ workload: 'mix', main, glamsterdam }] }) }; + swr.result = { data: bench(400, true, { mainRuns: [main], glamsterdamRuns: [glamsterdam] }) }; const section = render().glamsterdam; - expect(text(section), 'the comparison section should identify its workload').toContain('Mainnet mix'); - expect(text(section).match(/Target: 200 Mgas\/s/g), 'the two jobs should each show their own target').toHaveLength(2); - expect(text(section), 'the Glamsterdam run should show its target and measured successful TPS and gas rate').toContain('Target: 200 Mgas/s2,100.1 TPS · 197 Mgas/s actual'); - expect(text(section), 'the main run should show its target and measured successful TPS and gas rate').toContain('Target: 200 Mgas/s2,200.2 TPS · 195 Mgas/s actual'); - expect(text(section), 'the table should show the gap between successful TPS results').toContain('100.1 TPS'); - expect(section, 'the Glamsterdam run ID should link to its benchmark details').toContain('href="/scaling/runs/bench/glam-mix-200"'); - expect(section, 'the main run ID should link to its benchmark details').toContain('href="/scaling/runs/bench/main-mix-200"'); + expect(text(section)).toContain('Glamsterdam jobs'); + expect(text(section)).toContain('Main jobs'); + expect(text(section)).toContain('glam-mix-200'); + expect(text(section)).toContain('main-mix-400'); + expect(text(section)).toContain('2,100.1'); + expect(text(section)).toContain('2,200.2'); + expect(section).toContain('href="/scaling/runs/bench/glam-mix-200"'); + expect(section).toContain('href="/scaling/runs/bench/main-mix-400"'); + expect(section).not.toContain('closest TPS'); + expect(section).not.toContain('TPS gap'); }); - it('distinguishes unconfigured, loading, error and no-match states', () => { - expect(text(render({}).glamsterdam), 'an unconfigured benchmark host should be named').toContain('Benchmark source is not configured.'); - expect(text(render().glamsterdam), 'a configured host without data should show a loading state').toContain('Loading Glamsterdam comparisons…'); + it('distinguishes unconfigured, loading, error and unavailable jobs', () => { + expect(text(render({}).glamsterdam)).toContain('Benchmark source is not configured.'); + expect(text(render().glamsterdam)).toContain('Loading benchmark runs…'); swr.result = { error: new Error('offline') }; - expect(text(render().glamsterdam), 'a failed benchmark request should show an error state').toContain('Could not load Glamsterdam benchmark data.'); + expect(text(render().glamsterdam)).toContain('Could not load benchmark data.'); swr.result = { data: bench(400, true) }; - expect(text(render().glamsterdam), 'an empty result should state that no same-workload pairs were found').toContain('No Glamsterdam and main jobs for the same workload in the last 14 days.'); - swr.result = { data: bench(400, true, { comparisonErrors: 1 }) }; - expect(text(render().glamsterdam), 'a failed latest job should explain why its comparison is missing').toContain('A matching Glamsterdam run could not be loaded; its comparison is omitted.'); + expect(text(render().glamsterdam)).toContain('No eligible jobs in the last 14 days.'); + swr.result = { data: bench(400, true, { glamsterdamErrors: 1 }) }; + expect(text(render().glamsterdam)).toContain('1 latest Glamsterdam job could not be loaded.'); }); }); diff --git a/app/scaling/proven/capacity.test.ts b/app/scaling/proven/capacity.test.ts index 5854c9e..81a83cf 100644 --- a/app/scaling/proven/capacity.test.ts +++ b/app/scaling/proven/capacity.test.ts @@ -174,34 +174,29 @@ it('ignores alias copies and non-sustained scenarios', async () => { expect(unknown).toEqual([]); }); -it('pairs each Glamsterdam target with the closest-TPS main run at any target for the same workload and lists all latest main candidates', async () => { - const { comparisons, comparisonErrors, mainCandidates } = await load([ - { dir: 'nearest-main-mix-150', mgas: 150, daysAgo: 2, tps: 2000, reverted: 0 }, - { dir: 'nearest-main-mix-200', mgas: 200, daysAgo: 1, tps: 2970, reverted: 0 }, - { dir: 'nearest-main-payment-300', mgas: 300, daysAgo: 1, workload: 'payment', tps: 1500, reverted: 0 }, - { dir: 'nearest-glam-mix-200', mgas: 200, daysAgo: 0, scenario: 'glamsterdam-sustained-200mgas', ref: 'glamsterdam', tps: 2238.2, reverted: 0 }, - { dir: 'nearest-glam-payment-250', mgas: 250, daysAgo: 0, workload: 'payment', scenario: 'glamsterdam-sustained-250mgas', ref: 'glamsterdam', tps: 1520, reverted: 0 }, +it('lists the latest eligible jobs per workload and target independently', async () => { + const { glamsterdamRuns, mainRuns, glamsterdamErrors } = await load([ + { dir: 'list-main-mix-150', mgas: 150, daysAgo: 2, tps: 2000, reverted: 0 }, + { dir: 'list-main-mix-200', mgas: 200, daysAgo: 1, tps: 2970, reverted: 0 }, + { dir: 'list-main-payment-300', mgas: 300, daysAgo: 1, workload: 'payment', tps: 1500, reverted: 0 }, + { dir: 'list-glam-mix-200', mgas: 200, daysAgo: 0, scenario: 'glamsterdam-sustained-200mgas', ref: 'glamsterdam', tps: 2238.2, reverted: 0 }, + { dir: 'list-glam-payment-250', mgas: 250, daysAgo: 0, workload: 'payment', scenario: 'glamsterdam-sustained-250mgas', ref: 'glamsterdam', tps: 1520, reverted: 0 }, ]); - expect( - comparisons.map(({ workload, main, glamsterdam }) => [workload, glamsterdam.mgas, main.outputDir, main.mgas]), - ).toEqual( - [ - ['mix', 200, 'nearest-main-mix-150', 150], - ['payment', 250, 'nearest-main-payment-300', 300], - ], - 'each Glamsterdam run should show its independent target and the nearest main TPS match for that workload', - ); - expect(comparisonErrors).toBe(0); - expect(mainCandidates.map(({ workload, mgas, outputDir }) => [workload, mgas, outputDir])).toEqual([ - ['mix', 150, 'nearest-main-mix-150'], - ['mix', 200, 'nearest-main-mix-200'], - ['payment', 300, 'nearest-main-payment-300'], + expect(mainRuns.map(({ workload, mgas, outputDir }) => [workload, mgas, outputDir])).toEqual([ + ['mix', 150, 'list-main-mix-150'], + ['mix', 200, 'list-main-mix-200'], + ['payment', 300, 'list-main-payment-300'], ]); + expect(glamsterdamRuns.map(({ workload, mgas, outputDir }) => [workload, mgas, outputDir])).toEqual([ + ['mix', 200, 'list-glam-mix-200'], + ['payment', 250, 'list-glam-payment-250'], + ]); + expect(glamsterdamErrors).toBe(0); }); -it('pairs the newest Glamsterdam jobs with main at matching workloads without changing main capacity', async () => { - const { comparisons, comparisonErrors, current, history, latestAt } = await load([ +it('keeps the two job lists separate from main proven capacity and selects the latest rate run', async () => { + const { glamsterdamRuns, mainRuns, glamsterdamErrors, current, history, latestAt } = await load([ { dir: 'main-mix-200-old', mgas: 200, daysAgo: 3 }, { dir: 'main-mix-200-new', mgas: 200, daysAgo: 1, tps: 2200, reverted: 100 }, { dir: 'main-payment-200', mgas: 200, daysAgo: 2, workload: 'payment' }, @@ -212,40 +207,46 @@ it('pairs the newest Glamsterdam jobs with main at matching workloads without ch { dir: 'main-swap-200', mgas: 200, daysAgo: 1, workload: 'swap' }, ]); - expect(comparisons.map(({ workload, main, glamsterdam }) => [workload, glamsterdam.mgas, main.outputDir])).toEqual([ - ['mix', 200, 'main-mix-200-new'], - ['mix', 300, 'main-mix-200-new'], - ['payment', 200, 'main-payment-200'], + expect(mainRuns.map(({ workload, outputDir }) => [workload, outputDir])).toEqual([ + ['mix', 'main-mix-200-new'], + ['payment', 'main-payment-200'], + ['swap', 'main-swap-200'], + ]); + expect(glamsterdamRuns.map(({ workload, outputDir }) => [workload, outputDir])).toEqual([ + ['mix', 'glam-mix-200-latest'], + ['mix', 'glam-mix-300'], + ['payment', 'glam-payment-200'], ]); - expect(successfulTps(comparisons[0].main)).toBe(1980, 'the selected main run should retain its measured successful TPS'); - expect(successfulTps(comparisons[0].glamsterdam)).toBe(1890, 'the selected Glamsterdam run should retain its distinct measured successful TPS'); - expect(comparisons[0].glamsterdam.passed).toBe(false, 'the newest run should remain visible even when it does not pass'); - expect(current.mix).toMatchObject({ mgas: 200, run: { outputDir: 'main-mix-200-new' } }, 'Glamsterdam runs must not affect main proven capacity'); - expect(history.map(({ mgas }) => mgas)).toEqual([200], 'Glamsterdam runs must not enter main capacity history'); - expect(latestAt).toBe(NOW - DAY, 'the main freshness timestamp must exclude the newer Glamsterdam run'); - expect(comparisonErrors).toBe(0, 'successful comparisons should have no load errors'); + expect(successfulTps(mainRuns[0])).toBe(1980); + expect(successfulTps(glamsterdamRuns[0])).toBe(1890); + expect(glamsterdamRuns[0].passed).toBe(false); + expect(current.mix).toMatchObject({ mgas: 200, run: { outputDir: 'main-mix-200-new' } }); + expect(history.map(({ mgas }) => mgas)).toEqual([200]); + expect(latestAt).toBe(NOW - DAY); + expect(glamsterdamErrors).toBe(0); }); -it('omits an older comparison when the newest Glamsterdam run cannot be loaded', async () => { - const { comparisons, comparisonErrors } = await load([ +it('omits a stale Glamsterdam job when the latest run cannot be loaded', async () => { + const { glamsterdamRuns, mainRuns, glamsterdamErrors } = await load([ { dir: 'failed-latest-main-200', mgas: 200, daysAgo: 1 }, { dir: 'failed-latest-glam-old-200', mgas: 200, daysAgo: 3, scenario: 'glamsterdam-sustained-200mgas' }, { dir: 'failed-latest-glam-new-200', mgas: 200, daysAgo: 0, scenario: 'glamsterdam-sustained-200mgas', fail: 'load-test-result.json' }, ]); - expect(comparisons).toEqual([], 'a failed latest job must not leave an older pair that looks current'); - expect(comparisonErrors).toBe(1, 'the omitted workload-and-rate comparison should be reported once'); + expect(glamsterdamRuns).toEqual([]); + expect(mainRuns.map(({ outputDir }) => outputDir)).toEqual(['failed-latest-main-200']); + expect(glamsterdamErrors).toBe(1); }); -it('keeps a newer loaded pair when only an older Glamsterdam run fails', async () => { - const { comparisons, comparisonErrors } = await load([ +it('keeps a loaded latest Glamsterdam job when only an older run fails', async () => { + const { glamsterdamRuns, glamsterdamErrors } = await load([ { dir: 'older-failure-main-200', mgas: 200, daysAgo: 1 }, { dir: 'older-failure-glam-old-200', mgas: 200, daysAgo: 3, scenario: 'glamsterdam-sustained-200mgas', fail: 'load-test-result.json' }, { dir: 'older-failure-glam-new-200', mgas: 200, daysAgo: 0, scenario: 'glamsterdam-sustained-200mgas' }, ]); - expect(comparisons.map(({ glamsterdam }) => glamsterdam.outputDir)).toEqual(['older-failure-glam-new-200'], 'the latest loaded Glamsterdam run should remain paired'); - expect(comparisonErrors).toBe(0, 'an older failed job should not mark the latest loaded comparison unavailable'); + expect(glamsterdamRuns.map(({ outputDir }) => outputDir)).toEqual(['older-failure-glam-new-200']); + expect(glamsterdamErrors).toBe(0); }); it('computes successful TPS by excluding reverts from confirmed transactions, not all submissions', async () => { diff --git a/app/scaling/proven/capacity.ts b/app/scaling/proven/capacity.ts index 673e4cf..b41fcc3 100644 --- a/app/scaling/proven/capacity.ts +++ b/app/scaling/proven/capacity.ts @@ -95,17 +95,14 @@ export type Step = { export type ProvenCapacity = { current: Record; da: Record; - comparisons: GlamsterdamPair[]; - /** Latest eligible main run per workload and target in the comparison window. */ - mainCandidates?: Run[]; - comparisonErrors: number; + glamsterdamRuns: Run[]; + mainRuns: Run[]; + glamsterdamErrors: number; history: Step[]; latestAt: number | null; unknown: string[]; }; -/** A Glamsterdam run paired with the closest-TPS main run for the same workload. */ -export type GlamsterdamPair = { workload: Workload; main: Run; glamsterdam: Run }; const WINDOW_MS = 14 * 24 * 60 * 60 * 1000; const PASS_RATIO = 0.95; @@ -168,13 +165,13 @@ export async function loadProvenCapacity( const run = latestDa(of(full, w)); return run?.da ? run : null; }; - const comparison = glamsterdamComparisons(full, glamRuns, now, failedGlamRuns); + const jobs = benchmarkRuns(full, glamRuns, now, failedGlamRuns); return { current: Object.fromEntries(WORKLOADS.map((w) => [w, provenAt(of(full, w), now)])) as ProvenCapacity['current'], da: Object.fromEntries(WORKLOADS.map((w) => [w, daRun(w)])) as ProvenCapacity['da'], - comparisons: comparison.pairs, - comparisonErrors: comparison.errors, - mainCandidates: comparison.mainCandidates, + glamsterdamRuns: jobs.glamsterdamRuns, + mainRuns: jobs.mainRuns, + glamsterdamErrors: jobs.errors, history: history(of(full, 'mix'), now), latestAt: runs.length ? Math.max(...runs.map((r) => r.createdAt)) : null, unknown, @@ -270,49 +267,30 @@ function latestByWorkloadAndRate(runs: Run[], now: number): Map { return latest; } -/** Pairs each latest Glamsterdam run with the closest-TPS main run for the same workload. */ -function glamsterdamComparisons( +/** Lists the latest in-window run for each workload and target, without pairing jobs. */ +function benchmarkRuns( mainRuns: Run[], glamRuns: Run[], now: number, failedGlamRuns: Map, -): { pairs: GlamsterdamPair[]; errors: number; mainCandidates: Run[] } { +): { glamsterdamRuns: Run[]; mainRuns: Run[]; errors: number } { const latestMain = latestByWorkloadAndRate(mainRuns, now); - const mainCandidates = [...latestMain.values()].sort( - (a, b) => WORKLOADS.indexOf(a.workload) - WORKLOADS.indexOf(b.workload) || a.mgas - b.mgas, - ); - const mainByWorkload = new Map(); - for (const run of latestMain.values()) { - mainByWorkload.set(run.workload, [...(mainByWorkload.get(run.workload) ?? []), run]); - } const latestGlam = latestByWorkloadAndRate(glamRuns, now); const blocked = new Set( [...failedGlamRuns].flatMap(([key, failedAt]) => { const loaded = latestGlam.get(key); - const workload = key.split(':')[0] as Workload; - const hasMainRun = (mainByWorkload.get(workload)?.length ?? 0) > 0; - return hasMainRun && failedAt > now - WINDOW_MS && failedAt <= now && (!loaded || failedAt >= loaded.createdAt) ? [key] : []; + return failedAt > now - WINDOW_MS && failedAt <= now && (!loaded || failedAt >= loaded.createdAt) ? [key] : []; }), ); - const pairs = [...latestGlam.entries()] - .flatMap(([key, glamsterdam]) => { - const baselines = mainByWorkload.get(glamsterdam.workload) ?? []; - if (blocked.has(key) || baselines.length === 0) return []; - const main = baselines.reduce((closest, candidate) => { - const candidateGap = Math.abs(successfulTps(candidate) - successfulTps(glamsterdam)); - const closestGap = Math.abs(successfulTps(closest) - successfulTps(glamsterdam)); - return candidateGap < closestGap || (candidateGap === closestGap && candidate.createdAt > closest.createdAt) - ? candidate - : closest; - }); - return [{ workload: glamsterdam.workload, main, glamsterdam }]; - }) - .sort( - (a, b) => - WORKLOADS.indexOf(a.workload) - WORKLOADS.indexOf(b.workload) || - a.glamsterdam.mgas - b.glamsterdam.mgas, - ); - return { pairs, errors: blocked.size, mainCandidates }; + const sortRuns = (a: Run, b: Run) => WORKLOADS.indexOf(a.workload) - WORKLOADS.indexOf(b.workload) || a.mgas - b.mgas; + return { + mainRuns: [...latestMain.values()].sort(sortRuns), + glamsterdamRuns: [...latestGlam.entries()] + .filter(([key]) => !blocked.has(key)) + .map(([, run]) => run) + .sort(sortRuns), + errors: blocked.size, + }; } // Rust Durations serialize as { secs, nanos }. From 97d7dbd3c10814eeb47a567d1d5154566153fe7d Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 28 Oct 2026 19:42:18 -0400 Subject: [PATCH 7/8] fix(scaling): fit side-by-side benchmark tables Co-authored-by: Toshi --- app/scaling/components/GlamsterdamComparison.test.tsx | 1 + app/scaling/components/GlamsterdamComparison.tsx | 10 ++++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/scaling/components/GlamsterdamComparison.test.tsx b/app/scaling/components/GlamsterdamComparison.test.tsx index 25a7ca0..2e78275 100644 --- a/app/scaling/components/GlamsterdamComparison.test.tsx +++ b/app/scaling/components/GlamsterdamComparison.test.tsx @@ -44,6 +44,7 @@ it('shows Glamsterdam and Main jobs side by side without pairing or TPS-gap calc expect(html).toContain('2,238.2'); expect(html).toContain('2,970.6'); expect(html).toContain('5,942.5'); + expect(html).toContain('TPS / actual Mgas/s'); expect(html).not.toContain('closest TPS'); expect(html).not.toContain('TPS gap'); }); diff --git a/app/scaling/components/GlamsterdamComparison.tsx b/app/scaling/components/GlamsterdamComparison.tsx index d9d9cf9..248d3ed 100644 --- a/app/scaling/components/GlamsterdamComparison.tsx +++ b/app/scaling/components/GlamsterdamComparison.tsx @@ -25,14 +25,13 @@ function RunTable({ title, runs }: { title: string; runs: Run[] }) { No eligible jobs in the last 14 days. ) : (
- +
- - + @@ -47,8 +46,7 @@ function RunTable({ title, runs }: { title: string; runs: Run[] }) { {run.id} - - + ); })} @@ -95,7 +93,7 @@ export function GlamsterdamComparisonSection({ {glamsterdamErrors} latest Glamsterdam job{glamsterdamErrors === 1 ? '' : 's'} could not be loaded. ) : null} -
+
From b6081083f700d920ba96088f6cd884a9dfe91b52 Mon Sep 17 00:00:00 2001 From: William Law Date: Thu, 29 Oct 2026 13:27:15 -0400 Subject: [PATCH 8/8] feat(scaling): implement latest-results design option six Co-authored-by: Toshi --- .../components/GlamsterdamComparison.test.tsx | 24 ++++++++++- .../components/GlamsterdamComparison.tsx | 43 ++++++++++++++++--- app/scaling/components/scorecard.test.tsx | 4 +- 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/app/scaling/components/GlamsterdamComparison.test.tsx b/app/scaling/components/GlamsterdamComparison.test.tsx index 2e78275..cf55de8 100644 --- a/app/scaling/components/GlamsterdamComparison.test.tsx +++ b/app/scaling/components/GlamsterdamComparison.test.tsx @@ -36,7 +36,10 @@ it('shows Glamsterdam and Main jobs side by side without pairing or TPS-gap calc ); expect(html).toContain('Glamsterdam jobs'); - expect(html).toContain('Main jobs'); + expect(html).toContain('All Main targets'); + expect(html).toContain('All Glamsterdam targets'); + expect(html).toContain('Latest Glamsterdam mix run'); + expect(html).toContain('Latest Main mix run'); expect(html).toContain('glam-mix-200'); expect(html).toContain('glam-pay-300'); expect(html).toContain('main-mix-200'); @@ -44,7 +47,24 @@ it('shows Glamsterdam and Main jobs side by side without pairing or TPS-gap calc expect(html).toContain('2,238.2'); expect(html).toContain('2,970.6'); expect(html).toContain('5,942.5'); - expect(html).toContain('TPS / actual Mgas/s'); + expect(html).toContain('Actual Mgas/s'); + expect(html).toContain('Successful TPS'); expect(html).not.toContain('closest TPS'); expect(html).not.toContain('TPS gap'); }); + +/** Verifies highlights select by recency, exclude non-mix runs, and retain the inventory. */ +it('highlights the latest mix run rather than the largest target or newest other workload', () => { + const older = { ...run('older-high-target', 400, 5000, 406e6), createdAt: 1 }; + const newest = { ...run('newest-mix', 100, 1200, 102e6), createdAt: 3, reverted: 0.25 }; + const payment = { ...run('newest-payment', 300, 2000, 302e6, 'payment'), createdAt: 4 }; + const html = renderToStaticMarkup(); + const cards = html.slice(0, html.indexOf('All Glamsterdam targets')); + expect(cards, 'latest mix card uses the newest mix job link').toContain('href="/scaling/runs/bench/newest-mix"'); + expect(cards, 'largest target is not assumed newest').not.toContain('older-high-target'); + expect(cards, 'newer payment does not replace latest mix').not.toContain('newest-payment'); + expect(cards, 'successful TPS excludes 25% reverted').toContain('900 successful TPS'); + expect(cards, 'empty Main has an explicit empty state').toContain('No eligible mix jobs in the last 14 days.'); + expect(html, 'older mix remains in the full inventory').toContain('older-high-target'); + expect(html, 'payment remains in the full inventory').toContain('newest-payment'); +}); diff --git a/app/scaling/components/GlamsterdamComparison.tsx b/app/scaling/components/GlamsterdamComparison.tsx index 248d3ed..538c0f1 100644 --- a/app/scaling/components/GlamsterdamComparison.tsx +++ b/app/scaling/components/GlamsterdamComparison.tsx @@ -15,6 +15,31 @@ const TD = 'border-t border-bds-gray-10 px-3 py-2 align-top dark:border-white/10 const tps = (run: Run) => successfulTps(run).toLocaleString('en-US', { maximumFractionDigits: 1 }); const rate = (run: Run) => (run.gps / 1e6).toLocaleString('en-US', { maximumFractionDigits: 1 }); +/** Highlights the newest eligible mix run without matching it to the other branch. */ +function LatestRun({ title, runs, accent = false }: { title: string; runs: Run[]; accent?: boolean }) { + const latest = runs.filter((run) => run.workload === 'mix').reduce( + (newest, run) => !newest || run.createdAt > newest.createdAt ? run : newest, undefined, + ); + return ( +
+

{title}

+ {latest ? ( + <> +
+ {rate(latest)} + Mgas/s actual +
+

+ Target {latest.mgas.toLocaleString('en-US')} Mgas/s · {tps(latest)} successful TPS ·{' '} + View job ↗ +

+ + ) : No eligible mix jobs in the last 14 days.} +
+ ); +} + +/** Lists independent measured runs across all workloads and targets. */ function RunTable({ title, runs }: { title: string; runs: Run[] }) { return (
@@ -31,7 +56,8 @@ function RunTable({ title, runs }: { title: string; runs: Run[] }) {
- + + @@ -46,7 +72,8 @@ function RunTable({ title, runs }: { title: string; runs: Run[] }) { {run.id} - + + ); })} @@ -58,7 +85,7 @@ function RunTable({ title, runs }: { title: string; runs: Run[] }) { ); } -/** Shows the latest Glamsterdam and Main jobs in separate side-by-side lists. */ +/** Shows latest mix results above separate Glamsterdam and Main run inventories. */ export function GlamsterdamComparisonSection({ configured, glamsterdamRuns, @@ -93,9 +120,13 @@ export function GlamsterdamComparisonSection({ {glamsterdamErrors} latest Glamsterdam job{glamsterdamErrors === 1 ? '' : 's'} could not be loaded. ) : null} -
- - +
+ + +
+
+ +
)} diff --git a/app/scaling/components/scorecard.test.tsx b/app/scaling/components/scorecard.test.tsx index 7f9ede0..260247d 100644 --- a/app/scaling/components/scorecard.test.tsx +++ b/app/scaling/components/scorecard.test.tsx @@ -134,8 +134,8 @@ describe('Glamsterdam jobs', () => { swr.result = { data: bench(400, true, { mainRuns: [main], glamsterdamRuns: [glamsterdam] }) }; const section = render().glamsterdam; - expect(text(section)).toContain('Glamsterdam jobs'); - expect(text(section)).toContain('Main jobs'); + expect(text(section)).toContain('All Glamsterdam targets'); + expect(text(section)).toContain('All Main targets'); expect(text(section)).toContain('glam-mix-200'); expect(text(section)).toContain('main-mix-400'); expect(text(section)).toContain('2,100.1');
Workload Target JobSuccessful TPSActual Mgas/sTPS / actual Mgas/s
{tps(run)}{rate(run)}{tps(run)} / {rate(run)}
Workload Target JobTPS / actual Mgas/sActual Mgas/sSuccessful TPS
{tps(run)} / {rate(run)}{rate(run)}{tps(run)}