diff --git a/public/perf.js b/public/perf.js
index 2052cefd..ca0c8ab6 100644
--- a/public/perf.js
+++ b/public/perf.js
@@ -18,16 +18,33 @@
Promise.resolve(window.apiPerf ? window.apiPerf() : null)
]);
+ // Also fetch health telemetry
+ const health = await fetch('/api/health').then(r => r.json()).catch(() => null);
+
let html = '';
// Server overview
html += `
${server.totalRequests}
Total Requests
${server.avgMs}ms
Avg Response
-
${Math.round(server.uptime / 60)}m
Uptime
+
${health ? health.uptimeHuman : Math.round(server.uptime / 60) + 'm'}
Uptime
${server.slowQueries.length}
Slow (>100ms)
`;
+ // System health (memory, event loop, WS)
+ if (health) {
+ const m = health.memory, el = health.eventLoop;
+ const elColor = el.p95Ms > 500 ? '#ef4444' : el.p95Ms > 100 ? '#f59e0b' : '#22c55e';
+ const memColor = m.heapUsed > m.heapTotal * 0.85 ? '#ef4444' : m.heapUsed > m.heapTotal * 0.7 ? '#f59e0b' : '#22c55e';
+ html += `System Health
+
${m.heapUsed}MB
Heap Used / ${m.heapTotal}MB
+
+
${el.p95Ms}ms
Event Loop p95
+
${el.maxLagMs}ms
EL Max Lag
+
${el.currentLagMs}ms
EL Current
+
${health.websocket.clients}
WS Clients
+
`;
+
// Cache stats
if (server.cache) {
const c = server.cache;
@@ -37,6 +54,8 @@
${c.hitRate}%
Server Hit Rate
+ ${c.staleHits || 0}
Stale Hits (SWR)
+ ${c.recomputes || 0}
Recomputes
${clientCache}
Client Entries
`;
if (client) {
diff --git a/server.js b/server.js
index 7545c0fb..47f3743e 100644
--- a/server.js
+++ b/server.js
@@ -185,7 +185,7 @@ app.get('/api/perf', (req, res) => {
avgMs: perfStats.requests ? Math.round(perfStats.totalMs / perfStats.requests * 10) / 10 : 0,
endpoints: Object.fromEntries(sorted),
slowQueries: perfStats.slowQueries.slice(-20),
- cache: { size: cache.size, hits: cache.hits, misses: cache.misses, hitRate: cache.hits + cache.misses > 0 ? Math.round(cache.hits / (cache.hits + cache.misses) * 1000) / 10 : 0 },
+ cache: { size: cache.size, hits: cache.hits, misses: cache.misses, staleHits: cache.staleHits, recomputes: cache.recomputes, hitRate: cache.hits + cache.misses > 0 ? Math.round(cache.hits / (cache.hits + cache.misses) * 1000) / 10 : 0 },
packetStore: pktStore.getStats(),
});
});