import psutil from datetime import datetime from modules.config import Config class SystemMonitor: def __init__(self, messenger, ollama_manager): self.messenger = messenger self.ollama_monitor = ollama_manager self.last_health_check = datetime.now() # CPU 서킷 브레이커 상태 self._cpu_overload_count = 0 # 연속 과부하 횟수 self._circuit_open = False # 서킷 브레이커 발동 여부 self._circuit_open_since = None def is_cpu_critical(self): """서킷 브레이커가 발동 상태인지 반환 (True이면 분석 사이클 스킵)""" return self._circuit_open def reset_circuit(self): """서킷 브레이커 수동 리셋""" if self._circuit_open: print("[Monitor] CPU Circuit Breaker RESET") self._circuit_open = False self._cpu_overload_count = 0 self._circuit_open_since = None def check_health(self): """시스템 상태 점검 및 알림 (CPU, RAM, GPU) - 3분마다 실행""" now = datetime.now() if (now - self.last_health_check).total_seconds() < 180: return self.last_health_check = now alerts = [] # 1. CPU Check cpu_usage = psutil.cpu_percent(interval=1) # 1초 측정 (더 정확) if cpu_usage > Config.CPU_CIRCUIT_BREAKER_THRESHOLD: self._cpu_overload_count += 1 # 상위 프로세스 조회 top_processes = [] for proc in psutil.process_iter(['pid', 'name', 'cpu_percent']): try: if proc.info['name'] in ('System Idle Process', 'Idle'): continue top_processes.append(proc.info) except (psutil.NoSuchProcess, psutil.AccessDenied): pass top_processes.sort(key=lambda x: x['cpu_percent'], reverse=True) top_3_str = "" for p in top_processes[:3]: top_3_str += f"\n- {p['name']} ({p['cpu_percent']}%)" # 서킷 브레이커 발동 조건 if self._cpu_overload_count >= Config.CPU_CIRCUIT_BREAKER_CONSECUTIVE: if not self._circuit_open: self._circuit_open = True self._circuit_open_since = now alerts.append( f"🔴 [CPU Circuit Breaker OPEN] {cpu_usage}% × {self._cpu_overload_count}회 연속\n" f"⛔ 분석 사이클 일시 중단 (5분 후 자동 복구)\nTop Processes:{top_3_str}" ) print(f"[Monitor] CPU Circuit Breaker OPEN! CPU={cpu_usage}%") else: alerts.append( f"⚠️ [CPU Overload] Usage: {cpu_usage}% ({self._cpu_overload_count}회)\nTop Processes:{top_3_str}" ) else: # CPU 정상 → 카운터 리셋 if self._cpu_overload_count > 0: print(f"[Monitor] CPU 정상화 ({cpu_usage}%). 카운터 리셋.") self._cpu_overload_count = 0 # 서킷 브레이커가 열린 후 5분 경과 시 자동 복구 if self._circuit_open and self._circuit_open_since: elapsed = (now - self._circuit_open_since).total_seconds() if elapsed >= 300: # 5분 self._circuit_open = False self._circuit_open_since = None alerts.append("✅ [CPU Circuit Breaker CLOSED] 시스템 안정화. 분석 재개.") print("[Monitor] CPU Circuit Breaker CLOSED. 분석 재개.") # 2. RAM Check ram = psutil.virtual_memory() if ram.percent > 90: alerts.append(f"⚠️ [RAM High] Usage: {ram.percent}% (Free: {ram.available / 1024**3:.1f}GB)") # 3. GPU Check if self.ollama_monitor: gpu_status = self.ollama_monitor.get_gpu_status() temp = gpu_status.get('temp', 0) if temp > 80: alerts.append(f"🔥 [GPU Overheat] Temp: {temp}°C") # 알림 전송 (텔레그램 비활성화 - 콘솔 로그만 사용) if alerts: # 콘솔에만 출력 for alert in alerts: print(f"[Monitor] {alert}") # [비활성화] 텔레그램 알림 - 필요시 재활성화 # msg = "🔔 [System Health Alert]\n" + "\n".join(alerts) # if self.messenger: # self.messenger.send_message(msg)