From 043d7654f9238776e725a9ace8267669e7aa265a Mon Sep 17 00:00:00 2001 From: Daniel Dolezal Date: Sat, 4 Apr 2026 22:14:14 +0200 Subject: [PATCH] rename Metric Classes and change Folder Structure --- server/Server.py | 12 ++++--- server/metrics/MetricsCollector.py | 4 +-- server/metrics/__init__.py | 10 ++++-- .../Memory.py} | 7 ++-- .../Redis.py} | 10 +++--- .../Template.py} | 32 ++++++++++--------- tests/test_MetricsManager.py | 13 +++++--- 7 files changed, 52 insertions(+), 36 deletions(-) rename server/metrics/{MemoryMetricsStore.py => backends/Memory.py} (63%) rename server/metrics/{RedisMetricsStore.py => backends/Redis.py} (88%) rename server/metrics/{MetricsManager.py => backends/Template.py} (86%) diff --git a/server/Server.py b/server/Server.py index eb6d640..e8c7909 100644 --- a/server/Server.py +++ b/server/Server.py @@ -1,16 +1,18 @@ +from quart_common.web.logger import build_logger, await_log from server.Files import read_file from server.game_board_stats import GameBoardStoreBuilder from server.GameBoard import GameBoard from snakes import SnakeBuilder -from quart_common.web.logger import await_log -from quart_common.web.logger import build_logger -from server.metrics.MetricsManager import MetricsManager -from server.metrics.MetricsCollector import MetricsCollector from server.storage.StorageLoader import StorageLoader +from server.metrics import ( + MetricsStoreBuilder, + MetricsCollector, +) + from quart import Quart, request, jsonify import logging, json, os, re, time from typing import cast @@ -50,7 +52,7 @@ class Server: self.game_last_seen_unix:dict[str, int] = {} self.metrics_collector = MetricsCollector( - metrics_manager=MetricsManager( + metrics_manager=MetricsStoreBuilder.build( backend=metrics_backend_normalized, redis_url=metrics_redis_url, ttl_seconds=metrics_ttl_sec, diff --git a/server/metrics/MetricsCollector.py b/server/metrics/MetricsCollector.py index 3744395..25df86d 100644 --- a/server/metrics/MetricsCollector.py +++ b/server/metrics/MetricsCollector.py @@ -1,9 +1,9 @@ -from server.metrics.MetricsManager import MetricsManager +from server.metrics.backends.Template import StoreTemplate import time class MetricsCollector: - def __init__(self, metrics_manager:MetricsManager, game_state_local_cache:bool, metrics_backend:str, game_state_backend:str, stale_game_timeout_sec:int, game_last_seen_unix:dict, game_move_counts:dict): + def __init__(self, metrics_manager:StoreTemplate, game_state_local_cache:bool, metrics_backend:str, game_state_backend:str, stale_game_timeout_sec:int, game_last_seen_unix:dict, game_move_counts:dict): self._manager = metrics_manager self._stale_game_timeout_sec = stale_game_timeout_sec self._game_last_seen_unix = game_last_seen_unix diff --git a/server/metrics/__init__.py b/server/metrics/__init__.py index a91a93c..d203de9 100644 --- a/server/metrics/__init__.py +++ b/server/metrics/__init__.py @@ -1,9 +1,13 @@ -from server.metrics.MemoryMetricsStore import MemoryMetricsStore -from server.metrics.RedisMetricsStore import RedisMetricsStore +from .backends.Template import StoreTemplate + +from .backends.Memory import MemoryMetricsStore +from .backends.Redis import RedisMetricsStore + +from .MetricsCollector import MetricsCollector class MetricsStoreBuilder: @classmethod - def build(self, backend:str="memory", **kwargs) -> MemoryMetricsStore|RedisMetricsStore: + def build(self, backend:str="memory", **kwargs) -> StoreTemplate: selected = (backend or "memory").strip().lower() if selected == "redis": return RedisMetricsStore(**kwargs) diff --git a/server/metrics/MemoryMetricsStore.py b/server/metrics/backends/Memory.py similarity index 63% rename from server/metrics/MemoryMetricsStore.py rename to server/metrics/backends/Memory.py index 93cc626..35eafdd 100644 --- a/server/metrics/MemoryMetricsStore.py +++ b/server/metrics/backends/Memory.py @@ -1,5 +1,8 @@ -class MemoryMetricsStore: +from server.metrics.backends.Template import StoreTemplate + +class MemoryMetricsStore(StoreTemplate): def __init__(self, **kwargs): + super().__init__(backend="memory", **kwargs) self._snapshots:dict[str, dict] = {} async def publish(self, worker_id:str, snapshot:dict) -> None: @@ -11,7 +14,7 @@ class MemoryMetricsStore: async def clear_all(self) -> None: self._snapshots.clear() - async def acquire_startup_cleanup_lock(self, lock_key:str, ttl_seconds:int=300) -> bool: + async def _acquire_startup_cleanup_lock(self, lock_key:str, ttl_seconds:int=300) -> bool: return True async def close(self) -> None: diff --git a/server/metrics/RedisMetricsStore.py b/server/metrics/backends/Redis.py similarity index 88% rename from server/metrics/RedisMetricsStore.py rename to server/metrics/backends/Redis.py index e4f041b..66e8692 100644 --- a/server/metrics/RedisMetricsStore.py +++ b/server/metrics/backends/Redis.py @@ -1,8 +1,10 @@ -import inspect -import json +from server.metrics.backends.Template import StoreTemplate -class RedisMetricsStore: +import inspect, json + +class RedisMetricsStore(StoreTemplate): def __init__(self, redis_url:str="redis://localhost:6379/0", key_prefix:str="snake:metrics:worker", ttl_seconds:int|None=None, **kwargs): + super().__init__(backend="redis", key_prefix=key_prefix, **kwargs) self.redis_url = redis_url self.key_prefix = key_prefix self.ttl_seconds = ttl_seconds @@ -47,7 +49,7 @@ class RedisMetricsStore: if keys: await redis.delete(*keys) - async def acquire_startup_cleanup_lock(self, lock_key:str, ttl_seconds:int=300) -> bool: + async def _acquire_startup_cleanup_lock(self, lock_key:str, ttl_seconds:int=300) -> bool: redis = await self._get_redis() locked = await redis.set(lock_key, '1', ex=max(1, int(ttl_seconds)), nx=True) return bool(locked) diff --git a/server/metrics/MetricsManager.py b/server/metrics/backends/Template.py similarity index 86% rename from server/metrics/MetricsManager.py rename to server/metrics/backends/Template.py index 1dbcf7e..aa158e3 100644 --- a/server/metrics/MetricsManager.py +++ b/server/metrics/backends/Template.py @@ -1,19 +1,12 @@ -from server.metrics import MetricsStoreBuilder - from typing import Any, Awaitable, cast -import time, os, inspect +import inspect, os -class MetricsManager: - def __init__(self, backend:str="memory", redis_url:str="redis://localhost:6379/0", ttl_seconds:int|None=90, key_prefix:str="snake:metrics:worker", worker_id:str|None=None): +class StoreTemplate: + def __init__(self, backend:str="memory", key_prefix:str="snake:metrics:worker", worker_id:str|None=None, **kwargs): self.backend = (backend or "memory").strip().lower() self.key_prefix = key_prefix - self.worker_id = worker_id or f"{os.getpid()}-{int(time.time() * 1000)}" - self.store = MetricsStoreBuilder.build( - backend=self.backend, - redis_url=redis_url, - ttl_seconds=ttl_seconds, - key_prefix=key_prefix, - ) + self.worker_id = worker_id or f"{os.getpid()}" + self.store = self async def publish_only(self, snapshot:dict) -> None: await self.store.publish(self.worker_id, snapshot) @@ -30,7 +23,14 @@ class MetricsManager: return self._merge_snapshots(snapshots) async def close(self) -> None: - await self.store.close() + if self.store is self: + return + close_store = getattr(self.store, "close", None) + if not callable(close_store): + return + maybe_result = close_store() + if inspect.isawaitable(maybe_result): + await cast(Awaitable[Any], maybe_result) async def clear_all_workers(self) -> None: clear_all = getattr(self.store, "clear_all", None) @@ -43,9 +43,11 @@ class MetricsManager: if self.backend != "redis": return True - acquire_lock = getattr(self.store, "acquire_startup_cleanup_lock", None) + acquire_lock = getattr(self.store, "_acquire_startup_cleanup_lock", None) if not callable(acquire_lock): - return True + acquire_lock = getattr(self.store, "acquire_startup_cleanup_lock", None) + if not callable(acquire_lock): + return True lock_key = f"{self.key_prefix}:startup_cleanup_lock" maybe_result = acquire_lock(lock_key, ttl_seconds) diff --git a/tests/test_MetricsManager.py b/tests/test_MetricsManager.py index 859d196..5080e3f 100644 --- a/tests/test_MetricsManager.py +++ b/tests/test_MetricsManager.py @@ -1,11 +1,14 @@ import unittest from typing import Any, cast -from server.metrics.MetricsManager import MetricsManager +from server.metrics import ( + MetricsStoreBuilder, + MemoryMetricsStore, +) -class TestMetricsManager(unittest.IsolatedAsyncioTestCase): +class TestMetricsStoreTemplate(unittest.IsolatedAsyncioTestCase): async def test_memory_backend_returns_local_snapshot(self): - manager = MetricsManager(backend="memory") + manager = MetricsStoreBuilder.build(backend="memory") local = { "games_started": 2, "games_ended": 1, @@ -49,7 +52,7 @@ class TestMetricsManager(unittest.IsolatedAsyncioTestCase): await manager.close() async def test_merge_snapshots_aggregates_totals(self): - manager = MetricsManager(backend="memory") + manager = MemoryMetricsStore() merged = manager._merge_snapshots( [ { @@ -152,7 +155,7 @@ class TestMetricsManager(unittest.IsolatedAsyncioTestCase): async def close(self): return None - manager = MetricsManager(backend="redis", key_prefix="snake:metrics:worker") + manager = MetricsStoreBuilder.build(backend="redis", key_prefix="snake:metrics:worker") fake_store = FakeStore() manager.store = cast(Any, fake_store)