from __future__ import annotations import time from typing import Any from yuxi.utils.logging_config import logger class UnifiedCache: def __init__(self, name: str, max_entries: int = 2048, ttl_seconds: float = 600.0): self.name = name self._max_entries = max_entries self._ttl_seconds = ttl_seconds self._cache: dict[str, tuple[Any, float]] = {} self._hits = 0 self._misses = 0 self._evictions = 0 def get(self, key: str) -> Any | None: entry = self._cache.get(key) if entry is None: self._misses += 1 return None value, ts = entry if time.monotonic() - ts > self._ttl_seconds: del self._cache[key] self._evictions += 1 self._misses += 1 return None self._hits += 1 return value def set(self, key: str, value: Any) -> None: self._cache[key] = (value, time.monotonic()) self._trim() def delete(self, key: str) -> None: self._cache.pop(key, None) def clear(self) -> None: self._cache.clear() def _trim(self) -> None: if len(self._cache) <= self._max_entries: return now = time.monotonic() expired = [k for k, v in self._cache.items() if now - v[1] > self._ttl_seconds] for k in expired: del self._cache[k] self._evictions += 1 if len(self._cache) > self._max_entries: oldest = sorted(self._cache.items(), key=lambda x: x[1][1])[: len(self._cache) - self._max_entries] for k, _ in oldest: del self._cache[k] self._evictions += 1 @property def size(self) -> int: return len(self._cache) @property def stats(self) -> dict[str, Any]: return { "name": self.name, "size": self.size, "max_entries": self._max_entries, "ttl_seconds": self._ttl_seconds, "hits": self._hits, "misses": self._misses, "evictions": self._evictions, "hit_ratio": self._hits / max(self._hits + self._misses, 1), } class CacheManager: def __init__(self, max_cache_entries: int = 2048): self._caches: dict[str, UnifiedCache] = {} self._max_cache_entries = max_cache_entries def register( self, name: str, max_entries: int | None = None, ttl_seconds: float = 600.0, ) -> UnifiedCache: cache = UnifiedCache( name=name, max_entries=max_entries or self._max_cache_entries, ttl_seconds=ttl_seconds, ) self._caches[name] = cache return cache def get_cache(self, name: str) -> UnifiedCache | None: return self._caches.get(name) def clear_all(self) -> None: for cache in self._caches.values(): cache.clear() def get_all_stats(self) -> dict[str, dict[str, Any]]: return {name: cache.stats for name, cache in self._caches.items()} def log_stats(self) -> None: for name, cache in self._caches.items(): stats = cache.stats logger.debug( "[BlueBubbles] Cache '%s': %d entries, hit_ratio=%.2f", name, stats["size"], stats["hit_ratio"], )