Files
frigate/web/src/views/system/GeneralMetrics.tsx
T

1028 lines
31 KiB
TypeScript
Raw Normal View History

2024-04-04 10:24:23 -06:00
import useSWR from "swr";
2026-05-13 15:12:48 -06:00
import { FrigateStats, GpuInfo, GpuStats } from "@/types/stats";
2026-03-29 12:58:47 -05:00
import { startTransition, useEffect, useMemo, useState } from "react";
2024-04-04 10:24:23 -06:00
import { useFrigateStats } from "@/api/ws";
import {
DetectorCpuThreshold,
DetectorMemThreshold,
DetectorTempThreshold,
2024-04-04 10:24:23 -06:00
GPUMemThreshold,
GPUUsageThreshold,
InferenceThreshold,
} from "@/types/graph";
import { Button } from "@/components/ui/button";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
2024-10-07 20:15:31 -06:00
import GPUInfoDialog from "@/components/overlay/GPUInfoDialog";
2024-04-04 10:24:23 -06:00
import { Skeleton } from "@/components/ui/skeleton";
import { ThresholdBarGraph } from "@/components/graph/SystemGraph";
2024-10-07 20:15:31 -06:00
import { cn } from "@/lib/utils";
import { useTranslation } from "react-i18next";
import { CiCircleAlert } from "react-icons/ci";
2024-04-04 10:24:23 -06:00
type GeneralMetricsProps = {
lastUpdated: number;
setLastUpdated: (last: number) => void;
2026-03-29 12:58:47 -05:00
isActive: boolean;
2024-04-04 10:24:23 -06:00
};
export default function GeneralMetrics({
lastUpdated,
setLastUpdated,
2026-03-29 12:58:47 -05:00
isActive,
2024-04-04 10:24:23 -06:00
}: GeneralMetricsProps) {
// extra info
const { t } = useTranslation(["views/system"]);
2024-04-04 10:24:23 -06:00
const [showVainfo, setShowVainfo] = useState(false);
// stats
2026-03-29 12:58:47 -05:00
const { data: initialStats, mutate: refreshStats } = useSWR<FrigateStats[]>(
2024-04-04 10:24:23 -06:00
[
"stats/history",
2026-03-29 12:58:47 -05:00
{
keys: "detectors.inference_speed,detectors.temperature,detectors.cpu,detectors.mem,gpu_usages,npu_usages,processes.cpu,processes.mem,service.last_updated",
},
2024-04-04 10:24:23 -06:00
],
{
revalidateOnFocus: false,
},
);
const [statsHistory, setStatsHistory] = useState<FrigateStats[]>([]);
2024-07-11 09:25:33 -06:00
const updatedStats = useFrigateStats();
2024-04-04 10:24:23 -06:00
useEffect(() => {
if (initialStats == undefined || initialStats.length == 0) {
return;
}
if (statsHistory.length == 0) {
2026-03-29 12:58:47 -05:00
startTransition(() => setStatsHistory(initialStats));
2024-04-04 10:24:23 -06:00
return;
}
2026-03-29 12:58:47 -05:00
if (!isActive || !updatedStats) {
2024-04-04 10:24:23 -06:00
return;
}
if (updatedStats.service.last_updated > lastUpdated) {
setStatsHistory([...statsHistory.slice(1), updatedStats]);
2026-03-29 12:58:47 -05:00
setLastUpdated(updatedStats.service.last_updated);
2024-04-04 10:24:23 -06:00
}
2026-03-29 12:58:47 -05:00
}, [
initialStats,
updatedStats,
statsHistory,
lastUpdated,
setLastUpdated,
isActive,
]);
useEffect(() => {
if (isActive && statsHistory.length > 0) {
refreshStats().then((freshStats) => {
if (freshStats && freshStats.length > 0) {
setStatsHistory(freshStats);
}
});
}
// only re-fetch when tab becomes active, not on data changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isActive]);
2024-04-04 10:24:23 -06:00
2024-10-07 20:15:31 -06:00
const [canGetGpuInfo, gpuType] = useMemo<[boolean, GpuInfo]>(() => {
let vaCount = 0;
let nvCount = 0;
statsHistory.length > 0 &&
2026-05-13 15:12:48 -06:00
Object.values(statsHistory[0]?.gpu_usages ?? {}).forEach((stats) => {
if (stats.vendor === "nvidia") {
2024-10-07 20:15:31 -06:00
nvCount += 1;
2026-05-13 15:12:48 -06:00
} else if (stats.vendor === "intel" || stats.vendor === "amd") {
vaCount += 1;
2024-10-07 20:15:31 -06:00
}
});
return [vaCount > 0 || nvCount > 0, nvCount > 0 ? "nvinfo" : "vainfo"];
}, [statsHistory]);
2024-04-04 14:55:04 -06:00
2024-04-04 10:24:23 -06:00
// timestamps
const updateTimes = useMemo(
() => statsHistory.map((stats) => stats.service.last_updated),
[statsHistory],
);
// detectors stats
const detInferenceTimeSeries = useMemo(() => {
if (!statsHistory) {
return [];
}
const series: {
[key: string]: { name: string; data: { x: number; y: number }[] };
} = {};
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
Object.entries(stats.detectors).forEach(([key, stats]) => {
if (!(key in series)) {
series[key] = { name: key, data: [] };
}
2024-04-07 14:36:28 -06:00
series[key].data.push({ x: statsIdx + 1, y: stats.inference_speed });
2024-04-04 10:24:23 -06:00
});
});
return Object.values(series);
}, [statsHistory]);
const detTempSeries = useMemo(() => {
if (!statsHistory) {
2024-10-07 20:15:31 -06:00
return undefined;
}
const series: {
[key: string]: { name: string; data: { x: number; y: number }[] };
} = {};
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
Object.entries(stats.detectors).forEach(([key, detectorStats]) => {
if (detectorStats.temperature === undefined) {
2024-10-07 20:15:31 -06:00
return;
}
if (!(key in series)) {
series[key] = {
name: key,
data: [],
};
2026-02-26 21:27:31 -07:00
}
series[key].data.push({
x: statsIdx + 1,
y: Math.round(detectorStats.temperature),
});
});
});
2024-10-07 20:15:31 -06:00
if (Object.keys(series).length > 0) {
return Object.values(series);
}
return undefined;
}, [statsHistory]);
2024-04-04 10:24:23 -06:00
const detCpuSeries = useMemo(() => {
if (!statsHistory) {
return [];
}
const series: {
[key: string]: { name: string; data: { x: number; y: string }[] };
} = {};
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
Object.entries(stats.detectors).forEach(([key, detStats]) => {
if (!(key in series)) {
series[key] = { name: key, data: [] };
}
2026-03-29 12:58:47 -05:00
const data = detStats.cpu;
2024-06-17 06:19:16 -06:00
if (data != undefined) {
series[key].data.push({
x: statsIdx + 1,
y: data,
});
}
2024-04-04 10:24:23 -06:00
});
});
return Object.values(series);
}, [statsHistory]);
const detMemSeries = useMemo(() => {
if (!statsHistory) {
return [];
}
const series: {
[key: string]: { name: string; data: { x: number; y: string }[] };
} = {};
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
Object.entries(stats.detectors).forEach(([key, detStats]) => {
if (!(key in series)) {
series[key] = { name: key, data: [] };
}
2026-03-29 12:58:47 -05:00
if (detStats.mem != undefined) {
series[key].data.push({
x: statsIdx + 1,
y: detStats.mem,
});
}
2024-04-04 10:24:23 -06:00
});
});
return Object.values(series);
}, [statsHistory]);
// gpu stats
const gpuSeries = useMemo(() => {
if (!statsHistory) {
return [];
}
const series: {
[key: string]: { name: string; data: { x: number; y: string }[] };
} = {};
2024-04-27 10:26:51 -06:00
let hasValidGpu = false;
2024-04-04 10:24:23 -06:00
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
2025-04-29 17:03:44 -05:00
Object.entries(stats.gpu_usages || {}).forEach(([key, stats]) => {
2024-04-04 10:24:23 -06:00
if (!(key in series)) {
series[key] = { name: key, data: [] };
}
2024-04-27 10:26:51 -06:00
if (stats.gpu) {
hasValidGpu = true;
series[key].data.push({ x: statsIdx + 1, y: stats.gpu.slice(0, -1) });
}
2024-04-04 10:24:23 -06:00
});
});
2024-04-27 10:26:51 -06:00
if (!hasValidGpu) {
return [];
}
2024-04-04 10:24:23 -06:00
return Object.keys(series).length > 0 ? Object.values(series) : [];
}, [statsHistory]);
const gpuMemSeries = useMemo(() => {
if (!statsHistory) {
return [];
}
2026-05-13 15:12:48 -06:00
// Intel doesn't expose VRAM usage, so hide the memory section
// entirely when every reporting GPU is Intel.
const firstEntries: GpuStats[] = Object.values(
statsHistory[0]?.gpu_usages ?? {},
);
2024-04-09 17:08:11 -06:00
if (
2026-05-13 15:12:48 -06:00
firstEntries.length > 0 &&
firstEntries.every((s) => s.vendor === "intel")
2024-04-09 17:08:11 -06:00
) {
return undefined;
}
2024-04-04 10:24:23 -06:00
const series: {
[key: string]: { name: string; data: { x: number; y: string }[] };
} = {};
2024-04-27 10:26:51 -06:00
let hasValidGpu = false;
2024-04-04 10:24:23 -06:00
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
Object.entries(stats.gpu_usages || {}).forEach(([key, stats]) => {
2026-05-13 15:12:48 -06:00
if (stats.vendor === "intel") {
return;
}
2024-04-04 10:24:23 -06:00
if (!(key in series)) {
series[key] = { name: key, data: [] };
}
2024-04-27 10:26:51 -06:00
if (stats.mem) {
hasValidGpu = true;
series[key].data.push({ x: statsIdx + 1, y: stats.mem.slice(0, -1) });
}
2024-04-04 10:24:23 -06:00
});
});
2024-04-27 10:26:51 -06:00
if (!hasValidGpu) {
return [];
}
2024-04-04 10:24:23 -06:00
return Object.values(series);
}, [statsHistory]);
2024-10-07 20:15:31 -06:00
const gpuEncSeries = useMemo(() => {
if (!statsHistory) {
return [];
}
const series: {
[key: string]: { name: string; data: { x: number; y: string }[] };
} = {};
let hasValidGpu = false;
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
2025-04-29 17:03:44 -05:00
Object.entries(stats.gpu_usages || {}).forEach(([key, stats]) => {
2024-10-07 20:15:31 -06:00
if (!(key in series)) {
series[key] = { name: key, data: [] };
}
if (stats.enc) {
hasValidGpu = true;
series[key].data.push({ x: statsIdx + 1, y: stats.enc.slice(0, -1) });
}
});
});
if (!hasValidGpu) {
return [];
}
return Object.keys(series).length > 0 ? Object.values(series) : undefined;
}, [statsHistory]);
2026-03-29 11:09:02 -06:00
const gpuComputeSeries = useMemo(() => {
if (!statsHistory) {
return [];
}
const series: {
[key: string]: { name: string; data: { x: number; y: string }[] };
} = {};
let hasValidGpu = false;
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
Object.entries(stats.gpu_usages || {}).forEach(([key, stats]) => {
if (!(key in series)) {
series[key] = { name: key, data: [] };
}
if (stats.compute) {
hasValidGpu = true;
series[key].data.push({
x: statsIdx + 1,
y: stats.compute.slice(0, -1),
});
}
});
});
if (!hasValidGpu) {
return [];
}
return Object.keys(series).length > 0 ? Object.values(series) : undefined;
}, [statsHistory]);
2024-10-07 20:15:31 -06:00
const gpuDecSeries = useMemo(() => {
if (!statsHistory) {
return [];
}
const series: {
[key: string]: { name: string; data: { x: number; y: string }[] };
} = {};
let hasValidGpu = false;
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
2025-04-29 17:03:44 -05:00
Object.entries(stats.gpu_usages || {}).forEach(([key, stats]) => {
2024-10-07 20:15:31 -06:00
if (!(key in series)) {
series[key] = { name: key, data: [] };
}
if (stats.dec) {
hasValidGpu = true;
series[key].data.push({ x: statsIdx + 1, y: stats.dec.slice(0, -1) });
}
});
});
if (!hasValidGpu) {
return [];
}
return Object.keys(series).length > 0 ? Object.values(series) : undefined;
}, [statsHistory]);
const gpuTempSeries = useMemo(() => {
if (!statsHistory) {
return [];
}
const series: {
[key: string]: { name: string; data: { x: number; y: number }[] };
} = {};
let hasValidGpu = false;
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
Object.entries(stats.gpu_usages || {}).forEach(([key, stats]) => {
if (!(key in series)) {
series[key] = { name: key, data: [] };
}
2026-05-12 11:20:39 -05:00
if (stats?.temp !== undefined) {
hasValidGpu = true;
series[key].data.push({ x: statsIdx + 1, y: stats.temp });
}
});
});
if (!hasValidGpu) {
return [];
}
return Object.keys(series).length > 0 ? Object.values(series) : undefined;
}, [statsHistory]);
// npu stats
const npuSeries = useMemo(() => {
if (!statsHistory) {
return [];
}
const series: {
[key: string]: { name: string; data: { x: number; y: number }[] };
} = {};
let hasValidNpu = false;
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
2025-04-29 17:03:44 -05:00
Object.entries(stats.npu_usages || {}).forEach(([key, stats]) => {
if (!(key in series)) {
series[key] = { name: key, data: [] };
}
2025-04-30 18:11:45 -06:00
if (stats?.npu) {
hasValidNpu = true;
series[key].data.push({ x: statsIdx + 1, y: stats.npu });
}
});
});
if (!hasValidNpu) {
return [];
}
return Object.keys(series).length > 0 ? Object.values(series) : [];
}, [statsHistory]);
const npuTempSeries = useMemo(() => {
if (!statsHistory) {
return [];
}
const series: {
[key: string]: { name: string; data: { x: number; y: number }[] };
} = {};
let hasValidNpu = false;
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
Object.entries(stats.npu_usages || {}).forEach(([key, stats]) => {
if (!(key in series)) {
series[key] = { name: key, data: [] };
}
2026-05-12 11:20:39 -05:00
if (stats?.temp !== undefined) {
hasValidNpu = true;
series[key].data.push({ x: statsIdx + 1, y: stats.temp });
}
});
});
if (!hasValidNpu) {
return [];
}
return Object.keys(series).length > 0 ? Object.values(series) : undefined;
}, [statsHistory]);
2024-04-04 10:24:23 -06:00
// other processes stats
const hardwareType = useMemo(() => {
const hasGpu = gpuSeries.length > 0;
const hasNpu = npuSeries.length > 0;
if (hasGpu && !hasNpu) {
return "GPUs";
} else if (!hasGpu && hasNpu) {
return "NPUs";
} else {
return "GPUs / NPUs";
}
}, [gpuSeries, npuSeries]);
2024-04-04 10:24:23 -06:00
const otherProcessCpuSeries = useMemo(() => {
if (!statsHistory) {
return [];
}
const series: {
[key: string]: { name: string; data: { x: number; y: string }[] };
} = {};
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
Object.entries(stats.processes).forEach(([key, procStats]) => {
2026-03-29 12:58:47 -05:00
if (!(key in series)) {
series[key] = {
name: t(`general.otherProcesses.series.${key}`),
data: [],
};
}
2024-04-04 10:24:23 -06:00
2026-03-29 12:58:47 -05:00
if (procStats.cpu != undefined) {
series[key].data.push({
x: statsIdx + 1,
y: procStats.cpu,
});
2024-04-04 10:24:23 -06:00
}
});
});
return Object.keys(series).length > 0 ? Object.values(series) : [];
2026-03-24 20:49:05 +08:00
}, [statsHistory, t]);
2024-04-04 10:24:23 -06:00
const otherProcessMemSeries = useMemo(() => {
if (!statsHistory) {
return [];
}
const series: {
[key: string]: { name: string; data: { x: number; y: string }[] };
} = {};
statsHistory.forEach((stats, statsIdx) => {
if (!stats) {
return;
}
Object.entries(stats.processes).forEach(([key, procStats]) => {
2026-03-29 12:58:47 -05:00
if (!(key in series)) {
series[key] = {
name: t(`general.otherProcesses.series.${key}`),
data: [],
};
}
2024-04-04 10:24:23 -06:00
2026-03-29 12:58:47 -05:00
if (procStats.mem) {
series[key].data.push({
x: statsIdx + 1,
y: procStats.mem,
});
2024-04-04 10:24:23 -06:00
}
});
});
return Object.values(series);
2026-03-24 20:49:05 +08:00
}, [statsHistory, t]);
2024-04-04 10:24:23 -06:00
return (
<>
2024-10-07 20:15:31 -06:00
<GPUInfoDialog
showGpuInfo={showVainfo}
gpuType={gpuType}
setShowGpuInfo={setShowVainfo}
/>
2024-04-04 10:24:23 -06:00
<div className="scrollbar-container mt-4 flex size-full flex-col overflow-y-auto">
2024-05-14 10:06:44 -05:00
<div className="text-sm font-medium text-muted-foreground">
{t("general.detector.title")}
2024-04-04 10:24:23 -06:00
</div>
<div
2024-10-07 20:15:31 -06:00
className={cn(
"mt-4 grid w-full grid-cols-1 gap-2 sm:grid-cols-3",
detTempSeries && "sm:grid-cols-4",
)}
>
2024-04-04 14:55:04 -06:00
{statsHistory.length != 0 ? (
2024-05-14 10:06:44 -05:00
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">{t("general.detector.inferenceSpeed")}</div>
2024-04-04 10:24:23 -06:00
{detInferenceTimeSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-inference`}
name={series.name}
unit="ms"
threshold={InferenceThreshold}
updateTimes={updateTimes}
data={[series]}
2026-03-29 12:58:47 -05:00
isActive={isActive}
2024-04-04 10:24:23 -06:00
/>
))}
</div>
) : (
2024-05-14 10:06:44 -05:00
<Skeleton className="aspect-video w-full rounded-lg md:rounded-2xl" />
2024-04-04 10:24:23 -06:00
)}
2024-10-07 20:15:31 -06:00
{statsHistory.length != 0 && (
<>
{detTempSeries && (
2024-05-14 10:06:44 -05:00
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
2025-04-29 07:47:19 -05:00
<div className="mb-5">
{t("general.detector.temperature")}
</div>
{detTempSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-temp`}
name={series.name}
unit="°C"
threshold={DetectorTempThreshold}
updateTimes={updateTimes}
data={[series]}
2026-03-29 12:58:47 -05:00
isActive={isActive}
/>
))}
</div>
)}
</>
)}
2024-04-04 10:24:23 -06:00
{statsHistory.length != 0 ? (
2024-05-14 10:06:44 -05:00
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5 flex flex-row items-center justify-between">
{t("general.detector.cpuUsage")}
<Popover>
<PopoverTrigger asChild>
<button
className="focus:outline-none"
aria-label={t("general.detector.cpuUsage")}
>
<CiCircleAlert
className="size-5"
aria-label={t("general.detector.cpuUsage")}
/>
</button>
</PopoverTrigger>
<PopoverContent className="w-80">
<div className="space-y-2">
{t("general.detector.cpuUsageInformation")}
</div>
</PopoverContent>
</Popover>
</div>
2024-04-04 10:24:23 -06:00
{detCpuSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-cpu`}
unit="%"
name={series.name}
threshold={DetectorCpuThreshold}
updateTimes={updateTimes}
data={[series]}
2026-03-29 12:58:47 -05:00
isActive={isActive}
2024-04-04 10:24:23 -06:00
/>
))}
</div>
) : (
2024-05-14 10:06:44 -05:00
<Skeleton className="aspect-video w-full" />
2024-04-04 10:24:23 -06:00
)}
{statsHistory.length != 0 ? (
2024-05-14 10:06:44 -05:00
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">{t("general.detector.memoryUsage")}</div>
2024-04-04 10:24:23 -06:00
{detMemSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-mem`}
unit="%"
name={series.name}
threshold={DetectorMemThreshold}
updateTimes={updateTimes}
data={[series]}
2026-03-29 12:58:47 -05:00
isActive={isActive}
2024-04-04 10:24:23 -06:00
/>
))}
</div>
) : (
2024-05-14 10:06:44 -05:00
<Skeleton className="aspect-video w-full" />
2024-04-04 10:24:23 -06:00
)}
</div>
{(statsHistory.length == 0 ||
2025-04-30 18:11:45 -06:00
gpuSeries.length > 0 ||
npuSeries.length > 0) && (
2024-04-04 10:24:23 -06:00
<>
<div className="mt-4 flex items-center justify-between">
2024-05-14 10:06:44 -05:00
<div className="text-sm font-medium text-muted-foreground">
{hardwareType}
2024-04-04 10:24:23 -06:00
</div>
2024-04-04 14:55:04 -06:00
{canGetGpuInfo && (
<Button
className="cursor-pointer"
aria-label={t("general.hardwareInfo.title")}
2024-04-04 14:55:04 -06:00
size="sm"
onClick={() => setShowVainfo(true)}
>
{t("general.hardwareInfo.title")}
2024-04-04 14:55:04 -06:00
</Button>
)}
2024-04-04 10:24:23 -06:00
</div>
2024-10-07 20:15:31 -06:00
<div
className={cn(
"mt-4 grid grid-cols-1 gap-2 sm:grid-cols-2",
gpuTempSeries?.length && "md:grid-cols-3",
2026-03-29 11:09:02 -06:00
(gpuEncSeries?.length || gpuComputeSeries?.length) &&
"xl:grid-cols-4",
(gpuEncSeries?.length || gpuComputeSeries?.length) &&
gpuTempSeries?.length &&
"3xl:grid-cols-5",
2024-10-07 20:15:31 -06:00
)}
>
{statsHistory[0]?.gpu_usages && (
<>
{statsHistory.length != 0 ? (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
2026-07-15 19:49:05 -05:00
<div className="mb-5">
{t("general.hardwareInfo.gpuUsage")}
</div>
{gpuSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-gpu`}
name={series.name}
unit="%"
threshold={GPUUsageThreshold}
updateTimes={updateTimes}
data={[series]}
2026-03-29 12:58:47 -05:00
isActive={isActive}
/>
))}
</div>
) : (
<Skeleton className="aspect-video w-full" />
)}
{statsHistory.length != 0 ? (
<>
{gpuMemSeries && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.hardwareInfo.gpuMemory")}
</div>
{gpuMemSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-mem`}
unit="%"
name={series.name}
threshold={GPUMemThreshold}
updateTimes={updateTimes}
data={[series]}
2026-03-29 12:58:47 -05:00
isActive={isActive}
/>
))}
</div>
)}
</>
) : (
<Skeleton className="aspect-video w-full" />
)}
{statsHistory.length != 0 ? (
<>
{gpuEncSeries && gpuEncSeries?.length != 0 && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.hardwareInfo.gpuEncoder")}
</div>
{gpuEncSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-enc`}
unit="%"
name={series.name}
threshold={GPUMemThreshold}
updateTimes={updateTimes}
data={[series]}
2026-03-29 12:58:47 -05:00
isActive={isActive}
/>
))}
</div>
)}
</>
) : (
<Skeleton className="aspect-video w-full" />
)}
2026-03-29 11:09:02 -06:00
{statsHistory.length != 0 ? (
<>
{gpuComputeSeries && gpuComputeSeries?.length != 0 && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.hardwareInfo.gpuCompute")}
</div>
{gpuComputeSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-compute`}
unit="%"
name={series.name}
threshold={GPUMemThreshold}
updateTimes={updateTimes}
data={[series]}
2026-03-29 12:58:47 -05:00
isActive={isActive}
2026-03-29 11:09:02 -06:00
/>
))}
</div>
)}
</>
) : (
<Skeleton className="aspect-video w-full" />
)}
{statsHistory.length != 0 ? (
<>
{gpuDecSeries && gpuDecSeries?.length != 0 && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.hardwareInfo.gpuDecoder")}
</div>
{gpuDecSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-dec`}
unit="%"
name={series.name}
threshold={GPUMemThreshold}
updateTimes={updateTimes}
data={[series]}
2026-03-29 12:58:47 -05:00
isActive={isActive}
/>
))}
</div>
)}
</>
) : (
<Skeleton className="aspect-video w-full" />
)}
{statsHistory.length != 0 ? (
<>
{gpuTempSeries && gpuTempSeries?.length != 0 && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.hardwareInfo.gpuTemperature")}
</div>
{gpuTempSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-temp`}
name={series.name}
unit="°C"
threshold={DetectorTempThreshold}
updateTimes={updateTimes}
data={[series]}
2026-03-29 12:58:47 -05:00
isActive={isActive}
/>
))}
</div>
)}
</>
) : (
<Skeleton className="aspect-video w-full" />
)}
2025-11-20 16:50:17 -07:00
{statsHistory[0]?.npu_usages && (
<>
{statsHistory.length != 0 ? (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.hardwareInfo.npuUsage")}
</div>
{npuSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-npu`}
name={series.name}
unit="%"
threshold={GPUUsageThreshold}
updateTimes={updateTimes}
data={[series]}
2026-03-29 12:58:47 -05:00
isActive={isActive}
2025-11-20 16:50:17 -07:00
/>
))}
</div>
) : (
<Skeleton className="aspect-video w-full" />
)}
{statsHistory.length != 0 ? (
<>
{npuTempSeries && npuTempSeries?.length != 0 && (
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.hardwareInfo.npuTemperature")}
</div>
{npuTempSeries.map((series) => (
<ThresholdBarGraph
key={series.name}
graphId={`${series.name}-temp`}
name={series.name}
unit="°C"
threshold={DetectorTempThreshold}
updateTimes={updateTimes}
data={[series]}
2026-03-29 12:58:47 -05:00
isActive={isActive}
/>
))}
</div>
)}
</>
) : (
2025-11-20 16:50:17 -07:00
<Skeleton className="aspect-video w-full" />
)}
</>
)}
2025-11-20 16:50:17 -07:00
</>
2024-04-04 10:24:23 -06:00
)}
</div>
</>
)}
2024-05-14 10:06:44 -05:00
<div className="mt-4 text-sm font-medium text-muted-foreground">
{t("general.otherProcesses.title")}
2024-04-04 10:24:23 -06:00
</div>
2024-05-14 10:06:44 -05:00
<div className="mt-4 grid grid-cols-1 gap-2 sm:grid-cols-2">
2024-04-04 10:24:23 -06:00
{statsHistory.length != 0 ? (
2024-05-14 10:06:44 -05:00
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.otherProcesses.processCpuUsage")}
</div>
2026-03-24 20:49:05 +08:00
{otherProcessCpuSeries.map((series, index) => (
2024-04-04 10:24:23 -06:00
<ThresholdBarGraph
2026-03-24 20:49:05 +08:00
key={`other-process-cpu-${index}`}
graphId={`other-process-cpu-${index}`}
2024-04-04 10:24:23 -06:00
unit="%"
threshold={DetectorCpuThreshold}
updateTimes={updateTimes}
data={[series]}
2026-03-29 12:58:47 -05:00
isActive={isActive}
2024-04-04 10:24:23 -06:00
/>
))}
</div>
) : (
2024-05-14 10:06:44 -05:00
<Skeleton className="aspect-tall w-full" />
2024-04-04 10:24:23 -06:00
)}
{statsHistory.length != 0 ? (
2024-05-14 10:06:44 -05:00
<div className="rounded-lg bg-background_alt p-2.5 md:rounded-2xl">
<div className="mb-5">
{t("general.otherProcesses.processMemoryUsage")}
</div>
2026-03-24 20:49:05 +08:00
{otherProcessMemSeries.map((series, index) => (
2024-04-04 10:24:23 -06:00
<ThresholdBarGraph
2026-03-24 20:49:05 +08:00
key={`other-process-mem-${index}`}
graphId={`other-process-mem-${index}`}
2024-04-04 10:24:23 -06:00
unit="%"
threshold={DetectorMemThreshold}
updateTimes={updateTimes}
data={[series]}
2026-03-29 12:58:47 -05:00
isActive={isActive}
2024-04-04 10:24:23 -06:00
/>
))}
</div>
) : (
2024-05-14 10:06:44 -05:00
<Skeleton className="aspect-tall w-full" />
2024-04-04 10:24:23 -06:00
)}
</div>
</div>
</>
);
}