const { useState, useEffect, useRef } = React; const MetricCard = ({ title, stats }) => (

{title}

Avg: {stats.avg} ms
P50: {stats.p50} ms
P95: {stats.p95} ms
P99: {stats.p99} ms
); const DBActivityCard = ({ stats }) => (

Database Activity

Rows Read: {stats.rowsRead}
Rows Written: {stats.rowsWritten}
Total Rows: {stats.totalRows}
); const CacheActivityCard = ({ stats }) => (

Cache Activity

Cache Hits: {stats.hits}
Cache Misses: {stats.misses}
Hit Rate: {stats.hitRate}%
); const MetricsDashboard = () => { const [data, setData] = useState([]); const [timeRange, setTimeRange] = useState("30m"); const [customStart, setCustomStart] = useState(""); const [customEnd, setCustomEnd] = useState(""); const [stats, setStats] = useState({ service: { avg: 0, p50: 0, p95: 0, p99: 0 }, db: { avg: 0, p50: 0, p95: 0, p99: 0 }, cache: { avg: 0, p50: 0, p95: 0, p99: 0 }, dbActivity: { rowsRead: 0, rowsWritten: 0, totalRows: 0 }, cacheActivity: { hits: 0, misses: 0, hitRate: 0 }, }); const chartRef = useRef(null); const chartInstance = useRef(null); const getTimeRangeParams = () => { const now = Date.now(); switch (timeRange) { case "30m": return `?start=${now - 30 * 60 * 1000}&end=${now}`; case "1h": return `?start=${now - 60 * 60 * 1000}&end=${now}`; case "24h": return `?start=${now - 24 * 60 * 60 * 1000}&end=${now}`; case "7d": return `?start=${now - 7 * 24 * 60 * 60 * 1000}&end=${now}`; case "custom": if (customStart && customEnd) { return `?start=${new Date(customStart).getTime()}&end=${new Date(customEnd).getTime()}`; } return ""; case "all": return ""; default: return `?start=${now - 30 * 60 * 1000}&end=${now}`; } }; const bucketDataForChart = (data, bucketSizeMs = 1000) => { const buckets = {}; data.forEach((point) => { const bucketKey = Math.floor(point.timestamp / bucketSizeMs) * bucketSizeMs; if (!buckets[bucketKey]) { buckets[bucketKey] = { timestamp: bucketKey, service_time: [], db_time: [], cache_time: [], db_rows_read: [], db_rows_written: [], cache_hits: [], cache_misses: [], }; } buckets[bucketKey].service_time.push(point.service_time); buckets[bucketKey].db_time.push(point.db_time); buckets[bucketKey].cache_time.push(point.cache_time); buckets[bucketKey].db_rows_read.push(point.db_rows_read); buckets[bucketKey].db_rows_written.push(point.db_rows_written); buckets[bucketKey].cache_hits.push(point.cache_hits); buckets[bucketKey].cache_misses.push(point.cache_misses); }); return Object.values(buckets).map((bucket) => ({ timestamp: bucket.timestamp, service_time: _.mean(bucket.service_time), db_time: _.mean(bucket.db_time), cache_time: _.mean(bucket.cache_time), db_rows_read: _.sum(bucket.db_rows_read), db_rows_written: _.sum(bucket.db_rows_written), cache_hits: _.sum(bucket.cache_hits), cache_misses: _.sum(bucket.cache_misses), })); }; const calculateStats = (data) => { // Input validation with early return if (!data?.length) { return { service: { avg: 0, p50: 0, p95: 0, p99: 0 }, db: { avg: 0, p50: 0, p95: 0, p99: 0 }, cache: { avg: 0, p50: 0, p95: 0, p99: 0 }, dbActivity: { rowsRead: 0, rowsWritten: 0, totalRows: 0 }, cacheActivity: { hits: 0, misses: 0, hitRate: 0 }, }; } // Create separate arrays for each metric type and sort them independently const serviceValues = data .map((d) => Number(d.service_time) || 0) .sort((a, b) => a - b); const dbValues = data .map((d) => Number(d.db_time) || 0) .sort((a, b) => a - b); const cacheValues = data .map((d) => Number(d.cache_time) || 0) .sort((a, b) => a - b); // Calculate percentile indices const len = data.length; const p50idx = Math.floor(len * 0.5); const p95idx = Math.floor(len * 0.95); const p99idx = Math.floor(len * 0.99); // Log the actual values we're using console.log("Sorted Values Sample:", { service: serviceValues.slice(0, 5), db: dbValues.slice(0, 5), cache: cacheValues.slice(0, 5), }); console.log("Median Values:", { service: serviceValues[p50idx], db: dbValues[p50idx], cache: cacheValues[p50idx], }); // Get latest values for activity metrics const latest = data[0] || { cache_hits: 0, cache_misses: 0, db_rows_read: 0, db_rows_written: 0, db_total_rows: 0, }; const totalCacheRequests = latest.cache_hits + latest.cache_misses; const hitRate = totalCacheRequests > 0 ? (latest.cache_hits / totalCacheRequests) * 100 : 0; const stats = { service: { avg: _.round(_.mean(serviceValues), 2), p50: _.round(serviceValues[p50idx] || 0, 2), p95: _.round(serviceValues[p95idx] || 0, 2), p99: _.round(serviceValues[p99idx] || 0, 2), }, db: { avg: _.round(_.mean(dbValues), 2), p50: _.round(dbValues[p50idx] || 0, 2), p95: _.round(dbValues[p95idx] || 0, 2), p99: _.round(dbValues[p99idx] || 0, 2), }, cache: { avg: _.round(_.mean(cacheValues), 2), p50: _.round(cacheValues[p50idx] || 0, 2), p95: _.round(cacheValues[p95idx] || 0, 2), p99: _.round(cacheValues[p99idx] || 0, 2), }, dbActivity: { rowsRead: latest.db_rows_read, rowsWritten: latest.db_rows_written, totalRows: latest.db_total_rows, }, cacheActivity: { hits: latest.cache_hits, misses: latest.cache_misses, hitRate: _.round(hitRate, 1), }, }; // Log final calculated stats console.log("Final Stats:", stats); return stats; }; const updateChart = (forceReset = false) => { if (!chartRef.current) return; if (!chartInstance.current || forceReset) { if (chartInstance.current) { chartInstance.current.destroy(); } initializeChart(); } const bucketedData = bucketDataForChart(data); chartInstance.current.data.datasets[0].data = bucketedData.map((d) => ({ x: d.timestamp, y: d.service_time, })); chartInstance.current.data.datasets[1].data = bucketedData.map((d) => ({ x: d.timestamp, y: d.db_time, })); chartInstance.current.data.datasets[2].data = bucketedData.map((d) => ({ x: d.timestamp, y: d.cache_time, })); chartInstance.current.update("none"); }; const initializeChart = () => { if (!chartRef.current) { console.log("Chart ref not ready"); return; } console.log("Initializing chart"); const ctx = chartRef.current.getContext("2d"); chartInstance.current = new Chart(ctx, { type: "line", data: { datasets: [ { label: "Service Time", borderColor: "#8884d8", data: [], tension: 0.1, }, { label: "DB Time", borderColor: "#82ca9d", data: [], tension: 0.1, }, { label: "Cache Time", borderColor: "#ffc658", data: [], tension: 0.1, }, ], }, options: { responsive: true, maintainAspectRatio: false, animation: false, scales: { x: { type: "time", time: { parser: "MM/DD/YYYY HH:mm", tooltipFormat: "ll HH:mm", unit: "second", displayFormats: { second: "HH:mm:ss", }, }, title: { display: true, text: "Time", }, }, y: { beginAtZero: true, title: { display: true, text: "Time (ms)", }, }, }, }, }); }; const fetchMetrics = async () => { try { console.log("Fetching metrics with params:", getTimeRangeParams()); const response = await fetch(`/api/metrics${getTimeRangeParams()}`); const newData = await response.json(); console.log("Received metrics data:", newData); if (!newData || newData.length === 0) { console.log("No data received"); setData([]); setStats(calculateStats([])); return; } const newStats = calculateStats(newData); console.log("Calculated stats:", newStats); if (newStats) { setStats(newStats); } setData(newData || []); } catch (error) { console.error("Error fetching metrics:", error); setData([]); setStats(calculateStats([])); } }; useEffect(() => { console.log("Initial fetch and chart setup"); fetchMetrics(); updateChart(true); let interval; if (timeRange !== "all" && timeRange !== "custom") { interval = setInterval(fetchMetrics, 1000); console.log("Set up polling interval"); } return () => { if (interval) { console.log("Cleaning up interval"); clearInterval(interval); } }; }, [timeRange, customStart, customEnd]); useEffect(() => { console.log("Data updated:", data.length, "points"); if (data.length > 0 && chartRef.current) { updateChart(); } }, [data]); const exportCSV = () => { try { console.log("Exporting data:", data); const csv = Papa.unparse(data); const blob = new Blob([csv], { type: "text/csv" }); const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `metrics_export_${new Date().toISOString()}.csv`; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); } catch (error) { console.error("Error exporting CSV:", error); } }; return (

Service Performance Metrics

{timeRange === "custom" && (
setCustomStart(e.target.value)} className="rounded border p-2" /> to setCustomEnd(e.target.value)} className="rounded border p-2" />
)}
); }; // Render the app ReactDOM.createRoot(document.getElementById("root")).render( , );