mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
parent
509e46adc8
commit
33c77d03c7
@ -57,7 +57,8 @@ from frigate.ptz.onvif import OnvifController
|
||||
from frigate.record.cleanup import RecordingCleanup
|
||||
from frigate.record.record import manage_recordings
|
||||
from frigate.review.review import manage_review_segments
|
||||
from frigate.stats import StatsEmitter, stats_init
|
||||
from frigate.stats.emitter import StatsEmitter
|
||||
from frigate.stats.util import stats_init
|
||||
from frigate.storage import StorageMaintainer
|
||||
from frigate.timeline import TimelineProcessor
|
||||
from frigate.types import CameraMetricsTypes, PTZMetricsTypes
|
||||
@ -322,11 +323,6 @@ class FrigateApp:
|
||||
]
|
||||
self.db.bind(models)
|
||||
|
||||
def init_stats(self) -> None:
|
||||
self.stats_tracking = stats_init(
|
||||
self.config, self.camera_metrics, self.detectors, self.processes
|
||||
)
|
||||
|
||||
def init_external_event_processor(self) -> None:
|
||||
self.external_event_processor = ExternalEventProcessor(
|
||||
self.config, self.event_queue
|
||||
@ -341,12 +337,12 @@ class FrigateApp:
|
||||
self.flask_app = create_app(
|
||||
self.config,
|
||||
self.db,
|
||||
self.stats_tracking,
|
||||
self.detected_frames_processor,
|
||||
self.storage_maintainer,
|
||||
self.onvif_controller,
|
||||
self.external_event_processor,
|
||||
self.plus_api,
|
||||
self.stats_emitter,
|
||||
)
|
||||
|
||||
def init_onvif(self) -> None:
|
||||
@ -542,8 +538,9 @@ class FrigateApp:
|
||||
def start_stats_emitter(self) -> None:
|
||||
self.stats_emitter = StatsEmitter(
|
||||
self.config,
|
||||
self.stats_tracking,
|
||||
self.dispatcher,
|
||||
stats_init(
|
||||
self.config, self.camera_metrics, self.detectors, self.processes
|
||||
),
|
||||
self.stop_event,
|
||||
)
|
||||
self.stats_emitter.start()
|
||||
@ -648,14 +645,13 @@ class FrigateApp:
|
||||
self.start_camera_capture_processes()
|
||||
self.start_audio_processors()
|
||||
self.start_storage_maintainer()
|
||||
self.init_stats()
|
||||
self.init_external_event_processor()
|
||||
self.start_stats_emitter()
|
||||
self.init_web_server()
|
||||
self.start_timeline_processor()
|
||||
self.start_event_processor()
|
||||
self.start_event_cleanup()
|
||||
self.start_record_cleanup()
|
||||
self.start_stats_emitter()
|
||||
self.start_watchdog()
|
||||
self.check_shm()
|
||||
|
||||
|
@ -50,7 +50,7 @@ from frigate.object_processing import TrackedObject
|
||||
from frigate.plus import PlusApi
|
||||
from frigate.ptz.onvif import OnvifController
|
||||
from frigate.record.export import PlaybackFactorEnum, RecordingExporter
|
||||
from frigate.stats import stats_snapshot
|
||||
from frigate.stats.emitter import StatsEmitter
|
||||
from frigate.storage import StorageMaintainer
|
||||
from frigate.util.builtin import (
|
||||
clean_camera_user_pass,
|
||||
@ -70,12 +70,12 @@ bp = Blueprint("frigate", __name__)
|
||||
def create_app(
|
||||
frigate_config,
|
||||
database: SqliteQueueDatabase,
|
||||
stats_tracking,
|
||||
detected_frames_processor,
|
||||
storage_maintainer: StorageMaintainer,
|
||||
onvif: OnvifController,
|
||||
external_processor: ExternalEventProcessor,
|
||||
plus_api: PlusApi,
|
||||
stats_emitter: StatsEmitter,
|
||||
):
|
||||
app = Flask(__name__)
|
||||
|
||||
@ -97,14 +97,13 @@ def create_app(
|
||||
database.close()
|
||||
|
||||
app.frigate_config = frigate_config
|
||||
app.stats_tracking = stats_tracking
|
||||
app.detected_frames_processor = detected_frames_processor
|
||||
app.storage_maintainer = storage_maintainer
|
||||
app.onvif = onvif
|
||||
app.external_processor = external_processor
|
||||
app.plus_api = plus_api
|
||||
app.camera_error_image = None
|
||||
app.hwaccel_errors = []
|
||||
app.stats_emitter = stats_emitter
|
||||
|
||||
app.register_blueprint(bp)
|
||||
|
||||
@ -1739,12 +1738,12 @@ def version():
|
||||
|
||||
@bp.route("/stats")
|
||||
def stats():
|
||||
stats = stats_snapshot(
|
||||
current_app.frigate_config,
|
||||
current_app.stats_tracking,
|
||||
current_app.hwaccel_errors,
|
||||
)
|
||||
return jsonify(stats)
|
||||
return jsonify(current_app.stats_emitter.get_latest_stats())
|
||||
|
||||
|
||||
@bp.route("/stats/history")
|
||||
def stats_history():
|
||||
return jsonify(current_app.stats_emitter.get_stats_history())
|
||||
|
||||
|
||||
@bp.route("/<camera_name>")
|
||||
@ -1941,11 +1940,9 @@ def get_snapshot_from_recording(camera_name: str, frame_time: str):
|
||||
|
||||
@bp.route("/recordings/storage", methods=["GET"])
|
||||
def get_recordings_storage_usage():
|
||||
recording_stats = stats_snapshot(
|
||||
current_app.frigate_config,
|
||||
current_app.stats_tracking,
|
||||
current_app.hwaccel_errors,
|
||||
)["service"]["storage"][RECORD_DIR]
|
||||
recording_stats = current_app.stats_emitter.get_latest_stats()["service"][
|
||||
"storage"
|
||||
][RECORD_DIR]
|
||||
|
||||
if not recording_stats:
|
||||
return jsonify({})
|
||||
|
0
frigate/stats/__init__.py
Normal file
0
frigate/stats/__init__.py
Normal file
61
frigate/stats/emitter.py
Normal file
61
frigate/stats/emitter.py
Normal file
@ -0,0 +1,61 @@
|
||||
"""Emit stats to listeners."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
from multiprocessing.synchronize import Event as MpEvent
|
||||
|
||||
from frigate.comms.inter_process import InterProcessRequestor
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.stats.util import stats_snapshot
|
||||
from frigate.types import StatsTrackingTypes
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StatsEmitter(threading.Thread):
|
||||
def __init__(
|
||||
self,
|
||||
config: FrigateConfig,
|
||||
stats_tracking: StatsTrackingTypes,
|
||||
stop_event: MpEvent,
|
||||
):
|
||||
threading.Thread.__init__(self)
|
||||
self.name = "frigate_stats_emitter"
|
||||
self.config = config
|
||||
self.stats_tracking = stats_tracking
|
||||
self.stop_event = stop_event
|
||||
self.hwaccel_errors: list[str] = []
|
||||
self.stats_history: list[dict[str, any]] = []
|
||||
|
||||
# create communication for stats
|
||||
self.requestor = InterProcessRequestor()
|
||||
|
||||
def get_latest_stats(self) -> dict[str, any]:
|
||||
"""Get latest stats."""
|
||||
if len(self.stats_history) > 0:
|
||||
return self.stats_history[-1]
|
||||
else:
|
||||
stats = stats_snapshot(
|
||||
self.config, self.stats_tracking, self.hwaccel_errors
|
||||
)
|
||||
self.stats_history.append(stats)
|
||||
return stats
|
||||
|
||||
def get_stats_history(self) -> list[dict[str, any]]:
|
||||
"""Get stats history."""
|
||||
return self.stats_history
|
||||
|
||||
def run(self) -> None:
|
||||
time.sleep(10)
|
||||
while not self.stop_event.wait(self.config.mqtt.stats_interval):
|
||||
logger.debug("Starting stats collection")
|
||||
stats = stats_snapshot(
|
||||
self.config, self.stats_tracking, self.hwaccel_errors
|
||||
)
|
||||
self.stats_history.append(stats)
|
||||
self.stats_history = self.stats_history[-10:]
|
||||
self.requestor.send_data("stats", json.dumps(stats))
|
||||
logger.debug("Finished stats collection")
|
||||
logger.info("Exiting stats emitter...")
|
@ -1,18 +1,15 @@
|
||||
"""Utilities for stats."""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import threading
|
||||
import time
|
||||
from multiprocessing.synchronize import Event as MpEvent
|
||||
from typing import Any, Optional
|
||||
|
||||
import psutil
|
||||
import requests
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from frigate.comms.dispatcher import Dispatcher
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.const import CACHE_DIR, CLIPS_DIR, RECORD_DIR
|
||||
from frigate.object_detection import ObjectDetectProcess
|
||||
@ -28,8 +25,6 @@ from frigate.util.services import (
|
||||
)
|
||||
from frigate.version import VERSION
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_latest_version(config: FrigateConfig) -> str:
|
||||
if not config.telemetry.version_check:
|
||||
@ -318,31 +313,3 @@ def stats_snapshot(
|
||||
}
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
class StatsEmitter(threading.Thread):
|
||||
def __init__(
|
||||
self,
|
||||
config: FrigateConfig,
|
||||
stats_tracking: StatsTrackingTypes,
|
||||
dispatcher: Dispatcher,
|
||||
stop_event: MpEvent,
|
||||
):
|
||||
threading.Thread.__init__(self)
|
||||
self.name = "frigate_stats_emitter"
|
||||
self.config = config
|
||||
self.stats_tracking = stats_tracking
|
||||
self.dispatcher = dispatcher
|
||||
self.stop_event = stop_event
|
||||
self.hwaccel_errors: list[str] = []
|
||||
|
||||
def run(self) -> None:
|
||||
time.sleep(10)
|
||||
while not self.stop_event.wait(self.config.mqtt.stats_interval):
|
||||
logger.debug("Starting stats collection")
|
||||
stats = stats_snapshot(
|
||||
self.config, self.stats_tracking, self.hwaccel_errors
|
||||
)
|
||||
self.dispatcher.publish("stats", json.dumps(stats), retain=False)
|
||||
logger.debug("Finished stats collection")
|
||||
logger.info("Exiting stats emitter...")
|
@ -3,7 +3,7 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import Mock
|
||||
|
||||
from peewee_migrate import Router
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
@ -14,6 +14,7 @@ from frigate.config import FrigateConfig
|
||||
from frigate.http import create_app
|
||||
from frigate.models import Event, Recordings
|
||||
from frigate.plus import PlusApi
|
||||
from frigate.stats.emitter import StatsEmitter
|
||||
from frigate.test.const import TEST_DB, TEST_DB_CLEANUPS
|
||||
|
||||
|
||||
@ -119,8 +120,8 @@ class TestHttp(unittest.TestCase):
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
None,
|
||||
)
|
||||
id = "123456.random"
|
||||
id2 = "7890.random"
|
||||
@ -155,8 +156,8 @@ class TestHttp(unittest.TestCase):
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
None,
|
||||
)
|
||||
id = "123456.random"
|
||||
|
||||
@ -176,8 +177,8 @@ class TestHttp(unittest.TestCase):
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
None,
|
||||
)
|
||||
id = "123456.random"
|
||||
bad_id = "654321.other"
|
||||
@ -196,8 +197,8 @@ class TestHttp(unittest.TestCase):
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
None,
|
||||
)
|
||||
id = "123456.random"
|
||||
|
||||
@ -218,8 +219,8 @@ class TestHttp(unittest.TestCase):
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
None,
|
||||
)
|
||||
id = "123456.random"
|
||||
|
||||
@ -244,8 +245,8 @@ class TestHttp(unittest.TestCase):
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
None,
|
||||
)
|
||||
morning_id = "123456.random"
|
||||
evening_id = "654321.random"
|
||||
@ -282,8 +283,8 @@ class TestHttp(unittest.TestCase):
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
None,
|
||||
)
|
||||
id = "123456.random"
|
||||
sub_label = "sub"
|
||||
@ -317,8 +318,8 @@ class TestHttp(unittest.TestCase):
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
None,
|
||||
)
|
||||
id = "123456.random"
|
||||
sub_label = "sub"
|
||||
@ -342,8 +343,8 @@ class TestHttp(unittest.TestCase):
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
None,
|
||||
)
|
||||
|
||||
with app.test_client() as client:
|
||||
@ -359,8 +360,8 @@ class TestHttp(unittest.TestCase):
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
None,
|
||||
)
|
||||
id = "123456.random"
|
||||
|
||||
@ -370,8 +371,9 @@ class TestHttp(unittest.TestCase):
|
||||
assert recording
|
||||
assert recording[0]["id"] == id
|
||||
|
||||
@patch("frigate.http.stats_snapshot")
|
||||
def test_stats(self, mock_stats):
|
||||
def test_stats(self):
|
||||
stats = Mock(spec=StatsEmitter)
|
||||
stats.get_latest_stats.return_value = self.test_stats
|
||||
app = create_app(
|
||||
FrigateConfig(**self.minimal_config).runtime_config(),
|
||||
self.db,
|
||||
@ -379,14 +381,13 @@ class TestHttp(unittest.TestCase):
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
stats,
|
||||
)
|
||||
mock_stats.return_value = self.test_stats
|
||||
|
||||
with app.test_client() as client:
|
||||
stats = client.get("/stats").json
|
||||
assert stats == self.test_stats
|
||||
full_stats = client.get("/stats").json
|
||||
assert full_stats == self.test_stats
|
||||
|
||||
|
||||
def _insert_mock_event(
|
||||
|
Loading…
Reference in New Issue
Block a user