From 31175af43c10db668ae62fdebf07bff5c067c019 Mon Sep 17 00:00:00 2001 From: Keda Date: Sun, 2 Nov 2025 11:16:56 +0100 Subject: [PATCH] Ajout de la persistance des pics de connexions et configuration via JSON --- README.md | 31 +++++++++++++++++++++++- src/bot.js | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0819412..7818a5b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,32 @@ # UmbraMonitor -Bot Discord de monitoring pour l'infrastructure Umbra. Il vérifie périodiquement le serveur de synchronisation, le serveur de fichiers, le serveur d'identification ainsi que les endpoints externes (dépôt Dalamud, serveur Git) et publie un message « sticky » dans un canal Discord dédié. \ No newline at end of file +Bot Discord de monitoring pour l'infrastructure Umbra. Il vérifie périodiquement le serveur de synchronisation, le serveur de fichiers, le serveur d'identification ainsi que les endpoints externes (dépôt Dalamud, serveur Git) et publie un message « sticky » dans un canal Discord dédié. + +## Persistance du « pic de connexions » + +Le bot mémorise désormais le « Pic de connexions » même après un redémarrage. + +- Stockage: un fichier JSON `peaks.json` à la racine de l'application (chemin par défaut). Vous pouvez changer l'emplacement via la variable d'environnement `PEAKS_PATH`. +- Démarrage: le pic du service "Serveur d'identification" est amorcé à 70 si aucune valeur n'est connue, comme demandé. +- Écriture: les mises à jour sont écrites de façon groupée (debounce) afin de limiter les I/O disque. + +### Exemple de configuration + +Variables d'environnement: + +``` +# Emplacement personnalisé pour la persistance (optionnel) +PEAKS_PATH=./data/peaks.json +``` + +docker-compose.yml (conseillé pour garder la persistance à travers les redéploiements): + +``` +services: + umbra-monitor-bot-js: + volumes: + - ./config.yml:/app/config.yml:ro + - ./data:/app/data +``` + +Créez le dossier `data/` localement si vous utilisez `PEAKS_PATH=./data/peaks.json`. \ No newline at end of file diff --git a/src/bot.js b/src/bot.js index 2959c7b..760b815 100644 --- a/src/bot.js +++ b/src/bot.js @@ -8,7 +8,6 @@ const yaml = require('js-yaml'); const envPath = path.resolve(__dirname, '..', '.env'); const envResult = dotenv.config({ path: envPath }); if (envResult.error) { - // Fallback to default resolution when the project root already matches process.cwd(). dotenv.config(); } @@ -23,6 +22,11 @@ const RESOLVED_CONFIG_PATH = path.isAbsolute(CONFIG_PATH) const REFRESH_INTERVAL = parseInt(process.env.REFRESH_INTERVAL || '60', 10); const STICKY_MESSAGE = String(process.env.STICKY_MESSAGE || 'true').toLowerCase() === 'true'; +const PEAKS_PATH = process.env.PEAKS_PATH || './peaks.json'; +const RESOLVED_PEAKS_PATH = path.isAbsolute(PEAKS_PATH) + ? PEAKS_PATH + : path.resolve(__dirname, '..', PEAKS_PATH); + const requiredEnv = { DISCORD_TOKEN, STATUS_GUILD_ID, @@ -660,6 +664,58 @@ const metricNameOverrides = { const connectionPeaks = new Map(); +function loadConnectionPeaks() { + try { + if (!fs.existsSync(RESOLVED_PEAKS_PATH)) return; + const text = fs.readFileSync(RESOLVED_PEAKS_PATH, 'utf8'); + if (!text.trim()) return; + const data = JSON.parse(text); + if (data && typeof data === 'object') { + for (const [k, v] of Object.entries(data)) { + const n = asFiniteNumber(v); + if (n !== null) connectionPeaks.set(k, n); + } + } + } catch (e) { + console.warn('Unable to load peaks file:', e.message || e); + } +} + +let peaksSaveTimer = null; +function scheduleSaveConnectionPeaks() { + if (peaksSaveTimer) clearTimeout(peaksSaveTimer); + peaksSaveTimer = setTimeout(() => { + try { + const obj = Object.fromEntries(connectionPeaks.entries()); + fs.writeFileSync(RESOLVED_PEAKS_PATH, JSON.stringify(obj, null, 2), 'utf8'); + } catch (e) { + console.warn('Unable to write peaks file:', e.message || e); + } + }, 300); +} + +function updateConnectionPeak(name, value) { + const n = asFiniteNumber(value); + if (n === null) return; + const prev = connectionPeaks.get(name); + const next = prev !== undefined ? Math.max(prev, n) : n; + if (prev !== next) { + connectionPeaks.set(name, next); + scheduleSaveConnectionPeaks(); + } +} + +loadConnectionPeaks(); +{ + const key = "Serveur d'identification"; + const prev = connectionPeaks.get(key); + const seeded = prev !== undefined ? Math.max(prev, 70) : 70; + if (prev !== seeded) { + connectionPeaks.set(key, seeded); + scheduleSaveConnectionPeaks(); + } +} + function pickKeyMetrics(name, all) { const perService = { 'Serveur de synchronisation': [ @@ -749,14 +805,19 @@ function formatMetrics(name, allMetrics, metricsByService, peakStore = null) { if (peakStore) { if (peakEntry) { - peakStore.set(name, peakEntry.value); + updateConnectionPeak(name, peakEntry.value); } else { const numericConnected = asFiniteNumber(usersConnectedRaw); if (numericConnected !== null) { - const prev = peakStore.get(name); + const prev = peakStore.get ? peakStore.get(name) : undefined; const nextPeak = prev !== undefined ? Math.max(prev, numericConnected) : numericConnected; - peakStore.set(name, nextPeak); + updateConnectionPeak(name, nextPeak); peakEntry = { key: '_peak_authorized_connections', value: nextPeak }; + } else { + const stored = peakStore.get ? peakStore.get(name) : undefined; + if (stored !== undefined && stored !== null) { + peakEntry = { key: '_peak_authorized_connections', value: stored }; + } } } }