rename Metric Classes and change Folder Structure
This commit is contained in:
+7
-5
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user