error_handling.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902
  1. """
  2. Error Handling and Fallback Mechanisms for Trixy ML Integration.
  3. This module provides comprehensive error handling, recovery mechanisms,
  4. and fallback strategies for the ML components in the Trixy voice assistant.
  5. It ensures robust operation and graceful degradation under various failure scenarios.
  6. Key Features:
  7. - Comprehensive error classification and handling
  8. - Automatic recovery mechanisms
  9. - Fallback strategies for component failures
  10. - Circuit breaker patterns for fault isolation
  11. - Health monitoring and diagnostics
  12. - Error reporting and logging
  13. - Performance degradation handling
  14. - Resource exhaustion management
  15. Usage:
  16. from trixy_core.ml import MLErrorHandler, CircuitBreaker, FallbackManager
  17. # Create error handler
  18. error_handler = MLErrorHandler()
  19. # Register with ML components
  20. ml_manager = MLManager(config, error_handler=error_handler)
  21. # Configure fallback strategies
  22. fallback_manager = FallbackManager(error_handler)
  23. fallback_manager.configure_fallbacks()
  24. """
  25. import asyncio
  26. import logging
  27. import time
  28. import threading
  29. import traceback
  30. from typing import Dict, Any, Optional, List, Callable, Union, Type
  31. from dataclasses import dataclass, field
  32. from enum import Enum
  33. import psutil
  34. import gc
  35. from collections import defaultdict, deque
  36. from datetime import datetime, timedelta
  37. # ML component imports
  38. from .ml_manager import MLManager, MLManagerError
  39. from .audio_pipeline import AudioPipeline, AudioPipelineError
  40. from .wakeword import WakewordDetectorError
  41. from .voice_recognition import VoiceRecognitionDetectorError
  42. class ErrorSeverity(Enum):
  43. """Error severity levels."""
  44. LOW = 1
  45. MEDIUM = 2
  46. HIGH = 3
  47. CRITICAL = 4
  48. class ErrorCategory(Enum):
  49. """Error categories for classification."""
  50. SYSTEM = "system"
  51. NETWORK = "network"
  52. MODEL = "model"
  53. AUDIO = "audio"
  54. MEMORY = "memory"
  55. PERFORMANCE = "performance"
  56. CONFIGURATION = "configuration"
  57. USER_INPUT = "user_input"
  58. class RecoveryStrategy(Enum):
  59. """Recovery strategies."""
  60. RETRY = "retry"
  61. FALLBACK = "fallback"
  62. RESTART = "restart"
  63. DEGRADE = "degrade"
  64. IGNORE = "ignore"
  65. ESCALATE = "escalate"
  66. class CircuitState(Enum):
  67. """Circuit breaker states."""
  68. CLOSED = "closed"
  69. OPEN = "open"
  70. HALF_OPEN = "half_open"
  71. @dataclass
  72. class ErrorInfo:
  73. """Error information data structure."""
  74. error_id: str
  75. timestamp: float
  76. severity: ErrorSeverity
  77. category: ErrorCategory
  78. component: str
  79. error_type: str
  80. message: str
  81. traceback: Optional[str] = None
  82. context: Dict[str, Any] = field(default_factory=dict)
  83. recovery_strategy: Optional[RecoveryStrategy] = None
  84. retry_count: int = 0
  85. resolved: bool = False
  86. resolution_time: Optional[float] = None
  87. def duration(self) -> Optional[float]:
  88. """Get error duration if resolved."""
  89. if self.resolved and self.resolution_time:
  90. return self.resolution_time - self.timestamp
  91. return None
  92. @dataclass
  93. class HealthStatus:
  94. """Component health status."""
  95. component: str
  96. is_healthy: bool
  97. last_check: float
  98. error_count: int = 0
  99. performance_score: float = 1.0
  100. memory_usage_mb: float = 0.0
  101. cpu_usage_percent: float = 0.0
  102. uptime_seconds: float = 0.0
  103. issues: List[str] = field(default_factory=list)
  104. class CircuitBreaker:
  105. """
  106. Circuit breaker for fault isolation and automatic recovery.
  107. Implements the circuit breaker pattern to prevent cascading failures
  108. and provide automatic recovery mechanisms.
  109. """
  110. def __init__(
  111. self,
  112. name: str,
  113. failure_threshold: int = 5,
  114. recovery_timeout: float = 60.0,
  115. success_threshold: int = 3
  116. ):
  117. """
  118. Initialize circuit breaker.
  119. Args:
  120. name: Circuit breaker name
  121. failure_threshold: Number of failures to trigger open state
  122. recovery_timeout: Time to wait before trying half-open state
  123. success_threshold: Number of successes to close circuit
  124. """
  125. self.name = name
  126. self.failure_threshold = failure_threshold
  127. self.recovery_timeout = recovery_timeout
  128. self.success_threshold = success_threshold
  129. self._state = CircuitState.CLOSED
  130. self._failure_count = 0
  131. self._success_count = 0
  132. self._last_failure_time = 0.0
  133. self._lock = threading.RLock()
  134. def call(self, func: Callable, *args, **kwargs) -> Any:
  135. """
  136. Execute function through circuit breaker.
  137. Args:
  138. func: Function to execute
  139. *args: Function arguments
  140. **kwargs: Function keyword arguments
  141. Returns:
  142. Function result
  143. Raises:
  144. CircuitBreakerError: If circuit is open
  145. """
  146. with self._lock:
  147. if self._state == CircuitState.OPEN:
  148. if time.time() - self._last_failure_time >= self.recovery_timeout:
  149. self._state = CircuitState.HALF_OPEN
  150. self._success_count = 0
  151. else:
  152. raise CircuitBreakerError(f"Circuit breaker {self.name} is open")
  153. try:
  154. result = func(*args, **kwargs)
  155. self._on_success()
  156. return result
  157. except Exception as e:
  158. self._on_failure()
  159. raise
  160. async def async_call(self, func: Callable, *args, **kwargs) -> Any:
  161. """
  162. Execute async function through circuit breaker.
  163. Args:
  164. func: Async function to execute
  165. *args: Function arguments
  166. **kwargs: Function keyword arguments
  167. Returns:
  168. Function result
  169. """
  170. with self._lock:
  171. if self._state == CircuitState.OPEN:
  172. if time.time() - self._last_failure_time >= self.recovery_timeout:
  173. self._state = CircuitState.HALF_OPEN
  174. self._success_count = 0
  175. else:
  176. raise CircuitBreakerError(f"Circuit breaker {self.name} is open")
  177. try:
  178. result = await func(*args, **kwargs)
  179. self._on_success()
  180. return result
  181. except Exception as e:
  182. self._on_failure()
  183. raise
  184. def _on_success(self) -> None:
  185. """Handle successful operation."""
  186. with self._lock:
  187. self._failure_count = 0
  188. if self._state == CircuitState.HALF_OPEN:
  189. self._success_count += 1
  190. if self._success_count >= self.success_threshold:
  191. self._state = CircuitState.CLOSED
  192. def _on_failure(self) -> None:
  193. """Handle failed operation."""
  194. with self._lock:
  195. self._failure_count += 1
  196. self._last_failure_time = time.time()
  197. if self._failure_count >= self.failure_threshold:
  198. self._state = CircuitState.OPEN
  199. def get_state(self) -> CircuitState:
  200. """Get current circuit state."""
  201. return self._state
  202. def reset(self) -> None:
  203. """Reset circuit breaker to closed state."""
  204. with self._lock:
  205. self._state = CircuitState.CLOSED
  206. self._failure_count = 0
  207. self._success_count = 0
  208. class FallbackStrategy:
  209. """Base fallback strategy."""
  210. def __init__(self, name: str):
  211. self.name = name
  212. self.enabled = True
  213. async def execute(self, error_info: ErrorInfo, context: Dict[str, Any]) -> bool:
  214. """
  215. Execute fallback strategy.
  216. Args:
  217. error_info: Error information
  218. context: Execution context
  219. Returns:
  220. True if fallback was successful
  221. """
  222. raise NotImplementedError
  223. class RetryFallback(FallbackStrategy):
  224. """Retry fallback strategy with exponential backoff."""
  225. def __init__(self, max_retries: int = 3, base_delay: float = 1.0, max_delay: float = 60.0):
  226. super().__init__("retry")
  227. self.max_retries = max_retries
  228. self.base_delay = base_delay
  229. self.max_delay = max_delay
  230. async def execute(self, error_info: ErrorInfo, context: Dict[str, Any]) -> bool:
  231. """Execute retry fallback."""
  232. if error_info.retry_count >= self.max_retries:
  233. return False
  234. # Calculate delay with exponential backoff
  235. delay = min(self.base_delay * (2 ** error_info.retry_count), self.max_delay)
  236. await asyncio.sleep(delay)
  237. try:
  238. # Retry the original operation
  239. original_func = context.get('original_function')
  240. if original_func:
  241. await original_func()
  242. return True
  243. except Exception:
  244. pass
  245. return False
  246. class DegradedModeFallback(FallbackStrategy):
  247. """Degraded mode fallback strategy."""
  248. def __init__(self):
  249. super().__init__("degraded_mode")
  250. async def execute(self, error_info: ErrorInfo, context: Dict[str, Any]) -> bool:
  251. """Execute degraded mode fallback."""
  252. component = context.get('component')
  253. if error_info.component == 'wakeword':
  254. # Fallback to energy-based detection
  255. return await self._enable_energy_based_detection(context)
  256. elif error_info.component == 'voice_recognition':
  257. # Fallback to generic speaker identification
  258. return await self._enable_generic_speaker_id(context)
  259. elif error_info.component == 'audio_pipeline':
  260. # Reduce audio quality settings
  261. return await self._reduce_audio_quality(context)
  262. return False
  263. async def _enable_energy_based_detection(self, context: Dict[str, Any]) -> bool:
  264. """Enable energy-based wakeword detection."""
  265. try:
  266. # This would switch to a simpler energy-based detection
  267. logging.info("Switching to energy-based wakeword detection")
  268. return True
  269. except Exception:
  270. return False
  271. async def _enable_generic_speaker_id(self, context: Dict[str, Any]) -> bool:
  272. """Enable generic speaker identification."""
  273. try:
  274. # This would use a simpler speaker identification approach
  275. logging.info("Switching to generic speaker identification")
  276. return True
  277. except Exception:
  278. return False
  279. async def _reduce_audio_quality(self, context: Dict[str, Any]) -> bool:
  280. """Reduce audio quality settings."""
  281. try:
  282. # This would reduce audio processing quality to save resources
  283. logging.info("Reducing audio quality for degraded mode")
  284. return True
  285. except Exception:
  286. return False
  287. class RestartFallback(FallbackStrategy):
  288. """Component restart fallback strategy."""
  289. def __init__(self):
  290. super().__init__("restart")
  291. async def execute(self, error_info: ErrorInfo, context: Dict[str, Any]) -> bool:
  292. """Execute restart fallback."""
  293. component = context.get('component')
  294. try:
  295. if hasattr(component, 'restart'):
  296. await component.restart()
  297. return True
  298. elif hasattr(component, 'stop') and hasattr(component, 'start'):
  299. await component.stop()
  300. await asyncio.sleep(1.0) # Brief pause
  301. await component.start()
  302. return True
  303. except Exception as e:
  304. logging.error(f"Failed to restart component {error_info.component}: {e}")
  305. return False
  306. class CircuitBreakerError(Exception):
  307. """Circuit breaker exception."""
  308. pass
  309. class MLErrorHandler:
  310. """
  311. Comprehensive error handler for ML components.
  312. Provides centralized error handling, classification, recovery,
  313. and fallback mechanisms for the ML system.
  314. """
  315. def __init__(self, logger: Optional[logging.Logger] = None):
  316. """
  317. Initialize ML error handler.
  318. Args:
  319. logger: Logger instance
  320. """
  321. self.logger = logger or logging.getLogger(__name__)
  322. # Error tracking
  323. self._errors: Dict[str, ErrorInfo] = {}
  324. self._error_history = deque(maxlen=1000)
  325. self._error_counts = defaultdict(int)
  326. # Circuit breakers
  327. self._circuit_breakers: Dict[str, CircuitBreaker] = {}
  328. # Fallback strategies
  329. self._fallback_strategies: Dict[RecoveryStrategy, FallbackStrategy] = {
  330. RecoveryStrategy.RETRY: RetryFallback(),
  331. RecoveryStrategy.FALLBACK: DegradedModeFallback(),
  332. RecoveryStrategy.RESTART: RestartFallback()
  333. }
  334. # Health monitoring
  335. self._health_status: Dict[str, HealthStatus] = {}
  336. self._health_check_interval = 30.0
  337. self._health_check_task: Optional[asyncio.Task] = None
  338. # Component references
  339. self._components: Dict[str, Any] = {}
  340. # Configuration
  341. self._max_critical_errors = 5
  342. self._error_escalation_threshold = 10
  343. self.logger.info("ML Error Handler initialized")
  344. def register_component(self, name: str, component: Any) -> None:
  345. """
  346. Register component for error handling.
  347. Args:
  348. name: Component name
  349. component: Component instance
  350. """
  351. self._components[name] = component
  352. # Create circuit breaker for component
  353. self._circuit_breakers[name] = CircuitBreaker(name)
  354. # Initialize health status
  355. self._health_status[name] = HealthStatus(
  356. component=name,
  357. is_healthy=True,
  358. last_check=time.time()
  359. )
  360. self.logger.info(f"Registered component for error handling: {name}")
  361. async def handle_error(
  362. self,
  363. error: Exception,
  364. component: str,
  365. context: Optional[Dict[str, Any]] = None,
  366. severity: Optional[ErrorSeverity] = None
  367. ) -> bool:
  368. """
  369. Handle an error with appropriate recovery strategy.
  370. Args:
  371. error: Exception that occurred
  372. component: Component that generated the error
  373. context: Additional context information
  374. severity: Error severity (auto-detected if not provided)
  375. Returns:
  376. True if error was handled successfully
  377. """
  378. try:
  379. # Create error info
  380. error_info = self._create_error_info(error, component, context, severity)
  381. # Store error
  382. self._errors[error_info.error_id] = error_info
  383. self._error_history.append(error_info)
  384. self._error_counts[error_info.category] += 1
  385. # Update component health
  386. await self._update_component_health(component, error_info)
  387. # Log error
  388. self._log_error(error_info)
  389. # Determine recovery strategy
  390. recovery_strategy = self._determine_recovery_strategy(error_info)
  391. error_info.recovery_strategy = recovery_strategy
  392. # Execute recovery strategy
  393. success = await self._execute_recovery_strategy(error_info, context or {})
  394. if success:
  395. error_info.resolved = True
  396. error_info.resolution_time = time.time()
  397. self.logger.info(f"Error {error_info.error_id} resolved using {recovery_strategy.value}")
  398. else:
  399. # Escalate if recovery failed
  400. await self._escalate_error(error_info)
  401. return success
  402. except Exception as e:
  403. self.logger.error(f"Error in error handler: {e}")
  404. return False
  405. async def start_health_monitoring(self) -> None:
  406. """Start health monitoring task."""
  407. if self._health_check_task:
  408. return
  409. self._health_check_task = asyncio.create_task(self._health_monitoring_loop())
  410. self.logger.info("Health monitoring started")
  411. async def stop_health_monitoring(self) -> None:
  412. """Stop health monitoring task."""
  413. if self._health_check_task:
  414. self._health_check_task.cancel()
  415. try:
  416. await self._health_check_task
  417. except asyncio.CancelledError:
  418. pass
  419. self._health_check_task = None
  420. self.logger.info("Health monitoring stopped")
  421. def get_circuit_breaker(self, component: str) -> Optional[CircuitBreaker]:
  422. """Get circuit breaker for component."""
  423. return self._circuit_breakers.get(component)
  424. def get_error_statistics(self) -> Dict[str, Any]:
  425. """Get error statistics."""
  426. total_errors = len(self._error_history)
  427. resolved_errors = sum(1 for e in self._error_history if e.resolved)
  428. # Calculate error rates by category
  429. category_rates = {}
  430. for category, count in self._error_counts.items():
  431. category_rates[category.value] = count / max(total_errors, 1)
  432. # Calculate average resolution time
  433. resolution_times = [e.duration() for e in self._error_history if e.resolved and e.duration()]
  434. avg_resolution_time = sum(resolution_times) / len(resolution_times) if resolution_times else 0
  435. return {
  436. "total_errors": total_errors,
  437. "resolved_errors": resolved_errors,
  438. "resolution_rate": resolved_errors / max(total_errors, 1),
  439. "category_rates": category_rates,
  440. "average_resolution_time": avg_resolution_time,
  441. "active_circuit_breakers": {
  442. name: cb.get_state().value
  443. for name, cb in self._circuit_breakers.items()
  444. }
  445. }
  446. def get_health_status(self, component: Optional[str] = None) -> Union[HealthStatus, Dict[str, HealthStatus]]:
  447. """Get health status for component(s)."""
  448. if component:
  449. return self._health_status.get(component)
  450. return self._health_status.copy()
  451. def _create_error_info(
  452. self,
  453. error: Exception,
  454. component: str,
  455. context: Optional[Dict[str, Any]],
  456. severity: Optional[ErrorSeverity]
  457. ) -> ErrorInfo:
  458. """Create error information object."""
  459. import uuid
  460. # Auto-detect severity if not provided
  461. if severity is None:
  462. severity = self._classify_error_severity(error)
  463. # Classify error category
  464. category = self._classify_error_category(error, component)
  465. return ErrorInfo(
  466. error_id=str(uuid.uuid4()),
  467. timestamp=time.time(),
  468. severity=severity,
  469. category=category,
  470. component=component,
  471. error_type=type(error).__name__,
  472. message=str(error),
  473. traceback=traceback.format_exc(),
  474. context=context or {}
  475. )
  476. def _classify_error_severity(self, error: Exception) -> ErrorSeverity:
  477. """Classify error severity."""
  478. if isinstance(error, (MemoryError, SystemError)):
  479. return ErrorSeverity.CRITICAL
  480. elif isinstance(error, (MLManagerError, AudioPipelineError)):
  481. return ErrorSeverity.HIGH
  482. elif isinstance(error, (WakewordDetectorError, VoiceRecognitionDetectorError)):
  483. return ErrorSeverity.MEDIUM
  484. else:
  485. return ErrorSeverity.LOW
  486. def _classify_error_category(self, error: Exception, component: str) -> ErrorCategory:
  487. """Classify error category."""
  488. if isinstance(error, (MemoryError, OSError)):
  489. return ErrorCategory.SYSTEM
  490. elif isinstance(error, (ConnectionError, TimeoutError)):
  491. return ErrorCategory.NETWORK
  492. elif 'model' in str(error).lower() or 'tensor' in str(error).lower():
  493. return ErrorCategory.MODEL
  494. elif 'audio' in component.lower():
  495. return ErrorCategory.AUDIO
  496. elif isinstance(error, MemoryError):
  497. return ErrorCategory.MEMORY
  498. else:
  499. return ErrorCategory.SYSTEM
  500. def _determine_recovery_strategy(self, error_info: ErrorInfo) -> RecoveryStrategy:
  501. """Determine appropriate recovery strategy."""
  502. if error_info.severity == ErrorSeverity.CRITICAL:
  503. return RecoveryStrategy.RESTART
  504. elif error_info.category == ErrorCategory.NETWORK:
  505. return RecoveryStrategy.RETRY
  506. elif error_info.category in [ErrorCategory.MODEL, ErrorCategory.MEMORY]:
  507. return RecoveryStrategy.FALLBACK
  508. elif error_info.retry_count < 2:
  509. return RecoveryStrategy.RETRY
  510. else:
  511. return RecoveryStrategy.FALLBACK
  512. async def _execute_recovery_strategy(
  513. self,
  514. error_info: ErrorInfo,
  515. context: Dict[str, Any]
  516. ) -> bool:
  517. """Execute recovery strategy."""
  518. strategy = error_info.recovery_strategy
  519. if not strategy or strategy not in self._fallback_strategies:
  520. return False
  521. fallback = self._fallback_strategies[strategy]
  522. if not fallback.enabled:
  523. return False
  524. try:
  525. # Add component reference to context
  526. context['component'] = self._components.get(error_info.component)
  527. # Execute fallback strategy
  528. success = await fallback.execute(error_info, context)
  529. if success:
  530. error_info.retry_count += 1
  531. return success
  532. except Exception as e:
  533. self.logger.error(f"Error executing recovery strategy {strategy.value}: {e}")
  534. return False
  535. async def _escalate_error(self, error_info: ErrorInfo) -> None:
  536. """Escalate unresolved error."""
  537. self.logger.critical(f"Escalating unresolved error: {error_info.error_id}")
  538. # Check if we need to shut down the system
  539. critical_errors = sum(
  540. 1 for e in self._error_history
  541. if e.severity == ErrorSeverity.CRITICAL and not e.resolved
  542. )
  543. if critical_errors >= self._max_critical_errors:
  544. self.logger.critical("Maximum critical errors reached - system shutdown may be required")
  545. async def _update_component_health(self, component: str, error_info: ErrorInfo) -> None:
  546. """Update component health status."""
  547. if component in self._health_status:
  548. health = self._health_status[component]
  549. health.error_count += 1
  550. health.last_check = time.time()
  551. # Determine if component is healthy
  552. if error_info.severity in [ErrorSeverity.HIGH, ErrorSeverity.CRITICAL]:
  553. health.is_healthy = False
  554. health.issues.append(f"{error_info.error_type}: {error_info.message}")
  555. # Keep only recent issues
  556. if len(health.issues) > 10:
  557. health.issues = health.issues[-10:]
  558. def _log_error(self, error_info: ErrorInfo) -> None:
  559. """Log error with appropriate level."""
  560. log_msg = f"Error {error_info.error_id} in {error_info.component}: {error_info.message}"
  561. if error_info.severity == ErrorSeverity.CRITICAL:
  562. self.logger.critical(log_msg)
  563. elif error_info.severity == ErrorSeverity.HIGH:
  564. self.logger.error(log_msg)
  565. elif error_info.severity == ErrorSeverity.MEDIUM:
  566. self.logger.warning(log_msg)
  567. else:
  568. self.logger.info(log_msg)
  569. # Log traceback for high/critical errors
  570. if error_info.severity in [ErrorSeverity.HIGH, ErrorSeverity.CRITICAL] and error_info.traceback:
  571. self.logger.debug(f"Traceback for {error_info.error_id}:\n{error_info.traceback}")
  572. async def _health_monitoring_loop(self) -> None:
  573. """Health monitoring loop."""
  574. while True:
  575. try:
  576. await self._perform_health_checks()
  577. await asyncio.sleep(self._health_check_interval)
  578. except asyncio.CancelledError:
  579. break
  580. except Exception as e:
  581. self.logger.error(f"Error in health monitoring: {e}")
  582. await asyncio.sleep(5.0)
  583. async def _perform_health_checks(self) -> None:
  584. """Perform health checks on all components."""
  585. for component_name, component in self._components.items():
  586. try:
  587. health = self._health_status[component_name]
  588. # Update system metrics
  589. process = psutil.Process()
  590. health.memory_usage_mb = process.memory_info().rss / 1024 / 1024
  591. health.cpu_usage_percent = process.cpu_percent()
  592. # Check component-specific health
  593. if hasattr(component, 'is_running') and hasattr(component, 'get_statistics'):
  594. is_running = component.is_running()
  595. if is_running:
  596. stats = component.get_statistics()
  597. health.performance_score = self._calculate_performance_score(stats)
  598. else:
  599. health.is_healthy = False
  600. health.issues.append("Component not running")
  601. health.last_check = time.time()
  602. except Exception as e:
  603. self.logger.warning(f"Health check failed for {component_name}: {e}")
  604. def _calculate_performance_score(self, stats: Dict[str, Any]) -> float:
  605. """Calculate performance score from component statistics."""
  606. # This is a simplified performance scoring algorithm
  607. score = 1.0
  608. # Factor in error rates
  609. if 'processing_errors' in stats and 'chunks_processed' in stats:
  610. processed = stats['chunks_processed']
  611. errors = stats['processing_errors']
  612. if processed > 0:
  613. error_rate = errors / processed
  614. score *= (1.0 - min(error_rate, 0.5)) # Cap error impact at 50%
  615. # Factor in processing time
  616. if 'average_processing_time' in stats:
  617. avg_time = stats['average_processing_time']
  618. if avg_time > 0.1: # Consider 100ms as baseline
  619. score *= max(0.1, 1.0 - (avg_time - 0.1))
  620. return max(0.0, min(1.0, score))
  621. class FallbackManager:
  622. """
  623. Manager for fallback strategies and degraded operation modes.
  624. Coordinates fallback mechanisms across ML components to maintain
  625. system functionality under various failure conditions.
  626. """
  627. def __init__(self, error_handler: MLErrorHandler):
  628. """
  629. Initialize fallback manager.
  630. Args:
  631. error_handler: ML error handler instance
  632. """
  633. self.error_handler = error_handler
  634. self.logger = logging.getLogger(__name__)
  635. # Fallback configurations
  636. self._fallback_configs: Dict[str, Dict[str, Any]] = {}
  637. self._active_fallbacks: Dict[str, str] = {}
  638. self.logger.info("Fallback Manager initialized")
  639. def configure_fallbacks(self) -> None:
  640. """Configure fallback strategies for all components."""
  641. # Configure wakeword fallbacks
  642. self._fallback_configs['wakeword'] = {
  643. 'energy_detection': {
  644. 'enabled': True,
  645. 'threshold': 0.05,
  646. 'confidence_penalty': 0.3
  647. },
  648. 'silence_detection': {
  649. 'enabled': True,
  650. 'timeout': 5.0
  651. }
  652. }
  653. # Configure voice recognition fallbacks
  654. self._fallback_configs['voice_recognition'] = {
  655. 'generic_speaker': {
  656. 'enabled': True,
  657. 'default_confidence': 0.5
  658. },
  659. 'skip_identification': {
  660. 'enabled': True,
  661. 'anonymous_mode': True
  662. }
  663. }
  664. # Configure audio pipeline fallbacks
  665. self._fallback_configs['audio_pipeline'] = {
  666. 'reduced_quality': {
  667. 'enabled': True,
  668. 'sample_rate': 8000,
  669. 'channels': 1
  670. },
  671. 'basic_processing': {
  672. 'enabled': True,
  673. 'disable_enhancement': True
  674. }
  675. }
  676. self.logger.info("Fallback configurations set up")
  677. async def activate_fallback(self, component: str, fallback_type: str) -> bool:
  678. """
  679. Activate fallback for component.
  680. Args:
  681. component: Component name
  682. fallback_type: Type of fallback to activate
  683. Returns:
  684. True if fallback was activated successfully
  685. """
  686. try:
  687. config = self._fallback_configs.get(component, {}).get(fallback_type)
  688. if not config or not config.get('enabled'):
  689. return False
  690. # Activate fallback
  691. self._active_fallbacks[component] = fallback_type
  692. self.logger.info(f"Activated fallback {fallback_type} for {component}")
  693. return True
  694. except Exception as e:
  695. self.logger.error(f"Failed to activate fallback {fallback_type} for {component}: {e}")
  696. return False
  697. async def deactivate_fallback(self, component: str) -> bool:
  698. """
  699. Deactivate fallback for component.
  700. Args:
  701. component: Component name
  702. Returns:
  703. True if fallback was deactivated successfully
  704. """
  705. if component in self._active_fallbacks:
  706. fallback_type = self._active_fallbacks.pop(component)
  707. self.logger.info(f"Deactivated fallback {fallback_type} for {component}")
  708. return True
  709. return False
  710. def get_active_fallbacks(self) -> Dict[str, str]:
  711. """Get currently active fallbacks."""
  712. return self._active_fallbacks.copy()
  713. def create_ml_error_handler(logger: Optional[logging.Logger] = None) -> MLErrorHandler:
  714. """
  715. Create ML error handler.
  716. Args:
  717. logger: Logger instance
  718. Returns:
  719. ML error handler instance
  720. """
  721. return MLErrorHandler(logger)
  722. def create_fallback_manager(error_handler: MLErrorHandler) -> FallbackManager:
  723. """
  724. Create fallback manager.
  725. Args:
  726. error_handler: ML error handler
  727. Returns:
  728. Fallback manager instance
  729. """
  730. return FallbackManager(error_handler)