mirror of
https://github.com/gadgethd/ukmesh.git
synced 2026-10-23 22:32:52 +00:00
Cull map rendering and correct health CPU metric
This commit is contained in:
1 parent
7b9ac180a9
commit
b908fee98f
3 files changed
+119
-8
No files matched your search
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
Reference in new issue
Block a user