Cull map rendering and correct health CPU metric

This commit is contained in:
Ben committed 2026-03-08 03:53:29 +00:00
1 parent 7b9ac180a9
commit b908fee98f
3 files changed
+119 -8

No files matched your search

+39
View File
@@ -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<WorkerSnapshot[]> {
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<WorkerSnapshot[]> {
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<WorkerSnapshot[]> {
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<WorkerSnapshot[]> {
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<WorkerSnapshot[]> {
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<WorkerSnapshot[]> {
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,
},
+77 -6
View File
@@ -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<SyncerProps> = ({ onViewStateChange }) => {
const map = useMap();
@@ -116,6 +123,7 @@ export const MapView: React.FC<MapViewProps> = ({
showLinks, showHexClashes, maxHexClashHops, viablePairsArr, linkMetrics, packetPaths, betaPaths, betaLowSegments, betaCompletionPaths, showBetaPaths, pathOpacity, onMapReady,
}) => {
const [map, setMap] = useState<LeafletMap | null>(null);
const [viewBounds, setViewBounds] = useState<ViewBounds | null>(null);
const [focusedPrefix, setFocusedPrefix] = useState<string | null>(null);
const [focusedNodeId, setFocusedNodeId] = useState<string | null>(null);
const [focusedPrefixNodeIds, setFocusedPrefixNodeIds] = useState<Set<string> | null>(null);
@@ -140,6 +148,26 @@ export const MapView: React.FC<MapViewProps> = ({
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<DeckViewState>({
longitude: DEFAULT_CENTER[1],
latitude: DEFAULT_CENTER[0],
@@ -285,6 +313,33 @@ export const MapView: React.FC<MapViewProps> = ({
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<MapViewProps> = ({
}
}
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<MapViewProps> = ({
{clashModeActive && (
<Pane name="hexClashPane" style={{ zIndex: 405 }}>
{clashPathLines.map((line) => (
{visibleClashPathLines.map((line) => (
<Polyline
key={line.key}
positions={line.positions}
@@ -572,7 +643,7 @@ export const MapView: React.FC<MapViewProps> = ({
)}
{/* 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<MapViewProps> = ({
})}
{/* 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;
+3 -2
View File
@@ -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 = () => {
<section className="prose-section">
<h2>Server Stats</h2>
<div className="site-stats-grid site-stats-grid--6 health-system-grid">
<div className="site-stat"><span className="site-stat__value">{fmtPct(data?.system.cpu.load_pct)}</span><span className="site-stat__label">CPU Load</span></div>
<div className="site-stat"><span className="site-stat__value">{fmtPct(data?.system.cpu.usage_pct)}</span><span className="site-stat__label">CPU Usage</span></div>
<div className="site-stat"><span className="site-stat__value">{fmtPct(data?.system.memory.used_pct)}</span><span className="site-stat__label">Memory Used</span></div>
<div className="site-stat"><span className="site-stat__value">{fmtPct(data?.system.disk.used_pct)}</span><span className="site-stat__label">Disk Used</span></div>
<div className="site-stat"><span className="site-stat__value">{fmtInt(data?.frontend_errors_1h)}</span><span className="site-stat__label">Frontend Errors (1h)</span></div>
@@ -196,6 +196,7 @@ export const HealthPage: React.FC = () => {
<div className="health-kv"><span>Updated</span><strong>{data?.system.generated_at ? timeAgo(data.system.generated_at) : 'just now'}</strong></div>
<div className="health-kv"><span>Node Runtime</span><strong>{data?.system.runtime.node_version ?? '-'}</strong></div>
<div className="health-kv"><span>Platform</span><strong>{data?.system.runtime.platform ?? '-'} / {data?.system.runtime.arch ?? '-'}</strong></div>
<div className="health-kv"><span>CPU Usage</span><strong>{fmtPct(data?.system.cpu.usage_pct)}</strong></div>
<div className="health-kv"><span>CPU 1m Load</span><strong>{Number(data?.system.cpu.load_1m ?? 0).toFixed(2)} ({data?.system.cpu.count ?? 0} cores)</strong></div>
<div className="health-kv"><span>Memory</span><strong>{fmtInt(data?.system.memory.used_mb)} / {fmtInt(data?.system.memory.total_mb)} MB</strong></div>
<div className="health-kv"><span>Disk</span><strong>{fmtGb(data?.system.disk.used_gb)} / {fmtGb(data?.system.disk.total_gb)}</strong></div>