cloudflare/vinext

Public

mirrored from https://github.com/cloudflare/vinextAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.0.9

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

examples/benchmarks/app/components/dashboard.tsx

307lines · modecode

1"use client";
2
3import { useState, useEffect } from "react";
4import { Tabs } from "@cloudflare/kumo/components/tabs";
5import { Badge } from "@cloudflare/kumo/components/badge";
6import { Table } from "@cloudflare/kumo/components/table";
7import { TrendChart } from "./chart";
8import {
9 formatMs,
10 formatBytes,
11 speedup,
12 sizeReduction,
13 isImprovement,
14 RUNNER_LABELS,
15 RUNNER_COLORS,
16} from "./format";
17
18// ─── Types ───────────────────────────────────────────────────────────────────
19
20interface RunnerMetrics {
21 buildTimeMean: number | null;
22 buildTimeStddev: number | null;
23 bundleSizeRaw: number | null;
24 bundleSizeGzip: number | null;
25 bundleFileCount: number | null;
26 devColdStartMs: number | null;
27 devPeakRssKb: number | null;
28}
29
30interface BenchmarkCommit {
31 commitSha: string;
32 commitShort: string;
33 commitMessage: string;
34 commitDate: string;
35 runDate: string;
36 runners: Record<string, RunnerMetrics>;
37}
38
39type MetricTab = "build_time" | "bundle_size" | "cold_start";
40
41// ─── Dashboard Component ─────────────────────────────────────────────────────
42
43export function Dashboard() {
44 const [data, setData] = useState<BenchmarkCommit[]>([]);
45 const [loading, setLoading] = useState(true);
46 const [error, setError] = useState<string | null>(null);
47 const [activeTab, setActiveTab] = useState<MetricTab>("build_time");
48
49 useEffect(() => {
50 fetch("/api/results?limit=50")
51 .then((res) => {
52 if (!res.ok) throw new Error(`HTTP ${res.status}`);
53 return res.json();
54 })
55 .then((d) => {
56 setData(d);
57 setLoading(false);
58 })
59 .catch((err) => {
60 setError(err.message);
61 setLoading(false);
62 });
63 }, []);
64
65 if (loading) {
66 return (
67 <div className="flex items-center justify-center py-20 text-gray-400">
68 Loading benchmark data...
69 </div>
70 );
71 }
72
73 if (error) {
74 return (
75 <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
76 Failed to load benchmarks: {error}
77 </div>
78 );
79 }
80
81 if (data.length === 0) {
82 return (
83 <div className="rounded-lg border border-gray-200 bg-white px-6 py-12 text-center text-gray-400">
84 No benchmark data yet. Results will appear after the first merge to main.
85 </div>
86 );
87 }
88
89 const latest = data[0];
90 // Reverse for chronological order in charts
91 const chronological = [...data].reverse();
92
93 return (
94 <div className="space-y-8">
95 {/* Latest results comparison table */}
96 <section>
97 <div className="mb-3 flex items-center gap-3">
98 <h2 className="text-lg font-semibold">Latest Results</h2>
99 <a href={`/commit/${latest.commitShort}`}>
100 <Badge variant="secondary">{latest.commitShort}</Badge>
101 </a>
102 <span className="text-xs text-gray-400">
103 {new Date(latest.commitDate).toLocaleDateString()}
104 </span>
105 </div>
106 <LatestResultsTable commit={latest} />
107 </section>
108
109 {/* Trend charts */}
110 <section>
111 <h2 className="mb-3 text-lg font-semibold">Trends</h2>
112 <Tabs
113 variant="segmented"
114 tabs={[
115 { value: "build_time", label: "Build Time" },
116 { value: "bundle_size", label: "Client Bundle Size" },
117 { value: "cold_start", label: "Dev Cold Start" },
118 ]}
119 value={activeTab}
120 onValueChange={(v) => setActiveTab(v as MetricTab)}
121 />
122 <div className="mt-4">
123 <MetricChart data={chronological} metric={activeTab} />
124 </div>
125 </section>
126
127 {/* Recent commits list */}
128 <section>
129 <h2 className="mb-3 text-lg font-semibold">Recent Commits</h2>
130 <CommitList commits={data} />
131 </section>
132 </div>
133 );
134}
135
136// ─── Latest Results Table ────────────────────────────────────────────────────
137
138function LatestResultsTable({ commit }: { commit: BenchmarkCommit }) {
139 const nextjs = commit.runners.nextjs;
140 const runners = Object.entries(commit.runners);
141
142 return (
143 <div className="overflow-x-auto rounded-lg border border-gray-200 bg-white">
144 <Table>
145 <Table.Header>
146 <Table.Row>
147 <Table.Head>Framework</Table.Head>
148 <Table.Head>Build Time</Table.Head>
149 <Table.Head>Client Bundle Size (gzip)</Table.Head>
150 <Table.Head>Dev Cold Start</Table.Head>
151 <Table.Head>Peak RSS</Table.Head>
152 </Table.Row>
153 </Table.Header>
154 <Table.Body>
155 {runners.map(([key, metrics]) => (
156 <Table.Row key={key}>
157 <Table.Cell>
158 <div className="flex items-center gap-2">
159 <span
160 className="inline-block h-2.5 w-2.5 rounded-full"
161 style={{ backgroundColor: RUNNER_COLORS[key] }}
162 />
163 <span className="font-medium">{RUNNER_LABELS[key] || key}</span>
164 </div>
165 </Table.Cell>
166 <Table.Cell>
167 <div className="flex items-center gap-2">
168 <span>{formatMs(metrics.buildTimeMean)}</span>
169 {key !== "nextjs" && nextjs && (
170 <ComparisonBadge
171 label={speedup(nextjs.buildTimeMean, metrics.buildTimeMean)}
172 />
173 )}
174 </div>
175 </Table.Cell>
176 <Table.Cell>
177 <div className="flex items-center gap-2">
178 <span>{formatBytes(metrics.bundleSizeGzip)}</span>
179 {key !== "nextjs" && nextjs && (
180 <ComparisonBadge
181 label={sizeReduction(nextjs.bundleSizeGzip, metrics.bundleSizeGzip)}
182 />
183 )}
184 </div>
185 </Table.Cell>
186 <Table.Cell>
187 <div className="flex items-center gap-2">
188 <span>{formatMs(metrics.devColdStartMs)}</span>
189 {key !== "nextjs" && nextjs && (
190 <ComparisonBadge
191 label={speedup(nextjs.devColdStartMs, metrics.devColdStartMs)}
192 />
193 )}
194 </div>
195 </Table.Cell>
196 <Table.Cell>
197 {metrics.devPeakRssKb
198 ? `${Math.round(metrics.devPeakRssKb / 1024)} MB`
199 : "-"}
200 </Table.Cell>
201 </Table.Row>
202 ))}
203 </Table.Body>
204 </Table>
205 </div>
206 );
207}
208
209function ComparisonBadge({ label }: { label: string | null }) {
210 if (!label) return null;
211 return (
212 <Badge variant={isImprovement(label) ? "primary" : "destructive"}>
213 {label}
214 </Badge>
215 );
216}
217
218// ─── Metric Chart ────────────────────────────────────────────────────────────
219
220function MetricChart({
221 data,
222 metric,
223}: {
224 data: BenchmarkCommit[];
225 metric: MetricTab;
226}) {
227 const runners = Object.keys(RUNNER_LABELS);
228
229 // Shared x-axis labels — one per commit, in chronological order
230 const labels = data.map((commit) => commit.commitShort);
231
232 // Each series has one value per commit; null when the runner has no data for that commit
233 const series = runners.map((runner) => ({
234 name: RUNNER_LABELS[runner] || runner,
235 color: RUNNER_COLORS[runner],
236 values: data.map((commit) => {
237 const m = commit.runners[runner];
238 if (!m) return null;
239 if (metric === "build_time") return m.buildTimeMean;
240 if (metric === "bundle_size") return m.bundleSizeGzip;
241 if (metric === "cold_start") return m.devColdStartMs;
242 return null;
243 }),
244 }));
245
246 const yLabel =
247 metric === "build_time"
248 ? "ms"
249 : metric === "bundle_size"
250 ? "bytes"
251 : "ms";
252
253 const formatY =
254 metric === "bundle_size"
255 ? (v: number) => formatBytes(v)
256 : (v: number) => formatMs(v);
257
258 return (
259 <div className="rounded-lg border border-gray-200 bg-white p-4">
260 <TrendChart labels={labels} series={series} yLabel={yLabel} formatY={formatY} height={300} />
261 </div>
262 );
263}
264
265// ─── Commit List ─────────────────────────────────────────────────────────────
266
267function CommitList({ commits }: { commits: BenchmarkCommit[] }) {
268 return (
269 <div className="overflow-x-auto rounded-lg border border-gray-200 bg-white">
270 <Table>
271 <Table.Header>
272 <Table.Row>
273 <Table.Head>Commit</Table.Head>
274 <Table.Head>Date</Table.Head>
275 <Table.Head>Build Time (vinext Rolldown)</Table.Head>
276 <Table.Head>Client Bundle Size (vinext Rolldown)</Table.Head>
277 </Table.Row>
278 </Table.Header>
279 <Table.Body>
280 {commits.map((c) => {
281 const rd = c.runners.vinext_rolldown;
282 return (
283 <Table.Row key={c.commitSha}>
284 <Table.Cell>
285 <a
286 href={`/commit/${c.commitShort}`}
287 className="font-mono text-sm text-blue-600 hover:underline"
288 >
289 {c.commitShort}
290 </a>
291 <span className="ml-2 text-xs text-gray-400 truncate max-w-xs inline-block align-middle">
292 {c.commitMessage?.slice(0, 60)}
293 </span>
294 </Table.Cell>
295 <Table.Cell className="text-sm text-gray-500">
296 {new Date(c.commitDate).toLocaleDateString()}
297 </Table.Cell>
298 <Table.Cell>{rd ? formatMs(rd.buildTimeMean) : "-"}</Table.Cell>
299 <Table.Cell>{rd ? formatBytes(rd.bundleSizeGzip) : "-"}</Table.Cell>
300 </Table.Row>
301 );
302 })}
303 </Table.Body>
304 </Table>
305 </div>
306 );
307}
308