|
|
""" |
|
|
Diagnostics & Auto-Repair Service |
|
|
---------------------------------- |
|
|
سرویس اشکالیابی خودکار و تعمیر مشکلات سیستم |
|
|
""" |
|
|
|
|
|
import asyncio |
|
|
import logging |
|
|
import os |
|
|
import subprocess |
|
|
import sys |
|
|
from dataclasses import dataclass, asdict |
|
|
from datetime import datetime |
|
|
from typing import Any, Dict, List, Optional, Tuple |
|
|
import json |
|
|
import importlib.util |
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
@dataclass |
|
|
class DiagnosticIssue: |
|
|
"""یک مشکل شناسایی شده""" |
|
|
severity: str |
|
|
category: str |
|
|
title: str |
|
|
description: str |
|
|
fixable: bool |
|
|
fix_action: Optional[str] = None |
|
|
auto_fixed: bool = False |
|
|
timestamp: str = None |
|
|
|
|
|
def __post_init__(self): |
|
|
if self.timestamp is None: |
|
|
self.timestamp = datetime.now().isoformat() |
|
|
|
|
|
|
|
|
@dataclass |
|
|
class DiagnosticReport: |
|
|
"""گزارش کامل اشکالیابی""" |
|
|
timestamp: str |
|
|
total_issues: int |
|
|
critical_issues: int |
|
|
warnings: int |
|
|
info_issues: int |
|
|
issues: List[DiagnosticIssue] |
|
|
fixed_issues: List[DiagnosticIssue] |
|
|
system_info: Dict[str, Any] |
|
|
duration_ms: float |
|
|
|
|
|
|
|
|
class DiagnosticsService: |
|
|
"""سرویس اشکالیابی و تعمیر خودکار""" |
|
|
|
|
|
def __init__(self, resource_manager=None, provider_manager=None, auto_discovery_service=None): |
|
|
self.resource_manager = resource_manager |
|
|
self.provider_manager = provider_manager |
|
|
self.auto_discovery_service = auto_discovery_service |
|
|
self.last_report: Optional[DiagnosticReport] = None |
|
|
|
|
|
async def run_full_diagnostics(self, auto_fix: bool = False) -> DiagnosticReport: |
|
|
"""اجرای کامل اشکالیابی""" |
|
|
start_time = datetime.now() |
|
|
issues: List[DiagnosticIssue] = [] |
|
|
fixed_issues: List[DiagnosticIssue] = [] |
|
|
|
|
|
|
|
|
issues.extend(await self._check_dependencies()) |
|
|
|
|
|
|
|
|
issues.extend(await self._check_configuration()) |
|
|
|
|
|
|
|
|
issues.extend(await self._check_network()) |
|
|
|
|
|
|
|
|
issues.extend(await self._check_services()) |
|
|
|
|
|
|
|
|
issues.extend(await self._check_models()) |
|
|
|
|
|
|
|
|
issues.extend(await self._check_filesystem()) |
|
|
|
|
|
|
|
|
if auto_fix: |
|
|
for issue in issues: |
|
|
if issue.fixable and issue.fix_action: |
|
|
fixed = await self._apply_fix(issue) |
|
|
if fixed: |
|
|
issue.auto_fixed = True |
|
|
fixed_issues.append(issue) |
|
|
|
|
|
|
|
|
critical = sum(1 for i in issues if i.severity == 'critical') |
|
|
warnings = sum(1 for i in issues if i.severity == 'warning') |
|
|
info_count = sum(1 for i in issues if i.severity == 'info') |
|
|
|
|
|
duration_ms = (datetime.now() - start_time).total_seconds() * 1000 |
|
|
|
|
|
report = DiagnosticReport( |
|
|
timestamp=datetime.now().isoformat(), |
|
|
total_issues=len(issues), |
|
|
critical_issues=critical, |
|
|
warnings=warnings, |
|
|
info_issues=info_count, |
|
|
issues=issues, |
|
|
fixed_issues=fixed_issues, |
|
|
system_info=await self._get_system_info(), |
|
|
duration_ms=duration_ms |
|
|
) |
|
|
|
|
|
self.last_report = report |
|
|
return report |
|
|
|
|
|
async def _check_dependencies(self) -> List[DiagnosticIssue]: |
|
|
"""بررسی وابستگیهای Python""" |
|
|
issues = [] |
|
|
required_packages = { |
|
|
'fastapi': 'FastAPI', |
|
|
'uvicorn': 'Uvicorn', |
|
|
'httpx': 'HTTPX', |
|
|
'pydantic': 'Pydantic', |
|
|
'duckduckgo_search': 'DuckDuckGo Search', |
|
|
'huggingface_hub': 'HuggingFace Hub', |
|
|
'transformers': 'Transformers', |
|
|
} |
|
|
|
|
|
for package, name in required_packages.items(): |
|
|
try: |
|
|
spec = importlib.util.find_spec(package) |
|
|
if spec is None: |
|
|
issues.append(DiagnosticIssue( |
|
|
severity='critical' if package in ['fastapi', 'uvicorn'] else 'warning', |
|
|
category='dependency', |
|
|
title=f'بسته {name} نصب نشده است', |
|
|
description=f'بسته {package} مورد نیاز است اما نصب نشده است.', |
|
|
fixable=True, |
|
|
fix_action=f'pip install {package}' |
|
|
)) |
|
|
except Exception as e: |
|
|
issues.append(DiagnosticIssue( |
|
|
severity='warning', |
|
|
category='dependency', |
|
|
title=f'خطا در بررسی {name}', |
|
|
description=f'خطا در بررسی بسته {package}: {str(e)}', |
|
|
fixable=False |
|
|
)) |
|
|
|
|
|
return issues |
|
|
|
|
|
async def _check_configuration(self) -> List[DiagnosticIssue]: |
|
|
"""بررسی تنظیمات""" |
|
|
issues = [] |
|
|
|
|
|
|
|
|
important_env_vars = { |
|
|
'HF_API_TOKEN': ('warning', 'توکن HuggingFace برای استفاده از مدلها'), |
|
|
} |
|
|
|
|
|
for var, (severity, desc) in important_env_vars.items(): |
|
|
if not os.getenv(var): |
|
|
issues.append(DiagnosticIssue( |
|
|
severity=severity, |
|
|
category='config', |
|
|
title=f'متغیر محیطی {var} تنظیم نشده', |
|
|
description=desc, |
|
|
fixable=False |
|
|
)) |
|
|
|
|
|
|
|
|
config_files = ['resources.json', 'config.json'] |
|
|
for config_file in config_files: |
|
|
if not os.path.exists(config_file): |
|
|
issues.append(DiagnosticIssue( |
|
|
severity='info', |
|
|
category='config', |
|
|
title=f'فایل پیکربندی {config_file} وجود ندارد', |
|
|
description=f'فایل {config_file} یافت نشد. ممکن است به صورت خودکار ساخته شود.', |
|
|
fixable=False |
|
|
)) |
|
|
|
|
|
return issues |
|
|
|
|
|
async def _check_network(self) -> List[DiagnosticIssue]: |
|
|
"""بررسی اتصال شبکه""" |
|
|
issues = [] |
|
|
import httpx |
|
|
|
|
|
test_urls = [ |
|
|
('https://api.coingecko.com/api/v3/ping', 'CoinGecko API'), |
|
|
('https://api.huggingface.co', 'HuggingFace API'), |
|
|
] |
|
|
|
|
|
for url, name in test_urls: |
|
|
try: |
|
|
async with httpx.AsyncClient(timeout=5.0) as client: |
|
|
response = await client.get(url) |
|
|
if response.status_code >= 400: |
|
|
issues.append(DiagnosticIssue( |
|
|
severity='warning', |
|
|
category='network', |
|
|
title=f'مشکل در اتصال به {name}', |
|
|
description=f'درخواست به {url} با کد {response.status_code} پاسخ داد.', |
|
|
fixable=False |
|
|
)) |
|
|
except Exception as e: |
|
|
issues.append(DiagnosticIssue( |
|
|
severity='warning', |
|
|
category='network', |
|
|
title=f'عدم دسترسی به {name}', |
|
|
description=f'خطا در اتصال به {url}: {str(e)}', |
|
|
fixable=False |
|
|
)) |
|
|
|
|
|
return issues |
|
|
|
|
|
async def _check_services(self) -> List[DiagnosticIssue]: |
|
|
"""بررسی سرویسها""" |
|
|
issues = [] |
|
|
|
|
|
|
|
|
if self.auto_discovery_service: |
|
|
status = self.auto_discovery_service.get_status() |
|
|
if not status.get('enabled'): |
|
|
issues.append(DiagnosticIssue( |
|
|
severity='info', |
|
|
category='service', |
|
|
title='سرویس Auto-Discovery غیرفعال است', |
|
|
description='سرویس جستجوی خودکار منابع غیرفعال است.', |
|
|
fixable=False |
|
|
)) |
|
|
elif not status.get('model'): |
|
|
issues.append(DiagnosticIssue( |
|
|
severity='warning', |
|
|
category='service', |
|
|
title='مدل HuggingFace برای Auto-Discovery تنظیم نشده', |
|
|
description='سرویس Auto-Discovery بدون مدل HuggingFace کار میکند.', |
|
|
fixable=False |
|
|
)) |
|
|
|
|
|
|
|
|
if self.provider_manager: |
|
|
stats = self.provider_manager.get_all_stats() |
|
|
summary = stats.get('summary', {}) |
|
|
if summary.get('online', 0) == 0 and summary.get('total_providers', 0) > 0: |
|
|
issues.append(DiagnosticIssue( |
|
|
severity='critical', |
|
|
category='service', |
|
|
title='هیچ Provider آنلاینی وجود ندارد', |
|
|
description='تمام Providerها آفلاین هستند.', |
|
|
fixable=False |
|
|
)) |
|
|
|
|
|
return issues |
|
|
|
|
|
async def _check_models(self) -> List[DiagnosticIssue]: |
|
|
"""بررسی وضعیت مدلهای HuggingFace""" |
|
|
issues = [] |
|
|
|
|
|
try: |
|
|
from huggingface_hub import InferenceClient, HfApi |
|
|
api = HfApi() |
|
|
|
|
|
|
|
|
models_to_check = [ |
|
|
'HuggingFaceH4/zephyr-7b-beta', |
|
|
'cardiffnlp/twitter-roberta-base-sentiment-latest', |
|
|
] |
|
|
|
|
|
for model_id in models_to_check: |
|
|
try: |
|
|
model_info = api.model_info(model_id, timeout=5.0) |
|
|
if not model_info: |
|
|
issues.append(DiagnosticIssue( |
|
|
severity='warning', |
|
|
category='model', |
|
|
title=f'مدل {model_id} در دسترس نیست', |
|
|
description=f'نمیتوان به اطلاعات مدل {model_id} دسترسی پیدا کرد.', |
|
|
fixable=False |
|
|
)) |
|
|
except Exception as e: |
|
|
issues.append(DiagnosticIssue( |
|
|
severity='warning', |
|
|
category='model', |
|
|
title=f'خطا در بررسی مدل {model_id}', |
|
|
description=f'خطا: {str(e)}', |
|
|
fixable=False |
|
|
)) |
|
|
except ImportError: |
|
|
issues.append(DiagnosticIssue( |
|
|
severity='info', |
|
|
category='model', |
|
|
title='بسته huggingface_hub نصب نشده', |
|
|
description='برای بررسی مدلها نیاز به نصب huggingface_hub است.', |
|
|
fixable=True, |
|
|
fix_action='pip install huggingface_hub' |
|
|
)) |
|
|
|
|
|
return issues |
|
|
|
|
|
async def _check_filesystem(self) -> List[DiagnosticIssue]: |
|
|
"""بررسی فایل سیستم""" |
|
|
issues = [] |
|
|
|
|
|
|
|
|
important_dirs = ['static', 'static/css', 'static/js', 'backend', 'backend/services'] |
|
|
for dir_path in important_dirs: |
|
|
if not os.path.exists(dir_path): |
|
|
issues.append(DiagnosticIssue( |
|
|
severity='warning', |
|
|
category='filesystem', |
|
|
title=f'دایرکتوری {dir_path} وجود ندارد', |
|
|
description=f'دایرکتوری {dir_path} یافت نشد.', |
|
|
fixable=True, |
|
|
fix_action=f'mkdir -p {dir_path}' |
|
|
)) |
|
|
|
|
|
|
|
|
important_files = [ |
|
|
'api_server_extended.py', |
|
|
'unified_dashboard.html', |
|
|
'static/js/websocket-client.js', |
|
|
'static/css/connection-status.css', |
|
|
] |
|
|
for file_path in important_files: |
|
|
if not os.path.exists(file_path): |
|
|
issues.append(DiagnosticIssue( |
|
|
severity='critical' if 'api_server' in file_path else 'warning', |
|
|
category='filesystem', |
|
|
title=f'فایل {file_path} وجود ندارد', |
|
|
description=f'فایل {file_path} یافت نشد.', |
|
|
fixable=False |
|
|
)) |
|
|
|
|
|
return issues |
|
|
|
|
|
async def _apply_fix(self, issue: DiagnosticIssue) -> bool: |
|
|
"""اعمال تعمیر خودکار""" |
|
|
if not issue.fixable or not issue.fix_action: |
|
|
return False |
|
|
|
|
|
try: |
|
|
if issue.fix_action.startswith('pip install'): |
|
|
|
|
|
package = issue.fix_action.replace('pip install', '').strip() |
|
|
result = subprocess.run( |
|
|
[sys.executable, '-m', 'pip', 'install', package], |
|
|
capture_output=True, |
|
|
text=True, |
|
|
timeout=60 |
|
|
) |
|
|
if result.returncode == 0: |
|
|
logger.info(f'✅ بسته {package} با موفقیت نصب شد') |
|
|
return True |
|
|
else: |
|
|
logger.error(f'❌ خطا در نصب {package}: {result.stderr}') |
|
|
return False |
|
|
|
|
|
elif issue.fix_action.startswith('mkdir'): |
|
|
|
|
|
dir_path = issue.fix_action.replace('mkdir -p', '').strip() |
|
|
os.makedirs(dir_path, exist_ok=True) |
|
|
logger.info(f'✅ دایرکتوری {dir_path} ساخته شد') |
|
|
return True |
|
|
|
|
|
else: |
|
|
logger.warning(f'⚠️ عمل تعمیر ناشناخته: {issue.fix_action}') |
|
|
return False |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f'❌ خطا در اعمال تعمیر: {e}') |
|
|
return False |
|
|
|
|
|
async def _get_system_info(self) -> Dict[str, Any]: |
|
|
"""دریافت اطلاعات سیستم""" |
|
|
import platform |
|
|
return { |
|
|
'python_version': sys.version, |
|
|
'platform': platform.platform(), |
|
|
'architecture': platform.architecture(), |
|
|
'processor': platform.processor(), |
|
|
'cwd': os.getcwd(), |
|
|
} |
|
|
|
|
|
def get_last_report(self) -> Optional[Dict[str, Any]]: |
|
|
"""دریافت آخرین گزارش""" |
|
|
if self.last_report: |
|
|
return asdict(self.last_report) |
|
|
return None |
|
|
|
|
|
|