Improve metrics UI performance (#22691)

* embed cpu/mem stats into detectors, cameras, and processes

so history consumers don't need the full cpu_usages dict

* support dot-notation for nested keys

to avoid returning large objects when only specific subfields are needed

* fix setLastUpdated being called inside useMemo

this triggered a setState-during-render warning, so moved to a useEffect

* frontend types

* frontend

hide instead of unmount all graphs - re-rendering is much more expensive and disruptive than the amount of dom memory required

keep track of visited tabs to keep them mounted rather than re-mounting or mounting all tabs

add isActive prop to all charts to re-trigger animation when switching metrics tabs

fix chart data padding bug where the loop used number of series rather than number of data points

fix bug where only a shallow copy of the array was used for mutation

fix missing key prop causing console logs

* add isactive after rebase

* formatting

* skip None values in filtered output for dot notation
This commit is contained in:
Josh Hawkins
2026-03-29 11:58:47 -06:00
committed by GitHub
parent f002513d36
commit f44f485f48
10 changed files with 330 additions and 93 deletions
+25 -1
View File
@@ -2,7 +2,7 @@ import { useTheme } from "@/context/theme-provider";
import { useDateLocale } from "@/hooks/use-date-locale";
import { FrigateConfig } from "@/types/frigateConfig";
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
import { useCallback, useEffect, useMemo } from "react";
import { useCallback, useEffect, useMemo, useRef } from "react";
import Chart from "react-apexcharts";
import { isMobileOnly } from "react-device-detect";
import { useTranslation } from "react-i18next";
@@ -17,6 +17,7 @@ type CameraLineGraphProps = {
dataLabels: string[];
updateTimes: number[];
data: ApexAxisChartSeries;
isActive?: boolean;
};
export function CameraLineGraph({
graphId,
@@ -24,6 +25,7 @@ export function CameraLineGraph({
dataLabels,
updateTimes,
data,
isActive = true,
}: CameraLineGraphProps) {
const { t } = useTranslation(["views/system", "common"]);
const { data: config } = useSWR<FrigateConfig>("config", {
@@ -134,6 +136,16 @@ export function CameraLineGraph({
ApexCharts.exec(graphId, "updateOptions", options, true, true);
}, [graphId, options]);
const hasBeenActive = useRef(isActive);
useEffect(() => {
if (isActive && hasBeenActive.current === false) {
ApexCharts.exec(graphId, "updateSeries", data, true);
}
hasBeenActive.current = isActive;
// only replay animation on visibility change, not data updates
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isActive, graphId]);
return (
<div className="flex w-full flex-col">
{lastValues && (
@@ -166,6 +178,7 @@ type EventsPerSecondLineGraphProps = {
name: string;
updateTimes: number[];
data: ApexAxisChartSeries;
isActive?: boolean;
};
export function EventsPerSecondsLineGraph({
graphId,
@@ -173,6 +186,7 @@ export function EventsPerSecondsLineGraph({
name,
updateTimes,
data,
isActive = true,
}: EventsPerSecondLineGraphProps) {
const { data: config } = useSWR<FrigateConfig>("config", {
revalidateOnFocus: false,
@@ -277,6 +291,16 @@ export function EventsPerSecondsLineGraph({
ApexCharts.exec(graphId, "updateOptions", options, true, true);
}, [graphId, options]);
const hasBeenActive = useRef(isActive);
useEffect(() => {
if (isActive && hasBeenActive.current === false) {
ApexCharts.exec(graphId, "updateSeries", data, true);
}
hasBeenActive.current = isActive;
// only replay animation on visibility change, not data updates
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isActive, graphId]);
return (
<div className="flex w-full flex-col">
<div className="flex items-center gap-1">