From 3b4888309e509dc274b0e8904f938c22b6744539 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 10 Jun 2026 15:36:22 -0400 Subject: [PATCH 1/3] chore: update load test metrics summary --- report/src/components/ConfigCard.tsx | 6 + report/src/pages/LoadTestDetail.tsx | 375 ++++++++++++++++++++++----- report/src/types.ts | 48 ++++ 3 files changed, 371 insertions(+), 58 deletions(-) diff --git a/report/src/components/ConfigCard.tsx b/report/src/components/ConfigCard.tsx index 82b6c5f..6c862b5 100644 --- a/report/src/components/ConfigCard.tsx +++ b/report/src/components/ConfigCard.tsx @@ -65,6 +65,12 @@ const buildRows = (config: LoadTestConfig): Row[][] => { value: formatEthFromWeiString(config.swap_token_amount), }); } + if (config.b20_mint_amount && config.b20_mint_amount !== "0") { + funding.push({ + label: "B-20 mint amount", + value: formatEthFromWeiString(config.b20_mint_amount), + }); + } const repro: Row[] = [{ label: "Seed", value: config.seed.toLocaleString() }]; if (config.chain_id !== null) { diff --git a/report/src/pages/LoadTestDetail.tsx b/report/src/pages/LoadTestDetail.tsx index d902355..84aa3a7 100644 --- a/report/src/pages/LoadTestDetail.tsx +++ b/report/src/pages/LoadTestDetail.tsx @@ -19,11 +19,24 @@ import { formatTps, } from "../utils/formatters"; import { + BlockRange, FlashblocksLatencyStats, LatencyStats, LoadTestResult, + ObservedWindowMetrics, + TailMetrics, } from "../types"; +const formatBlockRange = (range: BlockRange): string => { + if ( + typeof range.first_block === "number" && + typeof range.last_block === "number" + ) { + return `${range.first_block.toLocaleString()} → ${range.last_block.toLocaleString()}`; + } + return "No confirmed transactions"; +}; + const buildLatencyRows = ( stats: LatencyStats | FlashblocksLatencyStats, ): PercentileBarRow[] => { @@ -75,7 +88,7 @@ const buildLatencyRows = ( return rows; }; -const SwapsPerSecondHero = ({ tps }: { tps: number }) => ( +const SwapsPerSecondHero = ({ tps, label }: { tps: number; label: string }) => (
{tps.toLocaleString(undefined, { @@ -83,64 +96,35 @@ const SwapsPerSecondHero = ({ tps }: { tps: number }) => ( maximumFractionDigits: 1, })}
-
Swaps/s
+
{label}
); -const SummarySection = ({ result }: { result: LoadTestResult }) => { - const submitted = result.throughput.total_submitted; - const confirmed = result.throughput.total_confirmed; - const failed = result.throughput.total_failed; - const reverted = result.throughput.total_reverted; - const blockRange = result.block_range; - const hasConfirmedBlockRange = - typeof blockRange?.first_block === "number" && - typeof blockRange.last_block === "number"; +const ObservedWindowSummary = ({ + window, +}: { + window: ObservedWindowMetrics; +}) => { + const blockRange = window.block_range; return ( - + - - - - {reverted > 0 && ( - - )} - - - + + {blockRange && ( )} @@ -149,6 +133,241 @@ const SummarySection = ({ result }: { result: LoadTestResult }) => { ); }; +const TailSection = ({ + tail, + totalConfirmed, +}: { + tail: TailMetrics; + totalConfirmed: number; +}) => { + const blockRange = tail.block_range; + const hasReceiptDelay = + tail.block_receipt_delay && + durationToNanos(tail.block_receipt_delay.max) > 0; + + const timePastRows = useMemo( + () => buildLatencyRows(tail.time_past_observed_window), + [tail.time_past_observed_window], + ); + const blockLatencyRows = useMemo( + () => buildLatencyRows(tail.block_latency), + [tail.block_latency], + ); + const receiptDelayRows = useMemo( + () => (hasReceiptDelay ? buildLatencyRows(tail.block_receipt_delay) : []), + [tail.block_receipt_delay, hasReceiptDelay], + ); + const flashblocksRows = useMemo( + () => buildLatencyRows(tail.flashblocks_latency), + [tail.flashblocks_latency], + ); + + if (tail.count === 0) { + return ( + +
+ No transactions landed past the observed window + {typeof tail.observed_window_end_block === "number" && ( + <> + {" "} + (boundary: block {tail.observed_window_end_block.toLocaleString()} + ) + + )} + . +
+
+ ); + } + + return ( + +
+ + + + {typeof tail.observed_window_end_block === "number" && ( + + )} + {blockRange && ( + + )} + + +
+

+ Time past observed window +

+ +
+ +
+

+ Block latency (tail) +

+ +
+ + {hasReceiptDelay && ( +
+

+ Block receipt delay (tail) +

+ +
+ )} + +
+

+ Flashblocks latency (tail) ·{" "} + {tail.flashblocks_latency.count.toLocaleString()} samples +

+ +
+
+
+ ); +}; + +const FullRunBaselineSection = ({ result }: { result: LoadTestResult }) => { + const submitted = result.throughput.total_submitted; + const confirmed = result.throughput.total_confirmed; + const failed = result.throughput.total_failed; + const reverted = result.throughput.total_reverted; + const blockRange = result.block_range; + + const blockLatencyRows = useMemo( + () => buildLatencyRows(result.block_latency), + [result.block_latency], + ); + const flashblocksRows = useMemo( + () => buildLatencyRows(result.flashblocks_latency), + [result.flashblocks_latency], + ); + const receiptDelayRows = useMemo( + () => + result.block_receipt_delay + ? buildLatencyRows(result.block_receipt_delay) + : [], + [result.block_receipt_delay], + ); + + return ( +
+ + Full-run baseline (observed window + tail combined) + +
+

+ Full-run averages dilute the clean reporting window with tail + stragglers. Use the observed-window numbers above for headline + comparisons; this section is included for completeness. +

+ + + + + + + {reverted > 0 && ( + + )} + + + + + {blockRange && ( + + )} + + +
+

+ Block latency (full run) +

+ +
+ + {result.block_receipt_delay && ( +
+

+ Block receipt delay (full run) +

+ +
+ )} + +
+

+ Flashblocks latency (full run) ·{" "} + {result.flashblocks_latency.count.toLocaleString()} samples +

+ +
+
+
+ ); +}; + interface LoadTestReportContentProps { result: LoadTestResult; title: string; @@ -165,14 +384,34 @@ export const LoadTestReportContent = ({ subtitle, backLink, }: LoadTestReportContentProps) => { - const blockLatencyRows = useMemo( - () => buildLatencyRows(result.block_latency), - [result], + const observedWindow = result.observed_window; + const tail = result.tail ?? undefined; + + // Headline numbers come from observed_window when available, otherwise fall + // back to the legacy full-run fields so older S3 runs still render. + const headlineTps = observedWindow?.tps ?? result.throughput.tps; + const headlineBlockLatency = + observedWindow?.block_latency ?? result.block_latency; + const headlineFlashblocksLatency = + observedWindow?.flashblocks_latency ?? result.flashblocks_latency; + const headlineReceiptDelay = + observedWindow?.block_receipt_delay ?? result.block_receipt_delay; + + const headlineBlockLatencyRows = useMemo( + () => buildLatencyRows(headlineBlockLatency), + [headlineBlockLatency], ); - const flashblocksLatencyRows = useMemo( - () => buildLatencyRows(result.flashblocks_latency), - [result], + const headlineFlashblocksRows = useMemo( + () => buildLatencyRows(headlineFlashblocksLatency), + [headlineFlashblocksLatency], ); + const headlineReceiptDelayRows = useMemo( + () => (headlineReceiptDelay ? buildLatencyRows(headlineReceiptDelay) : []), + [headlineReceiptDelay], + ); + + const headlineLabel = observedWindow ? "Observed-window TPS" : "Swaps/s"; + const latencyScopeLabel = observedWindow ? "observed window" : "full run"; return ( <> @@ -193,7 +432,7 @@ export const LoadTestReportContent = ({ - + {result.throughput_timeseries && result.throughput_timeseries.length > 1 && ( @@ -208,24 +447,42 @@ export const LoadTestReportContent = ({ {result.config && } - + {observedWindow && } - + + {headlineReceiptDelay && ( + + + + )} + + {tail && ( + + )} + {(() => { const reverted = result.throughput.total_reverted; @@ -254,6 +511,8 @@ export const LoadTestReportContent = ({ ); })()} + + {observedWindow && } ); }; diff --git a/report/src/types.ts b/report/src/types.ts index 8f63426..3bd9138 100644 --- a/report/src/types.ts +++ b/report/src/types.ts @@ -211,6 +211,10 @@ export interface LoadTestConfig { transactions: Array<{ type: string; weight: number }>; looper_contract: string | null; swap_token_amount: string; + // Optional for back-compat: older S3 runs predate this field. + b20_mint_amount?: string; + // Producer omits unless real-token setup is enabled; loosely typed. + real_token_setup?: Record | null; } /** @@ -224,8 +228,46 @@ export interface ThroughputSample { gps: number; } +/** + * Clean reporting window for a configured-duration run. Defined as the first + * `expected_block_count` blocks starting at `block_range.first_block`. TPS/GPS + * denominator is `duration` (= expected_block_count * BLOCK_INTERVAL), not the + * full wall-clock run, so headline numbers are not diluted by tail stragglers. + * Producer added in base/base#3358. + */ +export interface ObservedWindowMetrics { + expected_block_count: number; + block_range: BlockRange; + duration: RustDuration; + confirmed_count: number; + tps: number; + gps: number; + block_latency: LatencyStats; + block_receipt_delay: LatencyStats; + flashblocks_latency: FlashblocksLatencyStats; +} + +/** + * Inclusion-delay tail: txs landing in blocks past the observed window + * (`block_number > observed_window_end_block`). `null` on continuous runs + * (no configured duration). Producer added in base/base#3358. + */ +export interface TailMetrics { + observed_window_end_block: number | null; + count: number; + confirmed_pct: number; + block_range: BlockRange; + time_past_observed_window: LatencyStats; + block_latency: LatencyStats; + block_receipt_delay: LatencyStats; + flashblocks_latency: FlashblocksLatencyStats; +} + export interface LoadTestResult { block_latency: LatencyStats; + // Submit-to-receipt-observation delay, full-run baseline. Optional for + // back-compat: producer added in base/base#3358. + block_receipt_delay?: LatencyStats; flashblocks_latency: FlashblocksLatencyStats; throughput: ThroughputStats; throughput_percentiles: ThroughputPercentiles; @@ -241,6 +283,12 @@ export interface LoadTestResult { // Optional for back-compat: older runs predate this field. The summary // section gates the block range stats on its presence. block_range?: BlockRange; + // Observed reporting window (clean TPS / latency). Optional for back-compat: + // older S3 runs predate this; the page falls back to full-run fields. + observed_window?: ObservedWindowMetrics; + // Inclusion-delay tail. `null` on continuous runs; `undefined` on older + // runs that predate the field. + tail?: TailMetrics | null; } /** From 8a0d86589da4d57a526183c80106f0fb79662831 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 10 Jun 2026 15:43:44 -0400 Subject: [PATCH 2/3] no b20 --- report/src/components/ConfigCard.tsx | 6 ------ report/src/types.ts | 2 -- 2 files changed, 8 deletions(-) diff --git a/report/src/components/ConfigCard.tsx b/report/src/components/ConfigCard.tsx index 6c862b5..82b6c5f 100644 --- a/report/src/components/ConfigCard.tsx +++ b/report/src/components/ConfigCard.tsx @@ -65,12 +65,6 @@ const buildRows = (config: LoadTestConfig): Row[][] => { value: formatEthFromWeiString(config.swap_token_amount), }); } - if (config.b20_mint_amount && config.b20_mint_amount !== "0") { - funding.push({ - label: "B-20 mint amount", - value: formatEthFromWeiString(config.b20_mint_amount), - }); - } const repro: Row[] = [{ label: "Seed", value: config.seed.toLocaleString() }]; if (config.chain_id !== null) { diff --git a/report/src/types.ts b/report/src/types.ts index 3bd9138..62ad784 100644 --- a/report/src/types.ts +++ b/report/src/types.ts @@ -211,8 +211,6 @@ export interface LoadTestConfig { transactions: Array<{ type: string; weight: number }>; looper_contract: string | null; swap_token_amount: string; - // Optional for back-compat: older S3 runs predate this field. - b20_mint_amount?: string; // Producer omits unless real-token setup is enabled; loosely typed. real_token_setup?: Record | null; } From 1a92e18d388f5c50d95862ad7eac33f2eaeb64b6 Mon Sep 17 00:00:00 2001 From: William Law Date: Wed, 10 Jun 2026 17:02:03 -0400 Subject: [PATCH 3/3] tooltip --- report/src/components/StatCard.tsx | 24 +++++++++-- report/src/components/Tooltip.tsx | 2 +- report/src/pages/LoadTestDetail.tsx | 63 ++++++++++++++++++++++++++--- 3 files changed, 79 insertions(+), 10 deletions(-) diff --git a/report/src/components/StatCard.tsx b/report/src/components/StatCard.tsx index d268706..df64ecc 100644 --- a/report/src/components/StatCard.tsx +++ b/report/src/components/StatCard.tsx @@ -1,13 +1,20 @@ import { ReactNode } from "react"; import clsx from "clsx"; +import Tooltip from "./Tooltip"; interface StatCardProps { title: string; children: ReactNode; className?: string; + titleTooltip?: ReactNode; } -const StatCard = ({ title, children, className }: StatCardProps) => { +const StatCard = ({ + title, + children, + className, + titleTooltip, +}: StatCardProps) => { return (
{ className, )} > -

- {title} +

+ {title} + {titleTooltip && ( + + + + )}

{children}
diff --git a/report/src/components/Tooltip.tsx b/report/src/components/Tooltip.tsx index 453cb49..f57b258 100644 --- a/report/src/components/Tooltip.tsx +++ b/report/src/components/Tooltip.tsx @@ -3,7 +3,7 @@ import * as TooltipPrimitive from "@radix-ui/react-tooltip"; interface TooltipProps { children: React.ReactNode; - content: string; + content: React.ReactNode; className?: string; delayDuration?: number; side?: "top" | "right" | "bottom" | "left"; diff --git a/report/src/pages/LoadTestDetail.tsx b/report/src/pages/LoadTestDetail.tsx index 84aa3a7..23009a5 100644 --- a/report/src/pages/LoadTestDetail.tsx +++ b/report/src/pages/LoadTestDetail.tsx @@ -100,15 +100,52 @@ const SwapsPerSecondHero = ({ tps, label }: { tps: number; label: string }) => ( ); +// The full observed-window block range, matching the CLI's display: +// `first_block ..= first_block + expected_block_count - 1`. The +// `window.block_range` field is the range of blocks that actually contained +// confirmed test txs and is typically smaller — surfaced as a hint. +const formatObservedWindowRange = ( + window: ObservedWindowMetrics, +): { value: string; hint: string } | null => { + const first = window.block_range.first_block; + if (typeof first !== "number" || window.expected_block_count === 0) { + return null; + } + const end = first + window.expected_block_count - 1; + const confirmedCount = window.block_range.block_count; + return { + value: `${first.toLocaleString()} → ${end.toLocaleString()}`, + hint: `${window.expected_block_count.toLocaleString()} blocks · txs landed in ${confirmedCount.toLocaleString()}`, + }; +}; + +const OBSERVED_WINDOW_TOOLTIP = ( +

+ The clean, first portion of the run, sized to the configured duration. This + mirrors what you witness watching the chain live: sustained TPS over a + period of time. Use this against OKRs like “hit 3k swaps/s on the + chain.” +

+); + +const TAIL_INCLUSION_TOOLTIP = ( +

+ Txs that landed in blocks past the observed window. Including them in the + headline would lower TPS and raise block / FB latency, but the data is + critical: it surfaces where inclusion-side optimization is still needed. +

+); + const ObservedWindowSummary = ({ window, }: { window: ObservedWindowMetrics; }) => { const blockRange = window.block_range; + const windowRange = formatObservedWindowRange(window); return ( - + - {blockRange && ( + {windowRange ? ( + ) : ( + blockRange && ( + + ) )} @@ -164,7 +209,10 @@ const TailSection = ({ if (tail.count === 0) { return ( - +
No transactions landed past the observed window {typeof tail.observed_window_end_block === "number" && ( @@ -181,7 +229,10 @@ const TailSection = ({ } return ( - +