perf(beszel): throttle live renders, reduce buffer, skip hidden charts
- Decouple 1Hz SSE ingestion from render rate: accumulate data in a ref and flush to React state every 3s instead of every tick - First data point flushes immediately to avoid blank screen - Reduce MAX_BUFFER from 60 to 30 (halves SVG reconciliation work) - Gate chart data hooks behind visibility flags so disabled charts skip all array processing
This commit is contained in:
2 files changed
+53
-19
No files matched your search
@@ -129,16 +129,19 @@ export default function BeszelSystemStatsWidget({
|
||||
[t],
|
||||
);
|
||||
|
||||
const cpuData = useSystemChartData(activeStats?.systemStats, cpuMap, isLive);
|
||||
const memoryData = useSystemChartData(activeStats?.systemStats, memoryMap, isLive);
|
||||
const diskData = useSystemChartData(activeStats?.systemStats, diskMap, isLive);
|
||||
const diskIOData = useSystemChartData(activeStats?.systemStats, diskIOMap, isLive);
|
||||
const networkData = useSystemChartData(activeStats?.systemStats, networkMap, isLive);
|
||||
const systemStats = activeStats?.systemStats;
|
||||
const containerStatsRaw = activeStats?.containerStats;
|
||||
|
||||
const containerNames = useContainerNames(activeStats?.containerStats);
|
||||
const dockerCpuData = useDockerChartData(activeStats?.containerStats, containerNames, "cpu", isLive);
|
||||
const dockerMemData = useDockerChartData(activeStats?.containerStats, containerNames, "memory", isLive);
|
||||
const dockerNetData = useDockerChartData(activeStats?.containerStats, containerNames, "network", isLive);
|
||||
const cpuData = useSystemChartData(options.showCpu && systemStats || undefined, cpuMap, isLive);
|
||||
const memoryData = useSystemChartData(options.showMemory && systemStats || undefined, memoryMap, isLive);
|
||||
const diskData = useSystemChartData(options.showDisk && systemStats || undefined, diskMap, isLive);
|
||||
const diskIOData = useSystemChartData(options.showDiskIO && systemStats || undefined, diskIOMap, isLive);
|
||||
const networkData = useSystemChartData(options.showNetwork && systemStats || undefined, networkMap, isLive);
|
||||
|
||||
const containerNames = useContainerNames(showDocker && containerStatsRaw || undefined);
|
||||
const dockerCpuData = useDockerChartData(options.showDockerCpu && containerStatsRaw || undefined, containerNames, "cpu", isLive);
|
||||
const dockerMemData = useDockerChartData(options.showDockerMemory && containerStatsRaw || undefined, containerNames, "memory", isLive);
|
||||
const dockerNetData = useDockerChartData(options.showDockerNetwork && containerStatsRaw || undefined, containerNames, "network", isLive);
|
||||
|
||||
const containerSeries = useMemo(
|
||||
() => containerNames.map((name, i) => ({ name, color: containerColors[i % containerColors.length] as string })),
|
||||
|
||||
@@ -1,48 +1,79 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import type { BeszelContainerStatsRecord, BeszelSystemStatsRecord } from "@homarr/integrations/types";
|
||||
|
||||
const MAX_BUFFER = 60;
|
||||
const MAX_BUFFER = 30;
|
||||
const FLUSH_INTERVAL_MS = 3000;
|
||||
|
||||
interface LiveStatsData {
|
||||
systemStats: BeszelSystemStatsRecord[];
|
||||
containerStats: BeszelContainerStatsRecord[];
|
||||
}
|
||||
|
||||
function appendToBuffer(
|
||||
prev: LiveStatsData | null,
|
||||
incoming: { stats: BeszelSystemStatsRecord; containerStats: BeszelContainerStatsRecord | null },
|
||||
): LiveStatsData {
|
||||
const systemStats = [...(prev?.systemStats ?? []), incoming.stats].slice(-MAX_BUFFER);
|
||||
const prevContainer = prev?.containerStats ?? [];
|
||||
if (!incoming.containerStats) {
|
||||
return { systemStats, containerStats: prevContainer };
|
||||
}
|
||||
return { systemStats, containerStats: [...prevContainer, incoming.containerStats].slice(-MAX_BUFFER) };
|
||||
}
|
||||
|
||||
export const useLiveStats = (integrationIds: string[], systemId: string, enabled: boolean) => {
|
||||
const [data, setData] = useState<LiveStatsData | null>(null);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const bufferRef = useRef<LiveStatsData | null>(null);
|
||||
const dirtyRef = useRef(false);
|
||||
|
||||
const integrationKey = integrationIds.join(",");
|
||||
|
||||
useEffect(() => {
|
||||
setData(null);
|
||||
setError(null);
|
||||
bufferRef.current = null;
|
||||
dirtyRef.current = false;
|
||||
}, [systemId, enabled, integrationKey]);
|
||||
|
||||
const onData = useCallback(
|
||||
(incoming: { stats: BeszelSystemStatsRecord; containerStats: BeszelContainerStatsRecord | null }) => {
|
||||
setError(null);
|
||||
setData((prev) => {
|
||||
const systemStats = [...(prev?.systemStats ?? []), incoming.stats].slice(-MAX_BUFFER);
|
||||
const containerStats = incoming.containerStats
|
||||
? [...(prev?.containerStats ?? []), incoming.containerStats].slice(-MAX_BUFFER)
|
||||
: (prev?.containerStats ?? []);
|
||||
return { systemStats, containerStats };
|
||||
});
|
||||
const isFirst = !bufferRef.current;
|
||||
bufferRef.current = appendToBuffer(bufferRef.current, incoming);
|
||||
dirtyRef.current = true;
|
||||
if (isFirst) {
|
||||
setData(bufferRef.current);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const onError = useCallback((err: unknown) => {
|
||||
setError(err instanceof Error ? err : new Error(String(err)));
|
||||
if (err instanceof Error) {
|
||||
setError(err);
|
||||
return;
|
||||
}
|
||||
setError(new Error(String(err)));
|
||||
}, []);
|
||||
|
||||
const subscriptionInput = useMemo(() => ({ integrationIds, systemId }), [integrationKey, systemId]);
|
||||
const subscriptionEnabled = enabled && systemId !== "";
|
||||
|
||||
useEffect(() => {
|
||||
if (!subscriptionEnabled) return;
|
||||
const id = setInterval(() => {
|
||||
if (!dirtyRef.current || !bufferRef.current) return;
|
||||
dirtyRef.current = false;
|
||||
setData(bufferRef.current);
|
||||
}, FLUSH_INTERVAL_MS);
|
||||
return () => clearInterval(id);
|
||||
}, [subscriptionEnabled]);
|
||||
|
||||
clientApi.widget.beszel.subscribeSystemStats.useSubscription(subscriptionInput, {
|
||||
enabled: subscriptionEnabled,
|
||||
onData,
|
||||
|
||||
Reference in new issue
Block a user