rename Metric Classes and change Folder Structure

This commit is contained in:
2026-04-04 22:14:14 +02:00
parent a38a600bdc
commit 043d7654f9
7 changed files with 52 additions and 36 deletions
+7 -5
View File
@@ -1,16 +1,18 @@
from quart_common.web.logger import build_logger, await_log
from server.Files import read_file from server.Files import read_file
from server.game_board_stats import GameBoardStoreBuilder from server.game_board_stats import GameBoardStoreBuilder
from server.GameBoard import GameBoard from server.GameBoard import GameBoard
from snakes import SnakeBuilder 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.storage.StorageLoader import StorageLoader
from server.metrics import (
MetricsStoreBuilder,
MetricsCollector,
)
from quart import Quart, request, jsonify from quart import Quart, request, jsonify
import logging, json, os, re, time import logging, json, os, re, time
from typing import cast from typing import cast
@@ -50,7 +52,7 @@ class Server:
self.game_last_seen_unix:dict[str, int] = {} self.game_last_seen_unix:dict[str, int] = {}
self.metrics_collector = MetricsCollector( self.metrics_collector = MetricsCollector(
metrics_manager=MetricsManager( metrics_manager=MetricsStoreBuilder.build(
backend=metrics_backend_normalized, backend=metrics_backend_normalized,
redis_url=metrics_redis_url, redis_url=metrics_redis_url,
ttl_seconds=metrics_ttl_sec, ttl_seconds=metrics_ttl_sec,
+2 -2
View File
@@ -1,9 +1,9 @@
from server.metrics.MetricsManager import MetricsManager from server.metrics.backends.Template import StoreTemplate
import time import time
class MetricsCollector: 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._manager = metrics_manager
self._stale_game_timeout_sec = stale_game_timeout_sec self._stale_game_timeout_sec = stale_game_timeout_sec
self._game_last_seen_unix = game_last_seen_unix self._game_last_seen_unix = game_last_seen_unix
+7 -3
View File
@@ -1,9 +1,13 @@
from server.metrics.MemoryMetricsStore import MemoryMetricsStore from .backends.Template import StoreTemplate
from server.metrics.RedisMetricsStore import RedisMetricsStore
from .backends.Memory import MemoryMetricsStore
from .backends.Redis import RedisMetricsStore
from .MetricsCollector import MetricsCollector
class MetricsStoreBuilder: class MetricsStoreBuilder:
@classmethod @classmethod
def build(self, backend:str="memory", **kwargs) -> MemoryMetricsStore|RedisMetricsStore: def build(self, backend:str="memory", **kwargs) -> StoreTemplate:
selected = (backend or "memory").strip().lower() selected = (backend or "memory").strip().lower()
if selected == "redis": if selected == "redis":
return RedisMetricsStore(**kwargs) return RedisMetricsStore(**kwargs)
@@ -1,5 +1,8 @@
class MemoryMetricsStore: from server.metrics.backends.Template import StoreTemplate
class MemoryMetricsStore(StoreTemplate):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(backend="memory", **kwargs)
self._snapshots:dict[str, dict] = {} self._snapshots:dict[str, dict] = {}
async def publish(self, worker_id:str, snapshot:dict) -> None: async def publish(self, worker_id:str, snapshot:dict) -> None:
@@ -11,7 +14,7 @@ class MemoryMetricsStore:
async def clear_all(self) -> None: async def clear_all(self) -> None:
self._snapshots.clear() 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 return True
async def close(self) -> None: async def close(self) -> None:
@@ -1,8 +1,10 @@
import inspect from server.metrics.backends.Template import StoreTemplate
import json
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): 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.redis_url = redis_url
self.key_prefix = key_prefix self.key_prefix = key_prefix
self.ttl_seconds = ttl_seconds self.ttl_seconds = ttl_seconds
@@ -47,7 +49,7 @@ class RedisMetricsStore:
if keys: if keys:
await redis.delete(*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() redis = await self._get_redis()
locked = await redis.set(lock_key, '1', ex=max(1, int(ttl_seconds)), nx=True) locked = await redis.set(lock_key, '1', ex=max(1, int(ttl_seconds)), nx=True)
return bool(locked) return bool(locked)
@@ -1,19 +1,12 @@
from server.metrics import MetricsStoreBuilder
from typing import Any, Awaitable, cast from typing import Any, Awaitable, cast
import time, os, inspect import inspect, os
class MetricsManager: class StoreTemplate:
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): 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.backend = (backend or "memory").strip().lower()
self.key_prefix = key_prefix self.key_prefix = key_prefix
self.worker_id = worker_id or f"{os.getpid()}-{int(time.time() * 1000)}" self.worker_id = worker_id or f"{os.getpid()}"
self.store = MetricsStoreBuilder.build( self.store = self
backend=self.backend,
redis_url=redis_url,
ttl_seconds=ttl_seconds,
key_prefix=key_prefix,
)
async def publish_only(self, snapshot:dict) -> None: async def publish_only(self, snapshot:dict) -> None:
await self.store.publish(self.worker_id, snapshot) await self.store.publish(self.worker_id, snapshot)
@@ -30,7 +23,14 @@ class MetricsManager:
return self._merge_snapshots(snapshots) return self._merge_snapshots(snapshots)
async def close(self) -> None: 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: async def clear_all_workers(self) -> None:
clear_all = getattr(self.store, "clear_all", None) clear_all = getattr(self.store, "clear_all", None)
@@ -43,6 +43,8 @@ class MetricsManager:
if self.backend != "redis": if self.backend != "redis":
return True return True
acquire_lock = getattr(self.store, "_acquire_startup_cleanup_lock", None)
if not callable(acquire_lock):
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): if not callable(acquire_lock):
return True return True
+8 -5
View File
@@ -1,11 +1,14 @@
import unittest import unittest
from typing import Any, cast 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): async def test_memory_backend_returns_local_snapshot(self):
manager = MetricsManager(backend="memory") manager = MetricsStoreBuilder.build(backend="memory")
local = { local = {
"games_started": 2, "games_started": 2,
"games_ended": 1, "games_ended": 1,
@@ -49,7 +52,7 @@ class TestMetricsManager(unittest.IsolatedAsyncioTestCase):
await manager.close() await manager.close()
async def test_merge_snapshots_aggregates_totals(self): async def test_merge_snapshots_aggregates_totals(self):
manager = MetricsManager(backend="memory") manager = MemoryMetricsStore()
merged = manager._merge_snapshots( merged = manager._merge_snapshots(
[ [
{ {
@@ -152,7 +155,7 @@ class TestMetricsManager(unittest.IsolatedAsyncioTestCase):
async def close(self): async def close(self):
return None return None
manager = MetricsManager(backend="redis", key_prefix="snake:metrics:worker") manager = MetricsStoreBuilder.build(backend="redis", key_prefix="snake:metrics:worker")
fake_store = FakeStore() fake_store = FakeStore()
manager.store = cast(Any, fake_store) manager.store = cast(Any, fake_store)