""" agent.py — Moniteur système à déployer sur le serveur de jeux. Collecte CPU, RAM, swap + usage par service, et push vers le bot Discord. Lancement : python agent.py """ import asyncio import time import aiohttp import psutil from datetime import datetime # ─── Configuration ──────────────────────────────────────────────────────────── BOT_API_URL = "http://localhost:8765/metrics" # URL du bot (même machine ou réseau local) API_SECRET = "Mon-cul-est-tres-doux" # Doit correspondre à config.json PUSH_INTERVAL = 5 # secondes entre chaque envoi # Mots-clés pour identifier chaque service dans la liste des processus # Clé = identifiant interne, valeurs = sous-chaînes à chercher dans le nom / cmdline SERVICE_KEYWORDS: dict[str, list[str]] = { "minecraft": ["minecraft", "paper", "spigot", "purpur", "fabric", "forge"], "hytale": ["hytale"], "satisfactory": ["factoryserver", "satisfactory"], "valheim": ["valheim_server", "valheim"], "terraria": ["terraria"], "ark": ["shootergame", "ark"], "ollama": ["ollama"], "discord_bots": ["discord_bot", "bot.py", "main.py"], # adapter si besoin } # ─── Collecte ───────────────────────────────────────────────────────────────── def scan_services() -> dict: """Parcourt les processus et agrège l'usage par service.""" buckets: dict[str, dict] = {k: {"running": False, "cpu": 0.0, "ram_mb": 0.0, "pids": []} for k in SERVICE_KEYWORDS} for proc in psutil.process_iter(["pid", "name", "cmdline", "cpu_percent", "memory_info", "status"]): try: name = (proc.info["name"] or "").lower() cmdline = " ".join(proc.info["cmdline"] or []).lower() text = name + " " + cmdline for svc, keywords in SERVICE_KEYWORDS.items(): if any(kw in text for kw in keywords): b = buckets[svc] b["running"] = True b["cpu"] += proc.info["cpu_percent"] or 0.0 b["ram_mb"] += (proc.info["memory_info"].rss / 1024 / 1024) if proc.info["memory_info"] else 0.0 b["pids"].append(proc.info["pid"]) break # un process ne compte que pour un service except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): continue # Nettoyage : on retire les pids de la réponse (inutiles pour le bot) for svc in buckets: buckets[svc].pop("pids", None) return buckets async def collect() -> dict: """Construit le payload complet à envoyer au bot.""" cpu = psutil.cpu_percent(interval=1) # bloquant 1s — thread OK en async mem = psutil.virtual_memory() swap = psutil.swap_memory() load = psutil.getloadavg() # (1m, 5m, 15m) temps = {} try: sensor_data = psutil.sensors_temperatures() if sensor_data: # On prend la temp CPU principale si dispo for key in ("coretemp", "k10temp", "cpu_thermal"): if key in sensor_data: temps["cpu_c"] = round(sensor_data[key][0].current, 1) break except AttributeError: pass # Windows / pas de capteurs services = scan_services() return { "timestamp": time.time(), "api_key": API_SECRET, "system": { "cpu_percent": round(cpu, 1), "cpu_count": psutil.cpu_count(logical=True), "load_avg_1m": round(load[0], 2), "load_avg_5m": round(load[1], 2), "load_avg_15m": round(load[2], 2), "ram_total_gb": round(mem.total / 1024**3, 2), "ram_used_gb": round(mem.used / 1024**3, 2), "ram_percent": mem.percent, "swap_total_gb": round(swap.total / 1024**3, 2), "swap_used_gb": round(swap.used / 1024**3, 2), "swap_percent": swap.percent, **temps, }, "services": services, } # ─── Boucle principale ──────────────────────────────────────────────────────── async def run(): # Premier appel cpu_percent sans intervalle pour initialiser le compteur psutil.cpu_percent(interval=None) await asyncio.sleep(0.5) print(f"[Agent] Démarrage — push vers {BOT_API_URL} toutes les {PUSH_INTERVAL}s") async with aiohttp.ClientSession() as session: while True: try: payload = await collect() async with session.post( BOT_API_URL, json=payload, timeout=aiohttp.ClientTimeout(total=8) ) as resp: status_str = "✓" if resp.status == 200 else f"✗ HTTP {resp.status}" ts = datetime.now().strftime("%H:%M:%S") print(f"[{ts}] CPU={payload['system']['cpu_percent']}% " f"RAM={payload['system']['ram_percent']}% → {status_str}") except aiohttp.ClientConnectorError: print(f"[{datetime.now().strftime('%H:%M:%S')}] ✗ Impossible de joindre le bot — il est lancé ?") except Exception as exc: print(f"[{datetime.now().strftime('%H:%M:%S')}] ✗ Erreur inattendue : {exc}") await asyncio.sleep(PUSH_INTERVAL) if __name__ == "__main__": asyncio.run(run())