From b908fee98febbe4c6014fe253f25df6e1f7623b5 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 8 Mar 2026 03:53:29 +0000 Subject: [PATCH] Cull map rendering and correct health CPU metric --- backend/src/health/status.ts | 39 ++++++++++++ frontend/src/components/Map/MapView.tsx | 83 +++++++++++++++++++++++-- frontend/src/pages/HealthPage.tsx | 5 +- 3 files changed, 119 insertions(+), 8 deletions(-) diff --git a/backend/src/health/status.ts b/backend/src/health/status.ts index c8dad71..9027e94 100644 --- a/backend/src/health/status.ts +++ b/backend/src/health/status.ts @@ -10,6 +10,7 @@ type WorkerSnapshot = { processed_1h: number; last_activity_at: string | null; cpu_load_1m: number; + cpu_usage_pct: number; mem_used_pct: number; disk_used_pct: number; }; @@ -29,9 +30,40 @@ function toPct(num: number): number { return Math.round(num * 1000) / 10; } +type CpuSample = { + idle: number; + total: number; +}; + +let lastCpuSample: CpuSample | null = null; + +function readCpuSample(): CpuSample { + const cpus = os.cpus(); + let idle = 0; + let total = 0; + for (const cpu of cpus) { + idle += cpu.times.idle; + total += cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.idle + cpu.times.irq; + } + return { idle, total }; +} + +function cpuUsagePct(): number { + const current = readCpuSample(); + const previous = lastCpuSample; + lastCpuSample = current; + if (!previous) return 0; + + const idleDelta = Math.max(0, current.idle - previous.idle); + const totalDelta = Math.max(0, current.total - previous.total); + if (totalDelta <= 0) return 0; + return toPct(1 - idleDelta / totalDelta); +} + function systemStats() { const load1 = os.loadavg()[0] ?? 0; const cpuCount = os.cpus().length; + const usagePct = cpuUsagePct(); const totalMem = os.totalmem(); const freeMem = os.freemem(); const usedMem = Math.max(0, totalMem - freeMem); @@ -53,6 +85,7 @@ function systemStats() { load_1m: load1, count: cpuCount, load_pct: cpuCount > 0 ? toPct(load1 / cpuCount) : 0, + usage_pct: usagePct, }, memory: { total_mb: Math.round(totalMem / 1_048_576), @@ -140,6 +173,7 @@ async function currentWorkers(): Promise { processed_1h: viewshedProcessed, last_activity_at: viewshedLast.rows[0]?.ts ?? null, cpu_load_1m: load, + cpu_usage_pct: stats.cpu.usage_pct, mem_used_pct: memPct, disk_used_pct: diskPct, }, @@ -150,6 +184,7 @@ async function currentWorkers(): Promise { processed_1h: linkProcessed, last_activity_at: linkLast.rows[0]?.ts ?? null, cpu_load_1m: load, + cpu_usage_pct: stats.cpu.usage_pct, mem_used_pct: memPct, disk_used_pct: diskPct, }, @@ -160,6 +195,7 @@ async function currentWorkers(): Promise { processed_1h: learningRecent ? 1 : 0, last_activity_at: learningLast, cpu_load_1m: load, + cpu_usage_pct: stats.cpu.usage_pct, mem_used_pct: memPct, disk_used_pct: diskPct, }, @@ -170,6 +206,7 @@ async function currentWorkers(): Promise { processed_1h: healthProcessed, last_activity_at: healthLastTs, cpu_load_1m: load, + cpu_usage_pct: stats.cpu.usage_pct, mem_used_pct: memPct, disk_used_pct: diskPct, }, @@ -180,6 +217,7 @@ async function currentWorkers(): Promise { processed_1h: 0, last_activity_at: backfillLast, cpu_load_1m: load, + cpu_usage_pct: stats.cpu.usage_pct, mem_used_pct: memPct, disk_used_pct: diskPct, }, @@ -190,6 +228,7 @@ async function currentWorkers(): Promise { processed_1h: pathSimLast && (Date.now() - Date.parse(pathSimLast)) <= 60 * 60_000 ? 1 : 0, last_activity_at: pathSimLast, cpu_load_1m: load, + cpu_usage_pct: stats.cpu.usage_pct, mem_used_pct: memPct, disk_used_pct: diskPct, }, diff --git a/frontend/src/components/Map/MapView.tsx b/frontend/src/components/Map/MapView.tsx index ca3e0de..f7fa4c0 100644 --- a/frontend/src/components/Map/MapView.tsx +++ b/frontend/src/components/Map/MapView.tsx @@ -22,6 +22,13 @@ interface DeckViewState { bearing: number; } +type ViewBounds = { + north: number; + south: number; + east: number; + west: number; +}; + // Leaflet→deck.gl sync component const LeafletDeckSyncer: React.FC = ({ onViewStateChange }) => { const map = useMap(); @@ -116,6 +123,7 @@ export const MapView: React.FC = ({ showLinks, showHexClashes, maxHexClashHops, viablePairsArr, linkMetrics, packetPaths, betaPaths, betaLowSegments, betaCompletionPaths, showBetaPaths, pathOpacity, onMapReady, }) => { const [map, setMap] = useState(null); + const [viewBounds, setViewBounds] = useState(null); const [focusedPrefix, setFocusedPrefix] = useState(null); const [focusedNodeId, setFocusedNodeId] = useState(null); const [focusedPrefixNodeIds, setFocusedPrefixNodeIds] = useState | null>(null); @@ -140,6 +148,26 @@ export const MapView: React.FC = ({ if (map && onMapReady) onMapReady(map); }, [map, onMapReady]); + useEffect(() => { + if (!map) return; + const syncBounds = () => { + const bounds = map.getBounds().pad(0.2); + setViewBounds({ + north: bounds.getNorth(), + south: bounds.getSouth(), + east: bounds.getEast(), + west: bounds.getWest(), + }); + }; + map.on('moveend', syncBounds); + map.on('zoomend', syncBounds); + syncBounds(); + return () => { + map.off('moveend', syncBounds); + map.off('zoomend', syncBounds); + }; + }, [map]); + const [deckViewState, setDeckViewState] = useState({ longitude: DEFAULT_CENTER[1], latitude: DEFAULT_CENTER[0], @@ -285,6 +313,33 @@ export const MapView: React.FC = ({ const linkKey = (a: string, b: string) => (a < b ? `${a}:${b}` : `${b}:${a}`); + const inView = useCallback((lat: number, lon: number) => { + if (!viewBounds) return true; + return lat <= viewBounds.north + && lat >= viewBounds.south + && lon <= viewBounds.east + && lon >= viewBounds.west; + }, [viewBounds]); + + const lineInView = useCallback((positions: [number, number][]) => { + if (!viewBounds || positions.length < 1) return true; + let minLat = Number.POSITIVE_INFINITY; + let maxLat = Number.NEGATIVE_INFINITY; + let minLon = Number.POSITIVE_INFINITY; + let maxLon = Number.NEGATIVE_INFINITY; + for (const [lat, lon] of positions) { + if (inView(lat, lon)) return true; + minLat = Math.min(minLat, lat); + maxLat = Math.max(maxLat, lat); + minLon = Math.min(minLon, lon); + maxLon = Math.max(maxLon, lon); + } + return !(maxLat < viewBounds.south + || minLat > viewBounds.north + || maxLon < viewBounds.west + || minLon > viewBounds.east); + }, [inView, viewBounds]); + const linkColor = (pathLossDb: number | null | undefined) => { if (pathLossDb == null) return '#d1d5db'; if (pathLossDb <= 120) return '#22c55e'; @@ -467,12 +522,28 @@ export const MapView: React.FC = ({ } } const zoom = map?.getZoom() ?? DEFAULT_ZOOM; - if (zoom >= 9 || lines.length <= 400) return lines; - return lines + const visibleLines = lines.filter((line) => lineInView(line.positions)); + if (zoom >= 9 || visibleLines.length <= 400) return visibleLines; + return visibleLines .sort((a, b) => (b.observedCount - a.observedCount)) .slice(0, 400); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [showLinks, viablePairsArr, linkedNodesKey, linkMetrics, map]); + }, [showLinks, viablePairsArr, linkedNodesKey, linkMetrics, map, lineInView]); + + const visibleClashPathLines = useMemo( + () => clashPathLines.filter((line) => lineInView(line.positions)), + [clashPathLines, lineInView], + ); + + const visibleRepeaterNodes = useMemo( + () => nodesWithPos.filter((node) => hasCoords(node) && inView(node.lat, node.lon)), + [nodesWithPos, inView], + ); + + const visibleClientNodes = useMemo( + () => clientNodesArr.filter((node) => hasCoords(node) && inView(node.lat, node.lon)), + [clientNodesArr, inView], + ); const markerSize = useMemo(() => { const leafletZoom = deckViewState.zoom + 1; @@ -556,7 +627,7 @@ export const MapView: React.FC = ({ {clashModeActive && ( - {clashPathLines.map((line) => ( + {visibleClashPathLines.map((line) => ( = ({ )} {/* Repeater markers — Leaflet default marker pane at zIndex 600 */} - {nodesWithPos.map((node) => { + {visibleRepeaterNodes.map((node) => { if (isHiddenMapNode(node)) return null; if (showHexClashes && !clashVisibleNodeIds.has(node.node_id)) return null; const isFocusVisible = clashVisibleNodeIds.has(node.node_id) || (focusedPrefixNodeIds?.has(node.node_id) ?? false); @@ -595,7 +666,7 @@ export const MapView: React.FC = ({ })} {/* Companion radio + room server markers (toggled via filter) */} - {showClientNodes && !showHexClashes && clientNodesArr.map((node) => { + {showClientNodes && !showHexClashes && visibleClientNodes.map((node) => { if (isHiddenMapNode(node)) return null; const isFocusVisible = clashVisibleNodeIds.has(node.node_id); if (focusedPrefixNodeIds && focusHidePhase === 'hide' && !isFocusVisible) return null; diff --git a/frontend/src/pages/HealthPage.tsx b/frontend/src/pages/HealthPage.tsx index 6390140..cabd390 100644 --- a/frontend/src/pages/HealthPage.tsx +++ b/frontend/src/pages/HealthPage.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react'; type HealthPayload = { system: { generated_at: string; - cpu: { load_1m: number; count: number; load_pct: number }; + cpu: { load_1m: number; count: number; load_pct: number; usage_pct: number }; memory: { total_mb: number; used_mb: number; used_pct: number }; disk: { total_gb: number; used_gb: number; used_pct: number }; runtime: { uptime_s: number; node_version: string; platform: string; arch: string }; @@ -185,7 +185,7 @@ export const HealthPage: React.FC = () => {

Server Stats

-
{fmtPct(data?.system.cpu.load_pct)}CPU Load
+
{fmtPct(data?.system.cpu.usage_pct)}CPU Usage
{fmtPct(data?.system.memory.used_pct)}Memory Used
{fmtPct(data?.system.disk.used_pct)}Disk Used
{fmtInt(data?.frontend_errors_1h)}Frontend Errors (1h)
@@ -196,6 +196,7 @@ export const HealthPage: React.FC = () => {
Updated{data?.system.generated_at ? timeAgo(data.system.generated_at) : 'just now'}
Node Runtime{data?.system.runtime.node_version ?? '-'}
Platform{data?.system.runtime.platform ?? '-'} / {data?.system.runtime.arch ?? '-'}
+
CPU Usage{fmtPct(data?.system.cpu.usage_pct)}
CPU 1m Load{Number(data?.system.cpu.load_1m ?? 0).toFixed(2)} ({data?.system.cpu.count ?? 0} cores)
Memory{fmtInt(data?.system.memory.used_mb)} / {fmtInt(data?.system.memory.total_mb)} MB
Disk{fmtGb(data?.system.disk.used_gb)} / {fmtGb(data?.system.disk.total_gb)}