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