File size: 5,674 Bytes
dab3de7 4a70373 dab3de7 4a70373 dab3de7 4a70373 dab3de7 4a70373 dab3de7 4a70373 dab3de7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | "use client";
import { useState } from "react";
import { runBenchmark } from "@/lib/actions";
type BenchResult = { name: string; ms: number; rows?: number };
const badge = (ms: number) =>
ms < 1 ? "text-emerald-400" : ms < 5 ? "text-yellow-400" : ms < 20 ? "text-orange-400" : "text-red-400";
const bar = (ms: number, max: number) =>
Math.max(4, Math.min(100, (ms / max) * 100));
export default function QueryBench() {
const [results, setResults] = useState<BenchResult[]>([]);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
const [runs, setRuns] = useState<number[]>([]);
const run = async () => {
setLoading(true);
try {
const r = await runBenchmark();
setResults(r.results);
setTotal(r.totalMs);
setRuns((p) => [...p.slice(-9), r.totalMs]);
} finally {
setLoading(false);
}
};
const max = results.length ? Math.max(...results.map((r) => r.ms), 0.1) : 1;
const avg = runs.length ? +(runs.reduce((a, b) => a + b, 0) / runs.length).toFixed(2) : 0;
return (
<div className="space-y-4">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<h2 className="text-lg font-bold">⚡ Query Benchmark</h2>
<p className="text-xs text-zinc-500">SQLite query performance — direct server-side via Prisma (no API roundtrip)</p>
</div>
<button
onClick={run}
disabled={loading}
className="bg-emerald-600 hover:bg-emerald-500 disabled:bg-zinc-700 px-5 py-2.5 rounded-lg text-sm font-medium transition shrink-0"
>
{loading ? "Running..." : "🚀 Run Benchmark"}
</button>
</div>
{results.length > 0 && (
<>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<Stat label="Total" value={`${total}ms`} />
<Stat label="Queries" value={`${results.length}`} />
<Stat label="Avg/query" value={`${(total / results.length).toFixed(2)}ms`} />
<Stat label={`Avg (${runs.length} runs)`} value={`${avg}ms`} />
</div>
<div className="bg-zinc-900 border border-zinc-800 rounded-xl overflow-hidden overflow-x-auto">
<div className="min-w-[480px]">
<div className="grid grid-cols-[1fr_80px_60px_1fr] gap-2 px-4 py-2.5 border-b border-zinc-800 text-[11px] text-zinc-500 font-medium uppercase tracking-wider">
<span>Query</span>
<span className="text-right">Time</span>
<span className="text-right">Rows</span>
<span>Speed</span>
</div>
{results.map((r, i) => (
<div
key={i}
className="grid grid-cols-[1fr_80px_60px_1fr] gap-2 px-4 py-3 border-b border-zinc-800/50 hover:bg-zinc-800/30 transition text-sm items-center"
>
<span className="font-mono text-xs truncate">{r.name}</span>
<span className={`text-right font-mono text-xs font-bold ${badge(r.ms)}`}>
{r.ms}ms
</span>
<span className="text-right text-xs text-zinc-500">
{r.rows !== undefined ? r.rows : "—"}
</span>
<div className="h-2 bg-zinc-800 rounded-full overflow-hidden">
<div
className={`h-full rounded-full transition-all duration-500 ${r.ms < 1 ? "bg-emerald-500" : r.ms < 5 ? "bg-yellow-500" : r.ms < 20 ? "bg-orange-500" : "bg-red-500"}`}
style={{ width: `${bar(r.ms, max)}%` }}
/>
</div>
</div>
))}
</div>
</div>
<div className="flex flex-wrap gap-3 text-[11px] text-zinc-500">
<span className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-full bg-emerald-500" /><1ms</span>
<span className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-full bg-yellow-500" />1-5ms</span>
<span className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-full bg-orange-500" />5-20ms</span>
<span className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-full bg-red-500" />>20ms</span>
</div>
{runs.length > 1 && (
<div className="bg-zinc-900 border border-zinc-800 rounded-xl p-4">
<p className="text-xs text-zinc-500 mb-2">Run History (total ms)</p>
<div className="flex items-end gap-1 h-16">
{runs.map((r, i) => {
const h = Math.max(8, (r / Math.max(...runs)) * 100);
return (
<div key={i} className="flex-1 flex flex-col items-center gap-1">
<span className="text-[9px] text-zinc-500">{r.toFixed(0)}</span>
<div
className={`w-full rounded-t ${r < 20 ? "bg-emerald-600" : r < 50 ? "bg-yellow-600" : "bg-red-600"}`}
style={{ height: `${h}%` }}
/>
</div>
);
})}
</div>
</div>
)}
</>
)}
</div>
);
}
function Stat({ label, value }: { label: string; value: string }) {
return (
<div className="bg-zinc-900 border border-zinc-800 rounded-lg p-3 text-center">
<p className="text-lg font-bold font-mono">{value}</p>
<p className="text-[10px] text-zinc-500 mt-0.5">{label}</p>
</div>
);
}
|