mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
Improve stats (#10911)
* Add overview stats for overall detection and skipped fps * Fix intel memory stats * Fix iOS image long pressing * Cleanup
This commit is contained in:
parent
15e4f5c771
commit
524732ec73
@ -13,13 +13,14 @@ import { getIconForLabel } from "@/utils/iconUtil";
|
|||||||
import TimeAgo from "../dynamic/TimeAgo";
|
import TimeAgo from "../dynamic/TimeAgo";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import { isFirefox, isMobile, isSafari } from "react-device-detect";
|
import { isFirefox, isIOS, isMobile, isSafari } from "react-device-detect";
|
||||||
import Chip from "@/components/indicators/Chip";
|
import Chip from "@/components/indicators/Chip";
|
||||||
import { useFormattedTimestamp } from "@/hooks/use-date-utils";
|
import { useFormattedTimestamp } from "@/hooks/use-date-utils";
|
||||||
import useImageLoaded from "@/hooks/use-image-loaded";
|
import useImageLoaded from "@/hooks/use-image-loaded";
|
||||||
import { useSwipeable } from "react-swipeable";
|
import { useSwipeable } from "react-swipeable";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
||||||
import ImageLoadingIndicator from "../indicators/ImageLoadingIndicator";
|
import ImageLoadingIndicator from "../indicators/ImageLoadingIndicator";
|
||||||
|
import useContextMenu from "@/hooks/use-contextmenu";
|
||||||
|
|
||||||
type PreviewPlayerProps = {
|
type PreviewPlayerProps = {
|
||||||
review: ReviewSegment;
|
review: ReviewSegment;
|
||||||
@ -73,6 +74,10 @@ export default function PreviewThumbnailPlayer({
|
|||||||
setReviewed(review);
|
setReviewed(review);
|
||||||
}, [review, setReviewed]);
|
}, [review, setReviewed]);
|
||||||
|
|
||||||
|
useContextMenu(imgRef, () => {
|
||||||
|
onClick(review, true);
|
||||||
|
});
|
||||||
|
|
||||||
// playback
|
// playback
|
||||||
|
|
||||||
const relevantPreview = useMemo(() => {
|
const relevantPreview = useMemo(() => {
|
||||||
@ -170,10 +175,6 @@ export default function PreviewThumbnailPlayer({
|
|||||||
className="relative size-full cursor-pointer"
|
className="relative size-full cursor-pointer"
|
||||||
onMouseOver={isMobile ? undefined : () => setIsHovered(true)}
|
onMouseOver={isMobile ? undefined : () => setIsHovered(true)}
|
||||||
onMouseLeave={isMobile ? undefined : () => setIsHovered(false)}
|
onMouseLeave={isMobile ? undefined : () => setIsHovered(false)}
|
||||||
onContextMenu={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
onClick(review, true);
|
|
||||||
}}
|
|
||||||
onClick={handleOnClick}
|
onClick={handleOnClick}
|
||||||
{...swipeHandlers}
|
{...swipeHandlers}
|
||||||
>
|
>
|
||||||
@ -196,9 +197,18 @@ export default function PreviewThumbnailPlayer({
|
|||||||
<div className={`${imgLoaded ? "visible" : "invisible"}`}>
|
<div className={`${imgLoaded ? "visible" : "invisible"}`}>
|
||||||
<img
|
<img
|
||||||
ref={imgRef}
|
ref={imgRef}
|
||||||
className={`size-full transition-opacity ${
|
className={`size-full transition-opacity select-none ${
|
||||||
playingBack ? "opacity-0" : "opacity-100"
|
playingBack ? "opacity-0" : "opacity-100"
|
||||||
}`}
|
}`}
|
||||||
|
style={
|
||||||
|
isIOS
|
||||||
|
? {
|
||||||
|
WebkitUserSelect: "none",
|
||||||
|
WebkitTouchCallout: "none",
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
draggable={false}
|
||||||
src={`${apiHost}${review.thumb_path.replace("/media/frigate/", "")}`}
|
src={`${apiHost}${review.thumb_path.replace("/media/frigate/", "")}`}
|
||||||
loading={isSafari ? "eager" : "lazy"}
|
loading={isSafari ? "eager" : "lazy"}
|
||||||
onLoad={() => {
|
onLoad={() => {
|
||||||
|
46
web/src/hooks/use-contextmenu.ts
Normal file
46
web/src/hooks/use-contextmenu.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { MutableRefObject, useEffect } from "react";
|
||||||
|
import { isIOS } from "react-device-detect";
|
||||||
|
|
||||||
|
export default function useContextMenu(
|
||||||
|
ref: MutableRefObject<HTMLDivElement | null>,
|
||||||
|
callback: () => void,
|
||||||
|
) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ref.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const elem = ref.current;
|
||||||
|
|
||||||
|
if (isIOS) {
|
||||||
|
let timeoutId: NodeJS.Timeout;
|
||||||
|
const touchStart = () => {
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
callback();
|
||||||
|
}, 610);
|
||||||
|
};
|
||||||
|
const touchClear = () => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
};
|
||||||
|
elem.addEventListener("touchstart", touchStart);
|
||||||
|
elem.addEventListener("touchmove", touchClear);
|
||||||
|
elem.addEventListener("touchend", touchClear);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
elem.removeEventListener("touchstart", touchStart);
|
||||||
|
elem.removeEventListener("touchmove", touchClear);
|
||||||
|
elem.removeEventListener("touchend", touchClear);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const context = (e: MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
callback();
|
||||||
|
};
|
||||||
|
elem.addEventListener("contextmenu", context);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
elem.removeEventListener("contextmenu", context);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [callback, ref]);
|
||||||
|
}
|
@ -19,7 +19,7 @@ export default function CameraMetrics({
|
|||||||
// stats
|
// stats
|
||||||
|
|
||||||
const { data: initialStats } = useSWR<FrigateStats[]>(
|
const { data: initialStats } = useSWR<FrigateStats[]>(
|
||||||
["stats/history", { keys: "cpu_usages,cameras,service" }],
|
["stats/history", { keys: "cpu_usages,cameras,detection_fps,service" }],
|
||||||
{
|
{
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
},
|
},
|
||||||
@ -57,6 +57,44 @@ export default function CameraMetrics({
|
|||||||
|
|
||||||
// stats data
|
// stats data
|
||||||
|
|
||||||
|
const overallFpsSeries = useMemo(() => {
|
||||||
|
if (!statsHistory) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const series: {
|
||||||
|
[key: string]: { name: string; data: { x: number; y: number }[] };
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
series["overall_dps"] = { name: "overall detections per second", data: [] };
|
||||||
|
series["overall_skipped_dps"] = {
|
||||||
|
name: "overall skipped detections per second",
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
statsHistory.forEach((stats, statsIdx) => {
|
||||||
|
if (!stats) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
series["overall_dps"].data.push({
|
||||||
|
x: statsIdx,
|
||||||
|
y: stats.detection_fps,
|
||||||
|
});
|
||||||
|
|
||||||
|
let skipped = 0;
|
||||||
|
Object.values(stats.cameras).forEach(
|
||||||
|
(camStat) => (skipped += camStat.skipped_fps),
|
||||||
|
);
|
||||||
|
|
||||||
|
series["overall_skipped_dps"].data.push({
|
||||||
|
x: statsIdx,
|
||||||
|
y: skipped,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return Object.values(series);
|
||||||
|
}, [statsHistory]);
|
||||||
|
|
||||||
const cameraCpuSeries = useMemo(() => {
|
const cameraCpuSeries = useMemo(() => {
|
||||||
if (!statsHistory || statsHistory.length == 0) {
|
if (!statsHistory || statsHistory.length == 0) {
|
||||||
return {};
|
return {};
|
||||||
@ -147,19 +185,36 @@ export default function CameraMetrics({
|
|||||||
}, [statsHistory]);
|
}, [statsHistory]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="size-full mt-4 flex flex-col overflow-y-auto">
|
<div className="size-full mt-4 flex flex-col gap-3 overflow-y-auto">
|
||||||
|
<div className="text-muted-foreground text-sm font-medium">Overview</div>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3">
|
||||||
|
{statsHistory.length != 0 ? (
|
||||||
|
<div className="p-2.5 bg-background_alt rounded-2xl">
|
||||||
|
<div className="mb-5">DPS</div>
|
||||||
|
<CameraLineGraph
|
||||||
|
graphId="overall-stats"
|
||||||
|
unit=" DPS"
|
||||||
|
dataLabels={["detect", "skipped"]}
|
||||||
|
updateTimes={updateTimes}
|
||||||
|
data={overallFpsSeries}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Skeleton className="w-full h-32" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
{config &&
|
{config &&
|
||||||
Object.values(config.cameras).map((camera) => {
|
Object.values(config.cameras).map((camera) => {
|
||||||
if (camera.enabled) {
|
if (camera.enabled) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-col">
|
<div className="w-full flex flex-col gap-3">
|
||||||
<div className="mb-6 capitalize">
|
<div className="capitalize text-muted-foreground text-sm font-medium">
|
||||||
{camera.name.replaceAll("_", " ")}
|
{camera.name.replaceAll("_", " ")}
|
||||||
</div>
|
</div>
|
||||||
<div key={camera.name} className="grid sm:grid-cols-2 gap-2">
|
<div key={camera.name} className="grid sm:grid-cols-2 gap-2">
|
||||||
{Object.keys(cameraCpuSeries).includes(camera.name) ? (
|
{Object.keys(cameraCpuSeries).includes(camera.name) ? (
|
||||||
<div className="p-2.5 bg-background_alt rounded-2xl flex-col">
|
<div className="p-2.5 bg-background_alt rounded-2xl">
|
||||||
<div className="mb-5">CPU</div>
|
<div className="mb-5">CPU</div>
|
||||||
<CameraLineGraph
|
<CameraLineGraph
|
||||||
graphId={`${camera.name}-cpu`}
|
graphId={`${camera.name}-cpu`}
|
||||||
@ -175,7 +230,7 @@ export default function CameraMetrics({
|
|||||||
<Skeleton className="size-full aspect-video" />
|
<Skeleton className="size-full aspect-video" />
|
||||||
)}
|
)}
|
||||||
{Object.keys(cameraFpsSeries).includes(camera.name) ? (
|
{Object.keys(cameraFpsSeries).includes(camera.name) ? (
|
||||||
<div className="p-2.5 bg-background_alt rounded-2xl flex-col">
|
<div className="p-2.5 bg-background_alt rounded-2xl">
|
||||||
<div className="mb-5">DPS</div>
|
<div className="mb-5">DPS</div>
|
||||||
<CameraLineGraph
|
<CameraLineGraph
|
||||||
graphId={`${camera.name}-dps`}
|
graphId={`${camera.name}-dps`}
|
||||||
|
@ -193,6 +193,14 @@ export default function GeneralMetrics({
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
Object.keys(statsHistory?.at(0)?.gpu_usages ?? {}).length == 1 &&
|
||||||
|
Object.keys(statsHistory?.at(0)?.gpu_usages ?? {})[0].includes("intel")
|
||||||
|
) {
|
||||||
|
// intel gpu stats do not support memory
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const series: {
|
const series: {
|
||||||
[key: string]: { name: string; data: { x: number; y: string }[] };
|
[key: string]: { name: string; data: { x: number; y: string }[] };
|
||||||
} = {};
|
} = {};
|
||||||
@ -285,7 +293,7 @@ export default function GeneralMetrics({
|
|||||||
</div>
|
</div>
|
||||||
<div className="w-full mt-4 grid grid-cols-1 sm:grid-cols-3 gap-2">
|
<div className="w-full mt-4 grid grid-cols-1 sm:grid-cols-3 gap-2">
|
||||||
{statsHistory.length != 0 ? (
|
{statsHistory.length != 0 ? (
|
||||||
<div className="p-2.5 bg-background_alt rounded-2xl flex-col">
|
<div className="p-2.5 bg-background_alt rounded-2xl">
|
||||||
<div className="mb-5">Detector Inference Speed</div>
|
<div className="mb-5">Detector Inference Speed</div>
|
||||||
{detInferenceTimeSeries.map((series) => (
|
{detInferenceTimeSeries.map((series) => (
|
||||||
<ThresholdBarGraph
|
<ThresholdBarGraph
|
||||||
@ -303,7 +311,7 @@ export default function GeneralMetrics({
|
|||||||
<Skeleton className="w-full aspect-video" />
|
<Skeleton className="w-full aspect-video" />
|
||||||
)}
|
)}
|
||||||
{statsHistory.length != 0 ? (
|
{statsHistory.length != 0 ? (
|
||||||
<div className="p-2.5 bg-background_alt rounded-2xl flex-col">
|
<div className="p-2.5 bg-background_alt rounded-2xl">
|
||||||
<div className="mb-5">Detector CPU Usage</div>
|
<div className="mb-5">Detector CPU Usage</div>
|
||||||
{detCpuSeries.map((series) => (
|
{detCpuSeries.map((series) => (
|
||||||
<ThresholdBarGraph
|
<ThresholdBarGraph
|
||||||
@ -321,7 +329,7 @@ export default function GeneralMetrics({
|
|||||||
<Skeleton className="w-full aspect-video" />
|
<Skeleton className="w-full aspect-video" />
|
||||||
)}
|
)}
|
||||||
{statsHistory.length != 0 ? (
|
{statsHistory.length != 0 ? (
|
||||||
<div className="p-2.5 bg-background_alt rounded-2xl flex-col">
|
<div className="p-2.5 bg-background_alt rounded-2xl">
|
||||||
<div className="mb-5">Detector Memory Usage</div>
|
<div className="mb-5">Detector Memory Usage</div>
|
||||||
{detMemSeries.map((series) => (
|
{detMemSeries.map((series) => (
|
||||||
<ThresholdBarGraph
|
<ThresholdBarGraph
|
||||||
@ -358,7 +366,7 @@ export default function GeneralMetrics({
|
|||||||
</div>
|
</div>
|
||||||
<div className=" mt-4 grid grid-cols-1 sm:grid-cols-2 gap-2">
|
<div className=" mt-4 grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||||
{statsHistory.length != 0 ? (
|
{statsHistory.length != 0 ? (
|
||||||
<div className="p-2.5 bg-background_alt rounded-2xl flex-col">
|
<div className="p-2.5 bg-background_alt rounded-2xl">
|
||||||
<div className="mb-5">GPU Usage</div>
|
<div className="mb-5">GPU Usage</div>
|
||||||
{gpuSeries.map((series) => (
|
{gpuSeries.map((series) => (
|
||||||
<ThresholdBarGraph
|
<ThresholdBarGraph
|
||||||
@ -376,7 +384,9 @@ export default function GeneralMetrics({
|
|||||||
<Skeleton className="w-full aspect-video" />
|
<Skeleton className="w-full aspect-video" />
|
||||||
)}
|
)}
|
||||||
{statsHistory.length != 0 ? (
|
{statsHistory.length != 0 ? (
|
||||||
<div className="p-2.5 bg-background_alt rounded-2xl flex-col">
|
<>
|
||||||
|
{gpuMemSeries && (
|
||||||
|
<div className="p-2.5 bg-background_alt rounded-2xl">
|
||||||
<div className="mb-5">GPU Memory</div>
|
<div className="mb-5">GPU Memory</div>
|
||||||
{gpuMemSeries.map((series) => (
|
{gpuMemSeries.map((series) => (
|
||||||
<ThresholdBarGraph
|
<ThresholdBarGraph
|
||||||
@ -390,6 +400,8 @@ export default function GeneralMetrics({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Skeleton className="w-full aspect-video" />
|
<Skeleton className="w-full aspect-video" />
|
||||||
)}
|
)}
|
||||||
@ -402,7 +414,7 @@ export default function GeneralMetrics({
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-4 grid grid-cols-1 sm:grid-cols-2 gap-2">
|
<div className="mt-4 grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||||
{statsHistory.length != 0 ? (
|
{statsHistory.length != 0 ? (
|
||||||
<div className="p-2.5 bg-background_alt rounded-2xl flex-col">
|
<div className="p-2.5 bg-background_alt rounded-2xl">
|
||||||
<div className="mb-5">Process CPU Usage</div>
|
<div className="mb-5">Process CPU Usage</div>
|
||||||
{otherProcessCpuSeries.map((series) => (
|
{otherProcessCpuSeries.map((series) => (
|
||||||
<ThresholdBarGraph
|
<ThresholdBarGraph
|
||||||
@ -420,7 +432,7 @@ export default function GeneralMetrics({
|
|||||||
<Skeleton className="w-full aspect-tall" />
|
<Skeleton className="w-full aspect-tall" />
|
||||||
)}
|
)}
|
||||||
{statsHistory.length != 0 ? (
|
{statsHistory.length != 0 ? (
|
||||||
<div className="p-2.5 bg-background_alt rounded-2xl flex-col">
|
<div className="p-2.5 bg-background_alt rounded-2xl">
|
||||||
<div className="mb-5">Process Memory Usage</div>
|
<div className="mb-5">Process Memory Usage</div>
|
||||||
{otherProcessMemSeries.map((series) => (
|
{otherProcessMemSeries.map((series) => (
|
||||||
<ThresholdBarGraph
|
<ThresholdBarGraph
|
||||||
|
@ -43,9 +43,7 @@ export default function StorageMetrics({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="size-full mt-4 flex flex-col overflow-y-auto">
|
<div className="size-full mt-4 flex flex-col overflow-y-auto">
|
||||||
<div className="text-muted-foreground text-sm font-medium">
|
<div className="text-muted-foreground text-sm font-medium">Overview</div>
|
||||||
General Storage
|
|
||||||
</div>
|
|
||||||
<div className="mt-4 grid grid-cols-1 sm:grid-cols-3 gap-2">
|
<div className="mt-4 grid grid-cols-1 sm:grid-cols-3 gap-2">
|
||||||
<div className="p-2.5 bg-background_alt rounded-2xl flex-col">
|
<div className="p-2.5 bg-background_alt rounded-2xl flex-col">
|
||||||
<div className="mb-5">Recordings</div>
|
<div className="mb-5">Recordings</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user