139 lines
5.7 KiB
Python
139 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())
|