- Move src/core/ → src/server/ - Split SerieList.py (531 lines) and series.py (414 lines) into src/server/database/ - Add database/models.py for SQLAlchemy models - Update all test imports to reflect new structure - Remove deprecated test files (test_serie_class.py, test_serie_folder_with_year.py)
308 lines
9.0 KiB
Python
308 lines
9.0 KiB
Python
"""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,
|
|
)
|