diff --git a/agent.py b/agent.py index 58258c5..cf52727 100644 --- a/agent.py +++ b/agent.py @@ -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)