Health-Metriken im Checkin + Jitter aufs Standardintervall

collect_health() sammelt Diskbelegung/RAM/Uptime/Load Average rein per
Stdlib (shutil.disk_usage, /proc/meminfo, /proc/uptime, os.getloadavg),
jeder Wert einzeln try/except. checkin() schickt das Ergebnis jetzt mit.
main()-Loop wendet 0-30 Min Jitter nur auf 'große' Intervalle (>=1h) an -
der kurze Debug-Modus (60s) und der Fallback-Retry bleiben unverjittert,
sonst würde Jitter den Debug-Zweck untergraben. Boot-Checkin war schon
vorher vorhanden (run_once() läuft immer vor der ersten sleep()), hier
nicht angefasst.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Thomas Stallinger 2026-08-26 10:00:48 +02:00
parent 26fabfff03
commit 1046f9f13c

View File

@ -1,5 +1,6 @@
import json
import os
import random
import shutil
import subprocess
import sys
@ -13,6 +14,12 @@ CREDENTIALS_PATH = os.environ.get(
"TUXFLOTTE_AGENT_CREDENTIALS", "/etc/tuxflotte/agent.credentials"
)
FALLBACK_INTERVAL_SECONDS = 60
# Jitter (26.08.2026-Planung): nur auf "grosse" Intervalle (Standard 6h)
# anwenden, nicht auf den kurzen Debug-Modus (60s) oder den Fallback-Retry
# (FALLBACK_INTERVAL_SECONDS) - sonst wuerde Jitter genau den Zweck des
# schnellen Debug-Zyklus untergraben.
JITTER_THRESHOLD_SECONDS = 60 * 60
JITTER_MAX_SECONDS = 30 * 60
def log(message):
@ -71,9 +78,55 @@ def bootstrap(device_id):
return json.loads(response.read().decode("utf-8"))
def collect_health():
"""
Health-Metriken fuers Checkin (26.08.2026-Planung) - reines Stdlib,
keine neue Abhaengigkeit. Jeder Einzelwert einzeln in try/except: ein
fehlschlagender Wert (z.B. Load Average auf einem ungewoehnlichen
System) darf die anderen nicht verhindern.
"""
health = {}
try:
nutzung = shutil.disk_usage("/")
health["disk_used_percent"] = round(nutzung.used / nutzung.total * 100, 1)
except OSError as exc:
log(f"Health: Diskbelegung nicht ermittelbar: {exc}")
try:
werte = {}
with open("/proc/meminfo", "r", encoding="utf-8") as f:
for zeile in f:
schluessel, _, rest = zeile.partition(":")
if schluessel in ("MemTotal", "MemAvailable"):
werte[schluessel] = int(rest.strip().split()[0])
if werte.get("MemTotal"):
belegt = werte["MemTotal"] - werte.get("MemAvailable", 0)
health["ram_used_percent"] = round(belegt / werte["MemTotal"] * 100, 1)
except (OSError, ValueError) as exc:
log(f"Health: RAM-Auslastung nicht ermittelbar: {exc}")
try:
with open("/proc/uptime", "r", encoding="utf-8") as f:
health["uptime_seconds"] = int(float(f.read().split()[0]))
except (OSError, ValueError) as exc:
log(f"Health: Uptime nicht ermittelbar: {exc}")
try:
load1, load5, load15 = os.getloadavg()
health["load_1m"] = round(load1, 2)
health["load_5m"] = round(load5, 2)
health["load_15m"] = round(load15, 2)
except OSError as exc:
log(f"Health: Load Average nicht ermittelbar: {exc}")
return health
def checkin(device_id, agent_secret):
return api_request(
"/api/v1/agent/checkin", agent_secret, {"device_id": device_id}
"/api/v1/agent/checkin", agent_secret, {"device_id": device_id, "health": collect_health()}
)
@ -205,6 +258,9 @@ def main():
log(f"Check-in-Fehler: {exc}")
interval = FALLBACK_INTERVAL_SECONDS
if interval >= JITTER_THRESHOLD_SECONDS:
interval += random.uniform(0, JITTER_MAX_SECONDS)
time.sleep(interval)