V1.1 de la surveillance des performances du serveur (debug)
This commit is contained in:
parent
cd99ed12eb
commit
1c613b7133
1 changed files with 89 additions and 58 deletions
139
bot.py
139
bot.py
|
|
@ -11,6 +11,7 @@ import asyncio
|
|||
import json
|
||||
import os
|
||||
import logging
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -57,6 +58,7 @@ class StatusBot(commands.Bot):
|
|||
self.targets: dict[str, dict] = cfg.get("guilds", {})
|
||||
|
||||
self._api_runner: web.AppRunner | None = None
|
||||
self._agent_proc: asyncio.subprocess.Process | None = None
|
||||
|
||||
# ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -66,6 +68,16 @@ class StatusBot(commands.Bot):
|
|||
self.update_loop.start()
|
||||
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()
|
||||
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")
|
||||
)
|
||||
|
||||
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) ────────────────────────────────────────
|
||||
|
||||
async def _start_api_server(self):
|
||||
|
|
@ -82,10 +111,22 @@ class StatusBot(commands.Bot):
|
|||
app.router.add_post("/metrics", self._handle_metrics)
|
||||
runner = web.AppRunner(app)
|
||||
await runner.setup()
|
||||
site = web.TCPSite(runner, "0.0.0.0", self.cfg.get("api_port", 8765))
|
||||
port = self.cfg.get("api_port", 8765)
|
||||
site = web.TCPSite(runner, "0.0.0.0", port)
|
||||
|
||||
try:
|
||||
await site.start()
|
||||
self._api_runner = runner
|
||||
log.info(f"API HTTP en écoute sur le port {self.cfg.get('api_port', 8765)}")
|
||||
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:
|
||||
try:
|
||||
|
|
@ -161,79 +202,69 @@ class StatusBot(commands.Bot):
|
|||
|
||||
def _build_embed(self, r: AnalysisResult) -> discord.Embed:
|
||||
COLOR = {
|
||||
STATUS_OK: 0x2ECC71, # vert
|
||||
STATUS_WARNING: 0xF39C12, # orange
|
||||
STATUS_CRITICAL: 0xE74C3C, # rouge
|
||||
}
|
||||
|
||||
TITLE = {
|
||||
STATUS_OK: "🟢 Serveur — Tout est normal",
|
||||
STATUS_WARNING: "🟡 Serveur — Charge élevée",
|
||||
STATUS_CRITICAL: "🔴 Serveur — Surchargé",
|
||||
STATUS_OK: 0x00FF9D, # Vert Matrix / Cyberpunk
|
||||
STATUS_WARNING: 0xFFB300, # Or
|
||||
STATUS_CRITICAL: 0xFF3D00, # Rouge Vif
|
||||
}
|
||||
|
||||
# Embed Principal
|
||||
embed = discord.Embed(
|
||||
title=TITLE[r.status],
|
||||
color=COLOR[r.status],
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
)
|
||||
embed.set_footer(text="Dernière mise à jour")
|
||||
|
||||
# ── Ressources globales ──
|
||||
cpu_bar = self._bar(r.cpu, 100)
|
||||
ram_bar = self._bar(r.ram_pct, 100)
|
||||
swap_bar = self._bar(r.swap_pct, 100)
|
||||
|
||||
temp_str = f" 🌡️ {r.cpu_temp}°C" if r.cpu_temp else ""
|
||||
|
||||
resources = (
|
||||
f"`CPU ` {cpu_bar} **{r.cpu:.0f}%**{temp_str}\n"
|
||||
f"`RAM ` {ram_bar} **{r.ram_pct:.0f}%** "
|
||||
f"({r.ram_used_gb:.1f}/{r.ram_total_gb:.1f} GB)\n"
|
||||
# En-tête premium
|
||||
status_text = {
|
||||
STATUS_OK: "OPÉRATIONNEL",
|
||||
STATUS_WARNING: "CHARGE ÉLEVÉE",
|
||||
STATUS_CRITICAL: "SURCHARGE SYSTÈME",
|
||||
}
|
||||
embed.set_author(
|
||||
name=f"STATUT DU SERVEUR — {status_text[r.status]}",
|
||||
icon_url="https://cdn-icons-png.flaticon.com/512/689/689307.png" # Icone de serveur
|
||||
)
|
||||
if r.swap_pct > 5:
|
||||
resources += f"`SWAP` {swap_bar} **{r.swap_pct:.0f}%**\n"
|
||||
embed.set_footer(text="Système de monitoring Bêta • Temps réel")
|
||||
|
||||
resources += (
|
||||
f"\n📈 Charge (1m / 5m / 15m) : "
|
||||
f"`{r.load_avg[0]:.2f}` / `{r.load_avg[1]:.2f}` / `{r.load_avg[2]:.2f}`"
|
||||
# ── Ressources Système (Colonne 1) ──
|
||||
cpu_bar = self._bar(r.cpu, 100, length=8)
|
||||
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 ──
|
||||
# ── Services Actifs (Section Large) ──
|
||||
running = [s for s in r.services if s.running]
|
||||
stopped = [s for s in r.services if not s.running]
|
||||
|
||||
if running:
|
||||
lines = []
|
||||
svc_lines = []
|
||||
for svc in running:
|
||||
cpu_indicator = ""
|
||||
if svc.cpu >= 40:
|
||||
cpu_indicator = " 🔥"
|
||||
elif svc.cpu >= 20:
|
||||
cpu_indicator = " ⚡"
|
||||
lines.append(
|
||||
f"▸ {svc.name}{cpu_indicator} — "
|
||||
f"CPU **{svc.cpu:.0f}%** | RAM **{svc.ram_gb:.1f} GB**"
|
||||
icon = "🔥" if svc.cpu >= 40 else ("⚡" if svc.cpu >= 20 else "🔹")
|
||||
svc_lines.append(
|
||||
f"{icon} **{svc.name}**\n"
|
||||
f"╚ `CPU {svc.cpu:2.0f}%` `RAM {svc.ram_gb:4.1f} GB`"
|
||||
)
|
||||
embed.add_field(
|
||||
name="🎮 Services actifs",
|
||||
value="\n".join(lines),
|
||||
name="🎮 Services Actifs",
|
||||
value="\n".join(svc_lines),
|
||||
inline=False
|
||||
)
|
||||
|
||||
if stopped:
|
||||
stopped_names = " • ".join(s.name for s in stopped)
|
||||
embed.add_field(name="💤 Services arrêtés", value=stopped_names, inline=False)
|
||||
|
||||
# ── Cause ──
|
||||
# ── Diagnostics & Analyse ──
|
||||
if r.status != STATUS_OK:
|
||||
cause_text = "\n".join(r.cause_lines)
|
||||
embed.add_field(name="🔍 Pourquoi ça rame ?", value=cause_text, inline=False)
|
||||
diag_val = "\n".join([f"• {line}" for line in r.cause_lines])
|
||||
embed.add_field(name="🔍 Analyse Diagnostic", value=f"```\n{diag_val}\n```", inline=False)
|
||||
|
||||
# ── ETA ──
|
||||
embed.add_field(name="⏱️ Retour à la normale", value=r.eta_text, inline=False)
|
||||
# ── ETA & Footer ──
|
||||
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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue