Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/_build-binaries.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ on:
description: "Base Reth Node version to build"
required: false
type: string
default: "feature/load-test-benchmark"
default: "1e4a8a7"

# Set minimal permissions for all jobs by default
permissions:
Expand Down
2 changes: 1 addition & 1 deletion clients/versions.env
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ GETH_VERSION="v1.101604.0"

# Base Reth Node Configuration
BASE_RETH_NODE_REPO="https://github.com/base/base"
BASE_RETH_NODE_VERSION="feature/load-test-benchmark"
BASE_RETH_NODE_VERSION="1e4a8a7"

# Build Configuration
# BUILD_DIR="./build"
Expand Down
3 changes: 2 additions & 1 deletion report/.eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.eslintrc.cjs
vite.config.ts
tailwind.config.js
tailwind.config.js
dev-mock/
15 changes: 14 additions & 1 deletion report/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import { Route, Routes } from "react-router-dom";
import { Navigate, Route, Routes } from "react-router-dom";
import RunIndex from "./pages/RunIndex";
import RunComparison from "./pages/RunComparison";
import RedirectToLatestRun from "./pages/RedirectToLatestRun";
import LoadTestLanding from "./pages/LoadTestLanding";
import LoadTestAllRuns from "./pages/LoadTestAllRuns";
import LoadTestDetail from "./pages/LoadTestDetail";
import ErrorBoundary from "./components/ErrorBoundary";

function App() {
return (
<ErrorBoundary>
<Routes>
<Route path="/" element={<RedirectToLatestRun />} />
<Route
path="/load-tests"
element={<Navigate to="/load-tests/sepolia" replace />}
/>
<Route path="/load-tests/:network" element={<LoadTestLanding />} />
<Route path="/load-tests/:network/all" element={<LoadTestAllRuns />} />
<Route
path="/load-tests/:network/:timestamp"
element={<LoadTestDetail />}
/>
<Route path="/:benchmarkRunId" element={<RunIndex />} />
<Route
path="/run-comparison/:benchmarkRunId"
Expand Down
120 changes: 120 additions & 0 deletions report/src/components/ConfigCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { LoadTestConfig } from "../types";
import { formatEthFromWeiString } from "../utils/formatters";
import StatCard from "./StatCard";

interface ConfigCardProps {
config: LoadTestConfig;
}

interface Row {
label: string;
value: string;
}

const formatTransactions = (txs: LoadTestConfig["transactions"]): string => {
if (!txs || txs.length === 0) return "—";
const total = txs.reduce((acc, t) => acc + t.weight, 0);
if (total === 0) return txs.map((t) => t.type).join(" · ");
return txs
.map((t) => `${t.type} (${Math.round((t.weight / total) * 100)}%)`)
.join(" · ");
};

const formatTargetGps = (gps: number): string => {
if (gps >= 1e9) return `${(gps / 1e9).toFixed(1)}B gas/s`;
if (gps >= 1e6) return `${(gps / 1e6).toFixed(0)}M gas/s`;
if (gps >= 1e3) return `${(gps / 1e3).toFixed(0)}k gas/s`;
return `${gps.toLocaleString()} gas/s`;
};

const buildRows = (config: LoadTestConfig): Row[][] => {
const loadShape: Row[] = [
{ label: "Senders", value: config.sender_count.toLocaleString() },
{
label: "In-flight / sender",
value: config.in_flight_per_sender.toLocaleString(),
},
{ label: "Batch size", value: config.batch_size.toLocaleString() },
{ label: "Batch timeout", value: config.batch_timeout },
];
if (config.sender_offset !== 0) {
loadShape.push({
label: "Sender offset",
value: config.sender_offset.toLocaleString(),
});
}

const target: Row[] = [
{ label: "Duration", value: config.duration },
{ label: "Target gas/s", value: formatTargetGps(config.target_gps) },
];

const funding: Row[] = [
{
label: "Funding / sender",
value: formatEthFromWeiString(config.funding_amount),
},
];
const hasSwapToken =
config.transactions.some((t) => t.type === "swap") &&
config.swap_token_amount &&
config.swap_token_amount !== "0";
if (hasSwapToken) {
funding.push({
label: "Swap token amount",
value: formatEthFromWeiString(config.swap_token_amount),
});
}

const repro: Row[] = [{ label: "Seed", value: config.seed.toLocaleString() }];
if (config.chain_id !== null) {
repro.push({ label: "Chain ID", value: config.chain_id.toLocaleString() });
}
if (config.looper_contract) {
repro.push({ label: "Looper contract", value: config.looper_contract });
}

return [loadShape, target, funding, repro];
};

const RowGroup = ({ rows }: { rows: Row[] }) => (
<div className="grid grid-cols-2 gap-x-6 gap-y-3">
{rows.map((r) => (
<div key={r.label} className="flex flex-col">
<span className="text-xs uppercase tracking-wide text-slate-500">
{r.label}
</span>
<span className="text-sm text-slate-900 font-mono mt-0.5 break-all">
{r.value}
</span>
</div>
))}
</div>
);

const ConfigCard = ({ config }: ConfigCardProps) => {
const groups = buildRows(config);
const txLine = formatTransactions(config.transactions);

return (
<StatCard title="Run config">
<div className="flex flex-col gap-y-5">
{groups.map((rows, i) => (
<div key={i}>
{i > 0 && <hr className="border-slate-100 mb-5" />}
<RowGroup rows={rows} />
</div>
))}
<hr className="border-slate-100" />
<div className="flex flex-col">
<span className="text-xs uppercase tracking-wide text-slate-500">
Workload
</span>
<span className="text-sm text-slate-900 mt-0.5">{txLine}</span>
</div>
</div>
</StatCard>
);
};

export default ConfigCard;
73 changes: 70 additions & 3 deletions report/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
import {
Link,
useLocation,
useNavigate,
useParams,
useSearchParams,
} from "react-router-dom";
import clsx from "clsx";
import Logo from "../assets/logo.svg";
import { useTestMetadata } from "../utils/useDataSeries";
import { useLoadTestList, useTestMetadata } from "../utils/useDataSeries";
import { useCallback, useMemo } from "react";
import { uniqBy } from "lodash";
import {} from "react-router-dom";
import Select from "./Select";
import { formatLoadTestTimestamp } from "../utils/formatters";

interface ProvidedProps {
urlPrefix?: string;
}

const DEFAULT_LOAD_TEST_NETWORK = "sepolia";

const Navbar = ({ urlPrefix }: ProvidedProps) => {
const location = useLocation();
const isLoadTestsRoute = location.pathname.startsWith("/load-tests");

const { data: allBenchmarkRuns, isLoading } = useTestMetadata();

const [searchParams] = useSearchParams();
Expand All @@ -31,7 +39,33 @@ const Navbar = ({ urlPrefix }: ProvidedProps) => {
[urlPrefix, searchParams, navigate],
);

const { benchmarkRunId } = useParams();
const {
benchmarkRunId,
network: loadTestNetwork,
timestamp: loadTestTimestamp,
} = useParams();

const activeLoadTestNetwork = loadTestNetwork ?? DEFAULT_LOAD_TEST_NETWORK;

const { data: loadTestEntries, isLoading: isLoadingLoadTests } =
useLoadTestList(isLoadTestsRoute ? activeLoadTestNetwork : null);

const navigateToLoadTestRun = useCallback(
(timestamp: string) => {
navigate({
pathname: `/load-tests/${activeLoadTestNetwork}/${timestamp}`,
});
},
[activeLoadTestNetwork, navigate],
);

const loadTestOptions = useMemo(() => {
if (!loadTestEntries) return [];
return loadTestEntries.map((entry) => ({
label: formatLoadTestTimestamp(entry.timestamp),
value: entry.timestamp,
}));
}, [loadTestEntries]);

const latestRun = useMemo(() => {
return allBenchmarkRuns?.runs.sort(
Expand Down Expand Up @@ -80,6 +114,14 @@ const Navbar = ({ urlPrefix }: ProvidedProps) => {
return optionsWithTestNum;
}, [allBenchmarkRuns, latestRun]);

const tabClass = (active: boolean) =>
clsx(
"px-3 py-4 text-sm border-b-2 -mb-px",
active
? "border-blue-600 text-slate-900 font-medium"
: "border-transparent text-slate-500 hover:text-slate-900",
);

return (
<nav className="flex px-8 border-b border-slate-300 items-center bg-white gap-x-4">
<div className="flex items-center gap-x-4 flex-grow">
Expand All @@ -89,8 +131,16 @@ const Navbar = ({ urlPrefix }: ProvidedProps) => {
</Link>
<div className="font-medium">Client Benchmark Report</div>
</div>
<div className="flex items-center gap-x-2 ml-4 self-stretch">
<Link to="/" className={tabClass(!isLoadTestsRoute)}>
Benchmarks
</Link>
<Link to="/load-tests/sepolia" className={tabClass(isLoadTestsRoute)}>
Load Tests
</Link>
</div>
</div>
{!isLoading && !!allBenchmarkRuns?.runs.length && (
{!isLoadTestsRoute && !isLoading && !!allBenchmarkRuns?.runs.length && (
<div>
<Select
value={benchmarkRunId}
Expand All @@ -104,6 +154,23 @@ const Navbar = ({ urlPrefix }: ProvidedProps) => {
</Select>
</div>
)}
{isLoadTestsRoute &&
!!loadTestTimestamp &&
!isLoadingLoadTests &&
loadTestOptions.length > 0 && (
<div>
<Select
value={loadTestTimestamp}
onChange={(e) => navigateToLoadTestRun(e.target.value)}
>
{loadTestOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</Select>
</div>
)}
</nav>
);
};
Expand Down
60 changes: 60 additions & 0 deletions report/src/components/PercentileBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ReactNode } from "react";
import clsx from "clsx";

export interface PercentileBarRow {
label: string;
numericValue: number;
display: ReactNode;
emphasized?: boolean;
}

interface PercentileBarChartProps {
rows: PercentileBarRow[];
barColorClass?: string;
}

const PercentileBarChart = ({
rows,
barColorClass = "bg-blue-500",
}: PercentileBarChartProps) => {
const max = rows.reduce((m, r) => Math.max(m, r.numericValue), 0);

return (
<div className="flex flex-col gap-y-2">
{rows.map((row) => {
const pct = max === 0 ? 0 : (row.numericValue / max) * 100;
return (
<div
key={row.label}
className="grid grid-cols-[3rem_1fr_auto] items-center gap-x-3"
>
<div
className={clsx(
"text-xs text-slate-500 font-mono",
row.emphasized && "font-semibold text-slate-900",
)}
>
{row.label}
</div>
<div className="bg-slate-100 rounded h-2 relative overflow-hidden">
<div
className={clsx("h-2 rounded transition-all", barColorClass)}
style={{ width: `${pct}%` }}
/>
</div>
<div
className={clsx(
"text-sm text-slate-700 tabular-nums min-w-[6rem] text-right",
row.emphasized && "font-semibold text-slate-900",
)}
>
{row.display}
</div>
</div>
);
})}
</div>
);
};

export default PercentileBarChart;
Loading
Loading