V1.1 de la surveillance des performances du serveur (debug)

This commit is contained in:
Mathis 2026-02-26 18:59:38 +01:00
parent cd99ed12eb
commit 1c613b7133

145
bot.py
View file

@ -11,6 +11,7 @@ import asyncio
import json import json
import os import os
import logging import logging
import sys
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
@ -57,6 +58,7 @@ class StatusBot(commands.Bot):
self.targets: dict[str, dict] = cfg.get("guilds", {}) self.targets: dict[str, dict] = cfg.get("guilds", {})
self._api_runner: web.AppRunner | None = None self._api_runner: web.AppRunner | None = None
self._agent_proc: asyncio.subprocess.Process | None = None
# ── Lifecycle ───────────────────────────────────────────────────────────── # ── Lifecycle ─────────────────────────────────────────────────────────────
@ -66,6 +68,16 @@ class StatusBot(commands.Bot):
self.update_loop.start() self.update_loop.start()
log.info("Services de monitoring démarrés.") log.info("Services de monitoring démarrés.")
# Automation : Lancement de l'agent en tâche de fond
try:
self._agent_proc = await asyncio.create_subprocess_exec(
sys.executable, "agent.py",
stdout=None, stderr=None # L'agent loggera dans son propre flux si besoin
)
log.info(f"✅ Agent lancé automatiquement (PID {self._agent_proc.pid})")
except Exception as e:
log.error(f"❌ Impossible de lancer l'agent automatiquement : {e}")
await self.tree.sync() await self.tree.sync()
log.info("Slash commands synced.") log.info("Slash commands synced.")
@ -75,6 +87,23 @@ class StatusBot(commands.Bot):
activity=discord.Activity(type=discord.ActivityType.watching, name="l'état du serveur") activity=discord.Activity(type=discord.ActivityType.watching, name="l'état du serveur")
) )
async def close(self):
"""Action à la fermeture du bot (propre)."""
if self._agent_proc:
log.info("Arrêt de l'agent...")
try:
self._agent_proc.terminate()
await self._agent_proc.wait()
except Exception:
pass
if self._api_runner:
log.info("Arrêt du serveur API HTTP...")
if hasattr(self, "_api_site") and self._api_site:
await self._api_site.stop()
await self._api_runner.cleanup()
await super().close()
# ── API HTTP (réception métriques) ──────────────────────────────────────── # ── API HTTP (réception métriques) ────────────────────────────────────────
async def _start_api_server(self): async def _start_api_server(self):
@ -82,10 +111,22 @@ class StatusBot(commands.Bot):
app.router.add_post("/metrics", self._handle_metrics) app.router.add_post("/metrics", self._handle_metrics)
runner = web.AppRunner(app) runner = web.AppRunner(app)
await runner.setup() await runner.setup()
site = web.TCPSite(runner, "0.0.0.0", self.cfg.get("api_port", 8765)) port = self.cfg.get("api_port", 8765)
await site.start() site = web.TCPSite(runner, "0.0.0.0", port)
self._api_runner = runner
log.info(f"API HTTP en écoute sur le port {self.cfg.get('api_port', 8765)}") try:
await site.start()
self._api_runner = runner
self._api_site = site
log.info(f"✅ API HTTP en écoute sur le port {port}")
except OSError as e:
if e.errno == 98:
log.critical(f"❌ IMPOSSIBLE DE DÉMARRER L'API : Le port {port} est déjà utilisé.")
log.critical("Vérifiez qu'une autre instance du bot n'est pas déjà lancée.")
else:
log.critical(f"❌ Erreur lors du démarrage de l'API : {e}")
# On peut choisir de fermer le bot ici si l'API est critique
await self.close()
async def _handle_metrics(self, request: web.Request) -> web.Response: async def _handle_metrics(self, request: web.Request) -> web.Response:
try: try:
@ -161,79 +202,69 @@ class StatusBot(commands.Bot):
def _build_embed(self, r: AnalysisResult) -> discord.Embed: def _build_embed(self, r: AnalysisResult) -> discord.Embed:
COLOR = { COLOR = {
STATUS_OK: 0x2ECC71, # vert STATUS_OK: 0x00FF9D, # Vert Matrix / Cyberpunk
STATUS_WARNING: 0xF39C12, # orange STATUS_WARNING: 0xFFB300, # Or
STATUS_CRITICAL: 0xE74C3C, # rouge STATUS_CRITICAL: 0xFF3D00, # Rouge Vif
}
TITLE = {
STATUS_OK: "🟢 Serveur — Tout est normal",
STATUS_WARNING: "🟡 Serveur — Charge élevée",
STATUS_CRITICAL: "🔴 Serveur — Surchargé",
} }
# Embed Principal
embed = discord.Embed( embed = discord.Embed(
title=TITLE[r.status],
color=COLOR[r.status], color=COLOR[r.status],
timestamp=datetime.now(timezone.utc), timestamp=datetime.now(timezone.utc),
) )
embed.set_footer(text="Dernière mise à jour")
# ── Ressources globales ── # En-tête premium
cpu_bar = self._bar(r.cpu, 100) status_text = {
ram_bar = self._bar(r.ram_pct, 100) STATUS_OK: "OPÉRATIONNEL",
swap_bar = self._bar(r.swap_pct, 100) STATUS_WARNING: "CHARGE ÉLEVÉE",
STATUS_CRITICAL: "SURCHARGE SYSTÈME",
temp_str = f" 🌡️ {r.cpu_temp}°C" if r.cpu_temp else "" }
embed.set_author(
resources = ( name=f"STATUT DU SERVEUR — {status_text[r.status]}",
f"`CPU ` {cpu_bar} **{r.cpu:.0f}%**{temp_str}\n" icon_url="https://cdn-icons-png.flaticon.com/512/689/689307.png" # Icone de serveur
f"`RAM ` {ram_bar} **{r.ram_pct:.0f}%** "
f"({r.ram_used_gb:.1f}/{r.ram_total_gb:.1f} GB)\n"
) )
if r.swap_pct > 5: embed.set_footer(text="Système de monitoring Bêta • Temps réel")
resources += f"`SWAP` {swap_bar} **{r.swap_pct:.0f}%**\n"
resources += ( # ── Ressources Système (Colonne 1) ──
f"\n📈 Charge (1m / 5m / 15m) : " cpu_bar = self._bar(r.cpu, 100, length=8)
f"`{r.load_avg[0]:.2f}` / `{r.load_avg[1]:.2f}` / `{r.load_avg[2]:.2f}`" ram_bar = self._bar(r.ram_pct, 100, length=8)
temp_str = f" • 🌡️ `{r.cpu_temp}°C`" if r.cpu_temp else ""
load_str = f"`{r.load_avg[0]:.2f}` | `{r.load_avg[1]:.2f}` | `{r.load_avg[2]:.2f}`"
res_val = (
f"**CPU** {cpu_bar} **{r.cpu:.0f}%**{temp_str}\n"
f"**RAM** {ram_bar} **{r.ram_pct:.0f}%**\n"
f"┗ `{r.ram_used_gb:.1f} / {r.ram_total_gb:.1f} GB`"
) )
embed.add_field(name="🚀 Ressources", value=res_val, inline=True)
embed.add_field(name="📈 Charge Load", value=f"\n{load_str}", inline=True)
embed.add_field(name="📊 Ressources", value=resources, inline=False) # ── Services Actifs (Section Large) ──
running = [s for s in r.services if s.running]
# ── Services ──
running = [s for s in r.services if s.running]
stopped = [s for s in r.services if not s.running]
if running: if running:
lines = [] svc_lines = []
for svc in running: for svc in running:
cpu_indicator = "" icon = "🔥" if svc.cpu >= 40 else ("" if svc.cpu >= 20 else "🔹")
if svc.cpu >= 40: svc_lines.append(
cpu_indicator = " 🔥" f"{icon} **{svc.name}**\n"
elif svc.cpu >= 20: f"╚ `CPU {svc.cpu:2.0f}%` `RAM {svc.ram_gb:4.1f} GB`"
cpu_indicator = ""
lines.append(
f"{svc.name}{cpu_indicator}"
f"CPU **{svc.cpu:.0f}%** | RAM **{svc.ram_gb:.1f} GB**"
) )
embed.add_field( embed.add_field(
name="🎮 Services actifs", name="🎮 Services Actifs",
value="\n".join(lines), value="\n".join(svc_lines),
inline=False inline=False
) )
if stopped: # ── Diagnostics & Analyse ──
stopped_names = "".join(s.name for s in stopped)
embed.add_field(name="💤 Services arrêtés", value=stopped_names, inline=False)
# ── Cause ──
if r.status != STATUS_OK: if r.status != STATUS_OK:
cause_text = "\n".join(r.cause_lines) diag_val = "\n".join([f"{line}" for line in r.cause_lines])
embed.add_field(name="🔍 Pourquoi ça rame ?", value=cause_text, inline=False) embed.add_field(name="🔍 Analyse Diagnostic", value=f"```\n{diag_val}\n```", inline=False)
# ── ETA ── # ── ETA & Footer ──
embed.add_field(name="⏱️ Retour à la normale", value=r.eta_text, inline=False) eta_icon = "🟢" if r.status == STATUS_OK else ""
embed.add_field(name="⏱️ Prévision", value=f"{eta_icon} {r.eta_text}", inline=False)
return embed return embed