|
|
"""
|
|
|
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'),
|
|
|
('/static-proxy?url=https%3A%2F%2Fapi.huggingface.co%26%23x27%3B%3C%2Fspan%3E%2C '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
|
|
|
|
|
|
|