"""Performance monitoring wrapper for anime providers. This module provides a wrapper that adds automatic performance tracking to any provider implementation. """ import logging import time from typing import Any, Callable, Dict, List, Optional from src.server.providers.base_provider import Loader from src.server.providers.health_monitor import get_health_monitor logger = logging.getLogger(__name__) class MonitoredProviderWrapper(Loader): """Wrapper that adds performance monitoring to any provider.""" def __init__( self, provider: Loader, enable_monitoring: bool = True, ): """Initialize monitored provider wrapper. Args: provider: Provider instance to wrap. enable_monitoring: Whether to enable performance monitoring. """ self._provider = provider self._enable_monitoring = enable_monitoring self._health_monitor = ( get_health_monitor() if enable_monitoring else None ) logger.info( f"Monitoring wrapper initialized for provider: " f"{provider.get_site_key()}" ) def _record_operation( self, operation_name: str, start_time: float, success: bool, bytes_transferred: int = 0, error_message: Optional[str] = None, ) -> None: """Record operation metrics. Args: operation_name: Name of the operation. start_time: Operation start time (from time.time()). success: Whether operation succeeded. bytes_transferred: Number of bytes transferred. error_message: Error message if operation failed. """ if not self._enable_monitoring or not self._health_monitor: return elapsed_ms = (time.time() - start_time) * 1000 provider_name = self._provider.get_site_key() self._health_monitor.record_request( provider_name=provider_name, success=success, response_time_ms=elapsed_ms, bytes_transferred=bytes_transferred, error_message=error_message, ) if success: logger.debug( f"{operation_name} succeeded for {provider_name} " f"in {elapsed_ms:.2f}ms" ) else: logger.warning( f"{operation_name} failed for {provider_name} " f"in {elapsed_ms:.2f}ms: {error_message}" ) def search(self, word: str) -> List[Dict[str, Any]]: """Search for anime series by name (with monitoring). Args: word: Search term to look for. Returns: List of found series as dictionaries. """ start_time = time.time() try: result = self._provider.search(word) self._record_operation( operation_name="search", start_time=start_time, success=True, ) return result except Exception as e: self._record_operation( operation_name="search", start_time=start_time, success=False, error_message=str(e), ) raise def is_language( self, season: int, episode: int, key: str, language: str = "German Dub", ) -> bool: """Check if episode exists in specified language (monitored). Args: season: Season number (1-indexed). episode: Episode number (1-indexed). key: Unique series identifier/key. language: Language to check (default: German Dub). Returns: True if episode exists in specified language. """ start_time = time.time() try: result = self._provider.is_language( season, episode, key, language ) self._record_operation( operation_name="is_language", start_time=start_time, success=True, ) return result except Exception as e: self._record_operation( operation_name="is_language", start_time=start_time, success=False, error_message=str(e), ) raise def download( self, base_directory: str, serie_folder: str, season: int, episode: int, key: str, language: str = "German Dub", progress_callback: Optional[Callable[[str, Dict], None]] = None, ) -> bool: """Download episode to specified directory (with monitoring). Args: base_directory: Base directory for downloads. serie_folder: Series folder name. season: Season number. episode: Episode number. key: Unique series identifier/key. language: Language version to download. progress_callback: Optional callback for progress updates. Returns: True if download successful. """ start_time = time.time() bytes_transferred = 0 # Wrap progress callback to track bytes if progress_callback and self._enable_monitoring: def monitored_callback(event_type: str, data: Dict) -> None: nonlocal bytes_transferred if event_type == "progress" and "downloaded" in data: bytes_transferred = data.get("downloaded", 0) progress_callback(event_type, data) wrapped_callback = monitored_callback else: wrapped_callback = progress_callback try: result = self._provider.download( base_directory=base_directory, serie_folder=serie_folder, season=season, episode=episode, key=key, language=language, progress_callback=wrapped_callback, ) self._record_operation( operation_name="download", start_time=start_time, success=result, bytes_transferred=bytes_transferred, ) return result except Exception as e: self._record_operation( operation_name="download", start_time=start_time, success=False, bytes_transferred=bytes_transferred, error_message=str(e), ) raise def get_site_key(self) -> str: """Get the site key/identifier for this provider. Returns: Site key string. """ return self._provider.get_site_key() def get_title(self, key: str) -> str: """Get the human-readable title of a series. Args: key: Unique series identifier/key. Returns: Series title string. """ start_time = time.time() try: result = self._provider.get_title(key) self._record_operation( operation_name="get_title", start_time=start_time, success=True, ) return result except Exception as e: self._record_operation( operation_name="get_title", start_time=start_time, success=False, error_message=str(e), ) raise def get_season_episode_count(self, slug: str) -> Dict[int, int]: """Get season and episode counts for a series. Args: slug: Series slug/key identifier. Returns: Dictionary mapping season number to episode count. """ start_time = time.time() try: result = self._provider.get_season_episode_count(slug) self._record_operation( operation_name="get_season_episode_count", start_time=start_time, success=True, ) return result except Exception as e: self._record_operation( operation_name="get_season_episode_count", start_time=start_time, success=False, error_message=str(e), ) raise @property def wrapped_provider(self) -> Loader: """Get the underlying provider instance. Returns: Wrapped provider instance. """ return self._provider def wrap_provider( provider: Loader, enable_monitoring: bool = True, ) -> Loader: """Wrap a provider with performance monitoring. Args: provider: Provider to wrap. enable_monitoring: Whether to enable monitoring. Returns: Monitored provider wrapper. """ if isinstance(provider, MonitoredProviderWrapper): # Already wrapped return provider return MonitoredProviderWrapper( provider=provider, enable_monitoring=enable_monitoring, )