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" />&lt;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" />&gt;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>
  );
}