mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-19 23:08:08 +02:00
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:
@@ -52,18 +52,66 @@ class StatsEmitter(threading.Thread):
|
||||
def get_stats_history(
|
||||
self, keys: Optional[list[str]] = None
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Get stats history."""
|
||||
"""Get stats history.
|
||||
|
||||
Supports dot-notation for nested keys to avoid returning large objects
|
||||
when only specific subfields are needed. Handles two patterns:
|
||||
|
||||
- Flat dict: "service.last_updated" returns {"service": {"last_updated": ...}}
|
||||
- Dict-of-dicts: "cameras.camera_fps" returns each camera entry filtered
|
||||
to only include "camera_fps"
|
||||
"""
|
||||
if not keys:
|
||||
return self.stats_history
|
||||
|
||||
# Pre-parse keys into top-level keys and dot-notation fields
|
||||
top_level_keys: list[str] = []
|
||||
nested_keys: dict[str, list[str]] = {}
|
||||
|
||||
for k in keys:
|
||||
if "." in k:
|
||||
parent_key, child_key = k.split(".", 1)
|
||||
nested_keys.setdefault(parent_key, []).append(child_key)
|
||||
else:
|
||||
top_level_keys.append(k)
|
||||
|
||||
selected_stats: list[dict[str, Any]] = []
|
||||
|
||||
for s in self.stats_history:
|
||||
selected = {}
|
||||
selected: dict[str, Any] = {}
|
||||
|
||||
for k in keys:
|
||||
for k in top_level_keys:
|
||||
selected[k] = s.get(k)
|
||||
|
||||
for parent_key, child_keys in nested_keys.items():
|
||||
parent = s.get(parent_key)
|
||||
|
||||
if not isinstance(parent, dict):
|
||||
selected[parent_key] = parent
|
||||
continue
|
||||
|
||||
# Check if values are dicts (dict-of-dicts like cameras/detectors)
|
||||
first_value = next(iter(parent.values()), None)
|
||||
|
||||
if isinstance(first_value, dict):
|
||||
# Filter each nested entry to only requested fields,
|
||||
# omitting None values to preserve key-absence semantics
|
||||
selected[parent_key] = {
|
||||
entry_key: {
|
||||
field: val
|
||||
for field in child_keys
|
||||
if (val := entry.get(field)) is not None
|
||||
}
|
||||
for entry_key, entry in parent.items()
|
||||
}
|
||||
else:
|
||||
# Flat dict (like service) - pick individual fields
|
||||
if parent_key not in selected:
|
||||
selected[parent_key] = {}
|
||||
|
||||
for child_key in child_keys:
|
||||
selected[parent_key][child_key] = parent.get(child_key)
|
||||
|
||||
selected_stats.append(selected)
|
||||
|
||||
return selected_stats
|
||||
|
||||
@@ -498,4 +498,30 @@ def stats_snapshot(
|
||||
"pid": pid,
|
||||
}
|
||||
|
||||
# Embed cpu/mem stats into detectors, cameras, and processes
|
||||
# so history consumers don't need the full cpu_usages dict
|
||||
cpu_usages = stats.get("cpu_usages", {})
|
||||
|
||||
for det_stats in stats["detectors"].values():
|
||||
pid_str = str(det_stats.get("pid", ""))
|
||||
usage = cpu_usages.get(pid_str, {})
|
||||
det_stats["cpu"] = usage.get("cpu")
|
||||
det_stats["mem"] = usage.get("mem")
|
||||
|
||||
for cam_stats in stats["cameras"].values():
|
||||
for pid_key, field in [
|
||||
("ffmpeg_pid", "ffmpeg_cpu"),
|
||||
("capture_pid", "capture_cpu"),
|
||||
("pid", "detect_cpu"),
|
||||
]:
|
||||
pid_str = str(cam_stats.get(pid_key, ""))
|
||||
usage = cpu_usages.get(pid_str, {})
|
||||
cam_stats[field] = usage.get("cpu")
|
||||
|
||||
for proc_stats in stats["processes"].values():
|
||||
pid_str = str(proc_stats.get("pid", ""))
|
||||
usage = cpu_usages.get(pid_str, {})
|
||||
proc_stats["cpu"] = usage.get("cpu")
|
||||
proc_stats["mem"] = usage.get("mem")
|
||||
|
||||
return stats
|
||||
|
||||
Reference in New Issue
Block a user