""" Статистика и мониторинг системы токенов """ import asyncio from typing import Any, Dict from services.redis import redis as redis_adapter from utils.logger import root_logger as logger from .base import BaseTokenManager from .types import SCAN_BATCH_SIZE class TokenMonitoring(BaseTokenManager): """ Класс для мониторинга и статистики токенов """ async def get_token_statistics(self) -> Dict[str, Any]: """ Получает статистику по токенам для мониторинга Returns: Dict: Статистика токенов """ stats = { "session_tokens": 0, "verification_tokens": 0, "oauth_access_tokens": 0, "oauth_refresh_tokens": 0, "user_sessions": 0, "memory_usage": 0, } try: # Считаем токены по типам используя SCAN patterns = { "session_tokens": "session:*", "verification_tokens": "verification_token:*", "oauth_access_tokens": "oauth_access:*", "oauth_refresh_tokens": "oauth_refresh:*", "user_sessions": "user_sessions:*", } count_tasks = [self._count_keys_by_pattern(pattern) for pattern in patterns.values()] counts = await asyncio.gather(*count_tasks) for (stat_name, _), count in zip(patterns.items(), counts): stats[stat_name] = count # Получаем информацию о памяти Redis memory_info = await redis_adapter.execute("INFO", "MEMORY") stats["memory_usage"] = memory_info.get("used_memory", 0) except Exception as e: logger.error(f"Ошибка получения статистики токенов: {e}") return stats async def _count_keys_by_pattern(self, pattern: str) -> int: """Подсчет ключей по паттерну используя SCAN""" count = 0 cursor = 0 while True: cursor, keys = await redis_adapter.execute("scan", cursor, pattern, SCAN_BATCH_SIZE) count += len(keys) if cursor == 0: break return count async def optimize_memory_usage(self) -> Dict[str, Any]: """ Оптимизирует использование памяти Redis Returns: Dict: Результаты оптимизации """ results = {"cleaned_expired": 0, "optimized_structures": 0, "memory_saved": 0} try: # Очищаем истекшие токены from .batch import BatchTokenOperations batch_ops = BatchTokenOperations() cleaned = await batch_ops.cleanup_expired_tokens() results["cleaned_expired"] = cleaned # Оптимизируем структуры данных optimized = await self._optimize_data_structures() results["optimized_structures"] = optimized # Запускаем сборку мусора Redis await redis_adapter.execute("MEMORY", "PURGE") logger.info(f"Оптимизация памяти завершена: {results}") except Exception as e: logger.error(f"Ошибка оптимизации памяти: {e}") return results async def _optimize_data_structures(self) -> int: """Оптимизирует структуры данных Redis""" optimized_count = 0 cursor = 0 # Оптимизируем пользовательские списки сессий while True: cursor, keys = await redis_adapter.execute("scan", cursor, "user_sessions:*", SCAN_BATCH_SIZE) for key in keys: try: # Проверяем размер множества size = await redis_adapter.execute("scard", key) if size == 0: await redis_adapter.delete(key) optimized_count += 1 elif size > 100: # Слишком много сессий у одного пользователя # Оставляем только последние 50 сессий members = await redis_adapter.execute("smembers", key) if len(members) > 50: members_list = list(members) to_remove = members_list[:-50] if to_remove: await redis_adapter.srem(key, *to_remove) optimized_count += len(to_remove) except Exception as e: logger.error(f"Ошибка оптимизации ключа {key}: {e}") continue if cursor == 0: break return optimized_count async def health_check(self) -> Dict[str, Any]: """ Проверка здоровья системы токенов Returns: Dict: Результаты проверки """ health: Dict[str, Any] = { "status": "healthy", "redis_connected": False, "token_operations": False, "errors": [], } try: # Проверяем подключение к Redis await redis_adapter.ping() health["redis_connected"] = True # Тестируем основные операции с токенами from .sessions import SessionTokenManager session_manager = SessionTokenManager() test_user_id = "health_check_user" test_token = await session_manager.create_session(test_user_id) if test_token: # Проверяем валидацию valid, _ = await session_manager.validate_session_token(test_token) if valid: # Проверяем отзыв revoked = await session_manager.revoke_session_token(test_token) if revoked: health["token_operations"] = True else: health["errors"].append("Failed to revoke test token") # type: ignore[misc] else: health["errors"].append("Failed to validate test token") # type: ignore[misc] else: health["errors"].append("Failed to create test token") # type: ignore[misc] except Exception as e: health["errors"].append(f"Health check error: {e}") # type: ignore[misc] if health["errors"]: health["status"] = "unhealthy" return health