Gameurbot/agent.py

138 lines
5.7 KiB
Python

"""
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())