forked from Github/frigate
FEAT: Storage API & Frontend (#3409)
* Get storage output stats for each camera * Add storage route * Add storage route * Add storage page * Cleanup * Add stats and show more storage * Add tests for mb abbrev util fun * Rewrite storage logic to use storage maintainer and segment sizes * Include storage maintainer for http * Use correct format * Remove utils * Fix tests * Remove total from equation * Multiply by 100 to get percent * Add basic storage info * Fix storage stats * Fix endpoint and ui * Fix formatting
This commit is contained in:
@@ -163,6 +163,7 @@ class FrigateApp:
|
||||
self.db,
|
||||
self.stats_tracking,
|
||||
self.detected_frames_processor,
|
||||
self.storage_maintainer,
|
||||
self.plus_api,
|
||||
)
|
||||
|
||||
@@ -362,13 +363,13 @@ class FrigateApp:
|
||||
self.start_detected_frames_processor()
|
||||
self.start_camera_processors()
|
||||
self.start_camera_capture_processes()
|
||||
self.start_storage_maintainer()
|
||||
self.init_stats()
|
||||
self.init_web_server()
|
||||
self.start_event_processor()
|
||||
self.start_event_cleanup()
|
||||
self.start_recording_maintainer()
|
||||
self.start_recording_cleanup()
|
||||
self.start_storage_maintainer()
|
||||
self.start_stats_emitter()
|
||||
self.start_watchdog()
|
||||
# self.zeroconf = broadcast_zeroconf(self.config.mqtt.client_id)
|
||||
|
||||
@@ -27,12 +27,12 @@ from flask import (
|
||||
from peewee import SqliteDatabase, operator, fn, DoesNotExist
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
from frigate.config import CameraConfig
|
||||
from frigate.const import CLIPS_DIR
|
||||
from frigate.const import CLIPS_DIR, RECORD_DIR
|
||||
from frigate.models import Event, Recordings
|
||||
from frigate.object_processing import TrackedObject
|
||||
from frigate.stats import stats_snapshot
|
||||
from frigate.util import clean_camera_user_pass, ffprobe_stream, vainfo_hwaccel
|
||||
from frigate.storage import StorageMaintainer
|
||||
from frigate.version import VERSION
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -45,6 +45,7 @@ def create_app(
|
||||
database: SqliteDatabase,
|
||||
stats_tracking,
|
||||
detected_frames_processor,
|
||||
storage_maintainer: StorageMaintainer,
|
||||
plus_api,
|
||||
):
|
||||
app = Flask(__name__)
|
||||
@@ -62,6 +63,7 @@ def create_app(
|
||||
app.frigate_config = frigate_config
|
||||
app.stats_tracking = stats_tracking
|
||||
app.detected_frames_processor = detected_frames_processor
|
||||
app.storage_maintainer = storage_maintainer
|
||||
app.plus_api = plus_api
|
||||
app.camera_error_image = None
|
||||
|
||||
@@ -690,6 +692,26 @@ def latest_frame(camera_name):
|
||||
return "Camera named {} not found".format(camera_name), 404
|
||||
|
||||
|
||||
@bp.route("/recordings/storage", methods=["GET"])
|
||||
def get_recordings_storage_usage():
|
||||
recording_stats = stats_snapshot(
|
||||
current_app.frigate_config, current_app.stats_tracking
|
||||
)["service"]["storage"][RECORD_DIR]
|
||||
total_mb = recording_stats["total"]
|
||||
|
||||
camera_usages: dict[
|
||||
str, dict
|
||||
] = current_app.storage_maintainer.calculate_camera_usages()
|
||||
|
||||
for camera_name in camera_usages.keys():
|
||||
if camera_usages.get(camera_name, {}).get("usage"):
|
||||
camera_usages[camera_name]["usage_percent"] = (
|
||||
camera_usages.get(camera_name, {}).get("usage", 0) / total_mb
|
||||
) * 100
|
||||
|
||||
return jsonify(camera_usages)
|
||||
|
||||
|
||||
# return hourly summary for recordings of camera
|
||||
@bp.route("/<camera_name>/recordings/summary")
|
||||
def recordings_summary(camera_name):
|
||||
|
||||
@@ -60,6 +60,26 @@ class StorageMaintainer(threading.Thread):
|
||||
self.camera_storage_stats[camera]["bandwidth"] = bandwidth
|
||||
logger.debug(f"{camera} has a bandwidth of {bandwidth} MB/hr.")
|
||||
|
||||
def calculate_camera_usages(self) -> dict[str, dict]:
|
||||
"""Calculate the storage usage of each camera."""
|
||||
usages: dict[str, dict] = {}
|
||||
|
||||
for camera in self.config.cameras.keys():
|
||||
camera_storage = (
|
||||
Recordings.select(fn.SUM(Recordings.segment_size))
|
||||
.where(Recordings.camera == camera, Recordings.segment_size != 0)
|
||||
.scalar()
|
||||
)
|
||||
|
||||
usages[camera] = {
|
||||
"usage": camera_storage,
|
||||
"bandwidth": self.camera_storage_stats.get(camera, {}).get(
|
||||
"bandwidth", 0
|
||||
),
|
||||
}
|
||||
|
||||
return usages
|
||||
|
||||
def check_storage_needs_cleanup(self) -> bool:
|
||||
"""Return if storage needs cleanup."""
|
||||
# currently runs cleanup if less than 1 hour of space is left
|
||||
|
||||
@@ -114,7 +114,7 @@ class TestHttp(unittest.TestCase):
|
||||
|
||||
def test_get_event_list(self):
|
||||
app = create_app(
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, None, PlusApi()
|
||||
)
|
||||
id = "123456.random"
|
||||
id2 = "7890.random"
|
||||
@@ -143,7 +143,7 @@ class TestHttp(unittest.TestCase):
|
||||
|
||||
def test_get_good_event(self):
|
||||
app = create_app(
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, None, PlusApi()
|
||||
)
|
||||
id = "123456.random"
|
||||
|
||||
@@ -157,7 +157,7 @@ class TestHttp(unittest.TestCase):
|
||||
|
||||
def test_get_bad_event(self):
|
||||
app = create_app(
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, None, PlusApi()
|
||||
)
|
||||
id = "123456.random"
|
||||
bad_id = "654321.other"
|
||||
@@ -170,7 +170,7 @@ class TestHttp(unittest.TestCase):
|
||||
|
||||
def test_delete_event(self):
|
||||
app = create_app(
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, None, PlusApi()
|
||||
)
|
||||
id = "123456.random"
|
||||
|
||||
@@ -185,7 +185,7 @@ class TestHttp(unittest.TestCase):
|
||||
|
||||
def test_event_retention(self):
|
||||
app = create_app(
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, None, PlusApi()
|
||||
)
|
||||
id = "123456.random"
|
||||
|
||||
@@ -204,7 +204,7 @@ class TestHttp(unittest.TestCase):
|
||||
|
||||
def test_set_delete_sub_label(self):
|
||||
app = create_app(
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, None, PlusApi()
|
||||
)
|
||||
id = "123456.random"
|
||||
sub_label = "sub"
|
||||
@@ -232,7 +232,7 @@ class TestHttp(unittest.TestCase):
|
||||
|
||||
def test_sub_label_list(self):
|
||||
app = create_app(
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, PlusApi()
|
||||
FrigateConfig(**self.minimal_config), self.db, None, None, None, PlusApi()
|
||||
)
|
||||
id = "123456.random"
|
||||
sub_label = "sub"
|
||||
@@ -254,6 +254,7 @@ class TestHttp(unittest.TestCase):
|
||||
self.db,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
)
|
||||
|
||||
@@ -268,6 +269,7 @@ class TestHttp(unittest.TestCase):
|
||||
self.db,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
)
|
||||
id = "123456.random"
|
||||
@@ -285,6 +287,7 @@ class TestHttp(unittest.TestCase):
|
||||
self.db,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
PlusApi(),
|
||||
)
|
||||
mock_stats.return_value = self.test_stats
|
||||
|
||||
Reference in New Issue
Block a user