mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 11:49:16 +00:00
perf: fix dashboard out of memory crashing from massive historical metrics payloads
This commit is contained in:
@@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
- **Fixed:** A massive memory leak (browser Out of Memory crash) by throttling historical metrics polling down to 60s and downsampling SQLite metrics payload sizes by 12x.
|
||||
- **Fixed:** A bug where the active node UI dropdown would desync from the actual API requests on initial page load by properly hydrating state from localStorage.
|
||||
- **Fixed:** Remote node proxy forwarding the browser's `sencho_token` cookie to the remote Sencho instance — the remote's `authMiddleware` evaluates `cookieToken || bearerToken` and the cookie (signed with the local JWT secret) was validated before the valid Bearer token, causing 401 on all proxied API calls. Fixed by stripping the `cookie` header in `proxyReq` so only the Bearer token is used for remote authentication.
|
||||
- **Fixed:** `nodeContextMiddleware` blocking `/api/nodes` when `x-node-id` references a deleted/non-existent node — the nodes list endpoint must always succeed so the frontend can re-sync a stale node ID in localStorage; exempted alongside `/api/auth/`.
|
||||
|
||||
@@ -324,7 +324,20 @@ export class DatabaseService {
|
||||
|
||||
public getContainerMetrics(hoursLookback = 24): any[] {
|
||||
const cutoff = Date.now() - (hoursLookback * 60 * 60 * 1000);
|
||||
const stmt = this.db.prepare('SELECT * FROM container_metrics WHERE timestamp >= ? ORDER BY timestamp ASC');
|
||||
const stmt = this.db.prepare(`
|
||||
SELECT
|
||||
container_id,
|
||||
stack_name,
|
||||
AVG(cpu_percent) as cpu_percent,
|
||||
AVG(memory_mb) as memory_mb,
|
||||
MAX(net_rx_mb) as net_rx_mb,
|
||||
MAX(net_tx_mb) as net_tx_mb,
|
||||
(timestamp / 60000) * 60000 as timestamp
|
||||
FROM container_metrics
|
||||
WHERE timestamp >= ?
|
||||
GROUP BY container_id, stack_name, (timestamp / 60000)
|
||||
ORDER BY timestamp ASC
|
||||
`);
|
||||
return stmt.all(cutoff);
|
||||
}
|
||||
|
||||
|
||||
@@ -82,20 +82,15 @@ export default function HomeDashboard() {
|
||||
return () => clearInterval(interval);
|
||||
}, [activeNode?.id]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Fetch system stats and historical metrics - re-runs when active node changes
|
||||
// Fetch system stats (CPU/RAM/Disk/Network) - 5s polling, re-runs on node switch
|
||||
useEffect(() => {
|
||||
setSystemStats(null);
|
||||
setMetrics([]);
|
||||
const fetchSystemStats = async () => {
|
||||
try {
|
||||
const [sysRes, metricsRes] = await Promise.all([
|
||||
apiFetch('/system/stats'),
|
||||
apiFetch('/metrics/historical')
|
||||
]);
|
||||
if (sysRes.ok) setSystemStats(await sysRes.json());
|
||||
if (metricsRes.ok) setMetrics(await metricsRes.json());
|
||||
const res = await apiFetch('/system/stats');
|
||||
if (res.ok) setSystemStats(await res.json());
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch system stats or metrics:', error);
|
||||
console.error('Failed to fetch system stats:', error);
|
||||
}
|
||||
};
|
||||
fetchSystemStats();
|
||||
@@ -103,6 +98,24 @@ export default function HomeDashboard() {
|
||||
return () => clearInterval(interval);
|
||||
}, [activeNode?.id]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Fetch historical metrics - intentionally slow 60s poll to prevent OOM.
|
||||
// The backend now returns 1-minute buckets (down from raw 5s points), so
|
||||
// re-fetching more often than 60s would return the same data anyway.
|
||||
useEffect(() => {
|
||||
setMetrics([]);
|
||||
const fetchMetrics = async () => {
|
||||
try {
|
||||
const res = await apiFetch('/metrics/historical');
|
||||
if (res.ok) setMetrics(await res.json());
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch historical metrics:', error);
|
||||
}
|
||||
};
|
||||
fetchMetrics();
|
||||
const interval = setInterval(fetchMetrics, 60000);
|
||||
return () => clearInterval(interval);
|
||||
}, [activeNode?.id]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const chartData = useMemo(() => {
|
||||
const buckets: Record<string, { time: string; timestamp: number; cpu: number; ram: number }> = {};
|
||||
const cores = systemStats?.cpu.cores || 1;
|
||||
|
||||
Reference in New Issue
Block a user