Spaces:
Running
Running
| import asyncio | |
| import logging | |
| import time | |
| from enum import Enum | |
| from typing import Dict, List, Any, Optional | |
| from pydantic import BaseModel | |
| _logger = logging.getLogger("api.health_manager") | |
| class HealthStatus(str, Enum): | |
| HEALTHY = "healthy" | |
| DEGRADED = "degraded" | |
| DOWN = "down" | |
| class ComponentHealth(BaseModel): | |
| id: str | |
| type: str # "worker" | "provider" | "service" | |
| status: HealthStatus = HealthStatus.HEALTHY | |
| failure_count: int = 0 | |
| last_check: float = 0.0 | |
| latency: float = 0.0 | |
| error_message: Optional[str] = None | |
| class HealthManager: | |
| """ | |
| ARCH-P5.1: Health Manager | |
| Gestisce il monitoraggio, il Circuit Breaker e il Traffic Management. | |
| """ | |
| def __init__(self): | |
| self.components: Dict[str, ComponentHealth] = {} | |
| self._lock = asyncio.Lock() | |
| self.failure_threshold = 5 # Numero di errori prima di aprire il circuit | |
| self.recovery_timeout = 60 # Secondi prima di riprovare un componente DOWN | |
| async def record_success(self, component_id: str, latency: float = 0.0, component_type: str = "worker"): | |
| """Registra un'operazione riuscita per un componente.""" | |
| async with self._lock: | |
| if component_id not in self.components: | |
| self.components[component_id] = ComponentHealth(id=component_id, type=component_type) | |
| c = self.components[component_id] | |
| c.status = HealthStatus.HEALTHY | |
| c.failure_count = 0 | |
| c.last_check = time.time() | |
| c.latency = latency | |
| c.error_message = None | |
| async def record_failure(self, component_id: str, error: str, component_type: str = "worker"): | |
| """Registra un fallimento e attiva il circuit breaker se necessario.""" | |
| async with self._lock: | |
| if component_id not in self.components: | |
| self.components[component_id] = ComponentHealth(id=component_id, type=component_type) | |
| c = self.components[component_id] | |
| c.failure_count += 1 | |
| c.last_check = time.time() | |
| c.error_message = error | |
| if c.failure_count >= self.failure_threshold: | |
| if c.status != HealthStatus.DOWN: | |
| _logger.warning(f"Circuit Breaker APERTO per {component_id}: {error}") | |
| c.status = HealthStatus.DOWN | |
| elif c.failure_count >= 2: | |
| c.status = HealthStatus.DEGRADED | |
| async def is_healthy(self, component_id: str) -> bool: | |
| """Verifica se un componente è sano (o se è tempo di riprovare).""" | |
| async with self._lock: | |
| if component_id not in self.components: | |
| return True | |
| c = self.components[component_id] | |
| if c.status == HealthStatus.DOWN: | |
| # Half-open state: riprova dopo il timeout | |
| if time.time() - c.last_check > self.recovery_timeout: | |
| _logger.info(f"Circuit Breaker HALF-OPEN per {component_id} (tentativo di recovery)") | |
| return True | |
| return False | |
| return True | |
| async def get_status(self) -> Dict[str, Any]: | |
| """Ritorna lo stato aggregato di salute del sistema.""" | |
| async with self._lock: | |
| return { | |
| "ts": time.time(), | |
| "components": {k: v.dict() for k, v in self.components.items()}, | |
| "summary": { | |
| "healthy": sum(1 for c in self.components.values() if c.status == HealthStatus.HEALTHY), | |
| "degraded": sum(1 for c in self.components.values() if c.status == HealthStatus.DEGRADED), | |
| "down": sum(1 for c in self.components.values() if c.status == HealthStatus.DOWN), | |
| } | |
| } | |
| # Singleton instance | |
| health_manager = HealthManager() | |