forked from Github/frigate
Revamped debug UI and add camera / process info, ffprobe copying (#4349)
* Move each camera to a separate card and show per process info * Install top * Add support for cpu usage stats * Use cpu usage stats in debug * Increase number of runs to ensure good results * Add ffprobe endpoint * Get ffprobe for multiple inputs * Copy ffprobe in output * Add fps to camera metrics * Fix lint errors * Update stats config * Add ffmpeg pid * Use grid display so more cameras can take less vertical space * Fix hanging characters * Only show the current detector * Fix bad if statement * Return full output of ffprobe process * Return full output of ffprobe process * Don't specify rtsp_transport * Make ffprobe button show dialog with output and option to copy * Adjust ffprobe api to take paths directly * Add docs for ffprobe api
This commit is contained in:
@@ -39,9 +39,10 @@ export const handlers = [
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
cpu_usages: { 74: {cpu: 6, mem: 6}, 64: { cpu: 5, mem: 5 }, 54: { cpu: 4, mem: 4 }, 71: { cpu: 3, mem: 3}, 60: {cpu: 2, mem: 2}, 72: {cpu: 1, mem: 1} },
|
||||
detection_fps: 0.0,
|
||||
detectors: { coral: { detection_start: 0.0, inference_speed: 8.94, pid: 52 } },
|
||||
front: { camera_fps: 5.0, capture_pid: 64, detection_fps: 0.0, pid: 54, process_fps: 0.0, skipped_fps: 0.0 },
|
||||
front: { camera_fps: 5.0, capture_pid: 64, detection_fps: 0.0, pid: 54, process_fps: 0.0, skipped_fps: 0.0, ffmpeg_pid: 72 },
|
||||
side: {
|
||||
camera_fps: 6.9,
|
||||
capture_pid: 71,
|
||||
@@ -49,6 +50,7 @@ export const handlers = [
|
||||
pid: 60,
|
||||
process_fps: 0.0,
|
||||
skipped_fps: 0.0,
|
||||
ffmpeg_pid: 74,
|
||||
},
|
||||
service: { uptime: 34812, version: '0.8.1-d376f6b' },
|
||||
})
|
||||
|
||||
@@ -5,12 +5,15 @@ import Heading from '../components/Heading';
|
||||
import Link from '../components/Link';
|
||||
import { useMqtt } from '../api/mqtt';
|
||||
import useSWR from 'swr';
|
||||
import axios from 'axios';
|
||||
import { Table, Tbody, Thead, Tr, Th, Td } from '../components/Table';
|
||||
import { useCallback } from 'preact/hooks';
|
||||
import { useCallback, useState } from 'preact/hooks';
|
||||
import Dialog from '../components/Dialog';
|
||||
|
||||
const emptyObject = Object.freeze({});
|
||||
|
||||
export default function Debug() {
|
||||
const [state, setState] = useState({ showFfprobe: false, ffprobe: '' });
|
||||
const { data: config } = useSWR('config');
|
||||
|
||||
const {
|
||||
@@ -18,12 +21,10 @@ export default function Debug() {
|
||||
} = useMqtt('stats');
|
||||
const { data: initialStats } = useSWR('stats');
|
||||
|
||||
const { detectors, service = {}, detection_fps: _, ...cameras } = stats || initialStats || emptyObject;
|
||||
const { cpu_usages, detectors, service = {}, detection_fps: _, ...cameras } = stats || initialStats || emptyObject;
|
||||
|
||||
const detectorNames = Object.keys(detectors || emptyObject);
|
||||
const detectorDataKeys = Object.keys(detectors ? detectors[detectorNames[0]] : emptyObject);
|
||||
const cameraNames = Object.keys(cameras || emptyObject);
|
||||
const cameraDataKeys = Object.keys(cameras[cameraNames[0]] || emptyObject);
|
||||
|
||||
const handleCopyConfig = useCallback(() => {
|
||||
async function copy() {
|
||||
@@ -32,11 +33,64 @@ export default function Debug() {
|
||||
copy();
|
||||
}, [config]);
|
||||
|
||||
const onHandleFfprobe = async (camera, e) => {
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
setState({ ...state, showFfprobe: true });
|
||||
let paths = '';
|
||||
config.cameras[camera].ffmpeg.inputs.forEach((input) => {
|
||||
if (paths) {
|
||||
paths += ',';
|
||||
paths += input.path;
|
||||
} else {
|
||||
paths = input.path;
|
||||
}
|
||||
});
|
||||
const response = await axios.get('ffprobe', {
|
||||
params: {
|
||||
paths,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
setState({ showFfprobe: true, ffprobe: JSON.stringify(response.data, null, 2) });
|
||||
} else {
|
||||
setState({ ...state, ffprobe: 'There was an error getting the ffprobe output.' });
|
||||
}
|
||||
};
|
||||
|
||||
const onCopyFfprobe = async () => {
|
||||
await window.navigator.clipboard.writeText(JSON.stringify(state.ffprobe, null, 2));
|
||||
setState({ ...state, ffprobe: '', showFfprobe: false });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-2 px-4">
|
||||
<Heading>
|
||||
Debug <span className="text-sm">{service.version}</span>
|
||||
</Heading>
|
||||
{state.showFfprobe && (
|
||||
<Dialog>
|
||||
<div className="p-4">
|
||||
<Heading size="lg">Ffprobe Output</Heading>
|
||||
{state.ffprobe != '' ? <p className="mb-2">{state.ffprobe}</p> : <ActivityIndicator />}
|
||||
</div>
|
||||
<div className="p-2 flex justify-start flex-row-reverse space-x-2">
|
||||
<Button className="ml-2" onClick={() => onCopyFfprobe()} type="text">
|
||||
Copy
|
||||
</Button>
|
||||
<Button
|
||||
className="ml-2"
|
||||
onClick={() => setState({ ...state, ffprobe: '', showFfprobe: false })}
|
||||
type="text"
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog>
|
||||
)}
|
||||
|
||||
{!detectors ? (
|
||||
<div>
|
||||
@@ -44,52 +98,81 @@ export default function Debug() {
|
||||
</div>
|
||||
) : (
|
||||
<Fragment>
|
||||
<div data-testid="detectors" className="min-w-0 overflow-auto">
|
||||
<Table className="w-full">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>detector</Th>
|
||||
{detectorDataKeys.map((name) => (
|
||||
<Th key={name}>{name.replace('_', ' ')}</Th>
|
||||
))}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{detectorNames.map((detector, i) => (
|
||||
<Tr key={i} index={i}>
|
||||
<Td>{detector}</Td>
|
||||
{detectorDataKeys.map((name) => (
|
||||
<Td key={`${name}-${detector}`}>{detectors[detector][name]}</Td>
|
||||
))}
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
<Heading size="lg">Detectors</Heading>
|
||||
<div data-testid="detectors" className="grid grid-cols-1 3xl:grid-cols-3 md:grid-cols-2 gap-4">
|
||||
{detectorNames.map((detector) => (
|
||||
<div key={detector} className="dark:bg-gray-800 shadow-md hover:shadow-lg rounded-lg transition-shadow">
|
||||
<div className="text-lg flex justify-between p-4">{detector}</div>
|
||||
<div className="p-2">
|
||||
<Table className="w-full">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>P-ID</Th>
|
||||
<Th>Detection Start</Th>
|
||||
<Th>Inference Speed</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
<Tr>
|
||||
<Td>{detectors[detector]['pid']}</Td>
|
||||
<Td>{detectors[detector]['detection_start']}</Td>
|
||||
<Td>{detectors[detector]['inference_speed']}</Td>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div data-testid="cameras" className="min-w-0 overflow-auto">
|
||||
<Table className="w-full">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>camera</Th>
|
||||
{cameraDataKeys.map((name) => (
|
||||
<Th key={name}>{name.replace('_', ' ')}</Th>
|
||||
))}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{cameraNames.map((camera, i) => (
|
||||
<Tr key={i} index={i}>
|
||||
<Td>
|
||||
<Link href={`/cameras/${camera}`}>{camera.replaceAll('_', ' ')}</Link>
|
||||
</Td>
|
||||
{cameraDataKeys.map((name) => (
|
||||
<Td key={`${name}-${camera}`}>{cameras[camera][name]}</Td>
|
||||
))}
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
<Heading size="lg">Cameras</Heading>
|
||||
<div data-testid="cameras" className="grid grid-cols-1 3xl:grid-cols-3 md:grid-cols-2 gap-4">
|
||||
{cameraNames.map((camera) => (
|
||||
<div key={camera} className="dark:bg-gray-800 shadow-md hover:shadow-lg rounded-lg transition-shadow">
|
||||
<div className="text-lg flex justify-between p-4">
|
||||
<Link href={`/cameras/${camera}`}>{camera.replaceAll('_', ' ')}</Link>
|
||||
<Button onClick={(e) => onHandleFfprobe(camera, e)}>ffprobe</Button>
|
||||
</div>
|
||||
<div className="p-2">
|
||||
<Table className="w-full">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>Process</Th>
|
||||
<Th>P-ID</Th>
|
||||
<Th>fps</Th>
|
||||
<Th>Cpu %</Th>
|
||||
<Th>Memory %</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
<Tr key="capture" index="0">
|
||||
<Td>Capture</Td>
|
||||
<Td>{cameras[camera]['capture_pid']}</Td>
|
||||
<Td>{cameras[camera]['process_fps']}</Td>
|
||||
<Td>{cpu_usages[cameras[camera]['capture_pid']]['cpu']}%</Td>
|
||||
<Td>{cpu_usages[cameras[camera]['capture_pid']]['mem']}%</Td>
|
||||
</Tr>
|
||||
<Tr key="detect" index="1">
|
||||
<Td>Detect</Td>
|
||||
<Td>{cameras[camera]['pid']}</Td>
|
||||
<Td>
|
||||
{cameras[camera]['detection_fps']} ({cameras[camera]['skipped_fps']} skipped)
|
||||
</Td>
|
||||
<Td>{cpu_usages[cameras[camera]['pid']]['cpu']}%</Td>
|
||||
<Td>{cpu_usages[cameras[camera]['pid']]['cpu']}%</Td>
|
||||
</Tr>
|
||||
<Tr key="ffmpeg" index="2">
|
||||
<Td>ffmpeg</Td>
|
||||
<Td>{cameras[camera]['ffmpeg_pid']}</Td>
|
||||
<Td>{cameras[camera]['camera_fps']}</Td>
|
||||
<Td>{cpu_usages[cameras[camera]['ffmpeg_pid']]['cpu']}%</Td>
|
||||
<Td>{cpu_usages[cameras[camera]['ffmpeg_pid']]['cpu']}%</Td>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p>Debug stats update automatically every {config.mqtt.stats_interval} seconds.</p>
|
||||
|
||||
Reference in New Issue
Block a user