feat(scheduler+ntp): Memory-Alerts + NTP-Status freq/rms — v1.1.107
- Scheduler: runMemoryCheck() liest /proc/meminfo, feuert Warning bei ≥85% und Critical bei ≥95% RAM-Auslastung (5-Min-Intervall, 1h dedupe pro Severity-Key) - NTP-Status-Karte: freq_ppm (±ppm mit Warnung bei >100) und rms_offset_ms werden jetzt angezeigt — Felder waren im Backend schon vorhanden, aber nie gerendert - Alerts scopeDesc: mem.high-Trigger in beiden i18n-Dateien dokumentiert Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -40,7 +40,7 @@ import (
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
||||
)
|
||||
|
||||
var version = "1.1.106"
|
||||
var version = "1.1.107"
|
||||
|
||||
const (
|
||||
// renewTickInterval — how often we re-evaluate expiring certs.
|
||||
@@ -108,6 +108,13 @@ const (
|
||||
// Dedupe 12h pro Backend → kein Alert-Spam. Frischer Alert wenn das
|
||||
// Backend nach 12h immer noch unten ist.
|
||||
backendDownCheckInterval = 2 * time.Minute
|
||||
|
||||
// memCheckInterval — alle 5 Minuten /proc/meminfo lesen. Schwellen
|
||||
// warning 85%, critical 95%. Dedupe 1h pro Severity damit bei einem
|
||||
// kurzfristigen Spike nicht jeder Tick feuert.
|
||||
memCheckInterval = 5 * time.Minute
|
||||
memWarnPct = 85.0
|
||||
memCriticalPct = 95.0
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -197,6 +204,10 @@ func main() {
|
||||
// Scheduler-Restart down ist, brauchen wir nicht 2 Minuten zu warten.
|
||||
runBackendDownCheck(ctx, pool, alertSvc, alertDedupe)
|
||||
|
||||
memTick := time.NewTicker(memCheckInterval)
|
||||
defer memTick.Stop()
|
||||
runMemoryCheck(ctx, alertSvc, alertDedupe)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-renewTick.C:
|
||||
@@ -222,6 +233,8 @@ func main() {
|
||||
runAuditCleanup(ctx, auditRepo, setupStore)
|
||||
case <-backendDownTick.C:
|
||||
runBackendDownCheck(ctx, pool, alertSvc, alertDedupe)
|
||||
case <-memTick.C:
|
||||
runMemoryCheck(ctx, alertSvc, alertDedupe)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,6 +339,68 @@ func runDiskCheck(ctx context.Context, a *alerts.Service, d *dedupe) {
|
||||
// remaining hat UND eine lokale CA existiert, wird automatisch neu
|
||||
// signiert. Restart-Hinweis als Info-Alert — wir starten edgeguard-api
|
||||
// nicht selbst neu, das passiert beim nächsten geplanten Update/Reboot.
|
||||
// runMemoryCheck liest /proc/meminfo und feuert bei hoher RAM-Belegung.
|
||||
// Schwellen: warning >= 85%, critical >= 95%. Dedupe 1h pro Severity
|
||||
// damit kurze Spikes (Backup, apt-Upgrade) keine Alert-Flut erzeugen.
|
||||
func runMemoryCheck(ctx context.Context, a *alerts.Service, d *dedupe) {
|
||||
if a == nil || d == nil {
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile("/proc/meminfo")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var memTotal, memAvail int64
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
var key string
|
||||
var val int64
|
||||
if _, err := fmt.Sscanf(line, "%s %d", &key, &val); err != nil {
|
||||
continue
|
||||
}
|
||||
switch key {
|
||||
case "MemTotal:":
|
||||
memTotal = val
|
||||
case "MemAvailable:":
|
||||
memAvail = val
|
||||
}
|
||||
}
|
||||
if memTotal <= 0 {
|
||||
return
|
||||
}
|
||||
usedPct := float64(memTotal-memAvail) * 100 / float64(memTotal)
|
||||
usedGB := float64(memTotal-memAvail) / 1024 / 1024
|
||||
totalGB := float64(memTotal) / 1024 / 1024
|
||||
|
||||
var key, title string
|
||||
var sev alerts.Severity
|
||||
switch {
|
||||
case usedPct >= memCriticalPct:
|
||||
key = "mem.high.critical"
|
||||
sev = alerts.SeverityError
|
||||
title = fmt.Sprintf("RAM kritisch hoch: %.0f%%", usedPct)
|
||||
case usedPct >= memWarnPct:
|
||||
key = "mem.high.warning"
|
||||
sev = alerts.SeverityWarning
|
||||
title = fmt.Sprintf("RAM-Belegung hoch: %.0f%%", usedPct)
|
||||
default:
|
||||
return
|
||||
}
|
||||
if !d.shouldFire(key) {
|
||||
return
|
||||
}
|
||||
desc := fmt.Sprintf(
|
||||
"RAM-Auslastung: %.1f%% — %.1f von %.1f GB belegt.\n\n"+
|
||||
"Häufige Ursachen:\n"+
|
||||
" • Unbound-Cache zu groß (rrset-cache-size in /etc/edgeguard/unbound/unbound.conf)\n"+
|
||||
" • Squid cache_mem zu groß (64 MB default)\n"+
|
||||
" • PostgreSQL shared_buffers (default ~128 MB)\n"+
|
||||
" • Prozesse prüfen: ps aux --sort=-%mem | head -10",
|
||||
usedPct, usedGB, totalGB)
|
||||
if _, err := a.Fire(ctx, "mem.high", sev, title, desc); err != nil {
|
||||
slog.Warn("scheduler: memory-check alert fire failed", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
var egBackendRE = regexp.MustCompile(`^eg_backend_(\d+)$`)
|
||||
|
||||
// runBackendDownCheck liest HAProxy-Stats via Admin-Socket und feuert
|
||||
|
||||
Reference in New Issue
Block a user