# P42-S.M. – Server-Monitoring-Agent Kleiner Docker-Dienst, der die Auslastung des Ubuntu-Servers misst und als JSON-API bereitstellt. **P42-F.W.** (Dashboard) liest die Werte aus und stellt sie dar. Gemessen wird alle 5 Sekunden: CPU (gesamt, je Thread, iowait, Takt, Load), RAM, Swap, Platten (Belegung je Laufwerk, Lese-/Schreibrate), Netzwerk (Rate je Schnittstelle und Summe), Temperaturen, Prozesse, Uptime. Die letzte Stunde wird als Verlauf vorgehalten. - Nur Python-Standardbibliothek, keine Pakete. Das Image ist `python:3.13-slim` plus etwa 15 KB Code. - Liest `/proc` und `/` des Hosts **nur lesend** ein und läuft ohne Root, ohne Capabilities und mit schreibgeschütztem Dateisystem. - Kein Docker-Socket: der Container sieht keine anderen Container und kann nichts steuern. ## Betrieb ### Einrichten und Starten `/pfad/zu/docker` ist ein Platzhalter – durch den Container-Ordner auf dem Server ersetzen. ```bash git clone https://github.com/guyg1474/p42.s-m.git /pfad/zu/docker/p42-sm cd /pfad/zu/docker/p42-sm ./deploy/update-docker.sh ``` Beim ersten Aufruf legt das Skript `.env` mit einem zufälligen Token an und gibt ihn aus. **Diesen Token an P42-F.W. weitergeben.** Nach etwa 10 Sekunden zeigt `docker compose ps` den Zustand `healthy`. Test auf dem Server: ```bash curl -H "Authorization: Bearer $(grep ^SM_TOKEN= .env | cut -d= -f2)" http://127.0.0.1:9142/api/metrics ``` ### Hoch- und runterfahren, beobachten ```bash docker compose stop # anhalten docker compose start # wieder starten docker compose restart # nach Änderungen an .env: besser "docker compose up -d" docker compose down # Container entfernen (das Netz "p42" bleibt bestehen) docker compose ps # Status inkl. healthy/unhealthy docker compose logs -f # Log: nur Start, Fehler und abgelehnte Anfragen docker stats p42-sm # Verbrauch des Agenten selbst (wenige MB RAM) ``` Updates: `./deploy/update-docker.sh` holt per `git pull` den neuen Stand und baut den Container neu. ### Einstellungen (`.env`) | Variable | Standard | Bedeutung | |---|---|---| | `SM_TOKEN` | – (Pflicht) | Zugangs-Token der API | | `SM_PORT` | `9142` | Port auf dem Server | | `SM_BIND` | `127.0.0.1` | An welche Adresse der Port gebunden wird. Für Abfragen aus dem LAN die Server-IP, nie `0.0.0.0` | | `SM_INTERVAL` | `5` | Messintervall in Sekunden | | `SM_HISTORY` | `720` | Anzahl Verlaufspunkte (720 × 5 s = 1 h) | | `SM_UID` | `1000` | Benutzer-ID des Containers, siehe unten | **Fehlt eine Platte in `disks`?** Der Container muss die Ordner bis zum Einhängepunkt durchsuchen dürfen. Liegt die Platte z. B. unter `/home//` (Rechte 750), muss `SM_UID` die UID dieses Benutzers sein (`id -u `). Netzlaufwerke (NFS/SMB) werden bewusst nicht gemessen. ## Anbindung an P42-F.W. ### Erreichbarkeit **A) Dashboard läuft als Container auf demselben Server (empfohlen):** Beide hängen im Docker-Netz `p42`, das die `update-docker.sh` beider Projekte bei Bedarf anlegt (Reihenfolge egal). Das Dashboard erreicht den Dienst darin unter **`http://p42-sm:9142`**, ohne offenen Port nach außen. Im Dashboard unter Monitoring → „+ Server hinzufügen“ → Prüfung „Server-Auslastung (P42-S.M.)“, Adresse `http://p42-sm:9142`, Token aus `.env`. **B) Dashboard läuft auf einem anderen Rechner:** In `.env` `SM_BIND` auf die LAN-IP des Servers setzen. Die Adresse lautet dann `http://:9142`. Per Firewall sollte nur der Dashboard-Rechner zugreifen dürfen. Abgefragt wird **serverseitig** aus dem Dashboard-Backend, nicht aus dem Browser. Token und Port bleiben so verborgen, deshalb gibt es kein CORS. ### API Alle Antworten sind JSON. Byte-Werte sind ganze Zahlen in Byte, Raten in Byte pro Sekunde, Prozente auf eine Nachkommastelle gerundet, und Zeitstempel stehen in ISO 8601 mit Zeitzone. Sinnvolles Abfrageintervall: alle 5–30 Sekunden; häufiger bringt nichts, weil nur alle `intervalSeconds` neu gemessen wird. | Anfrage | Token | Antwort | |---|---|---| | `GET /api/health` | nein | `200 {"status":"ok"}`, bzw. `503`, solange noch keine Werte da sind oder die Messung hängt | | `GET /api/metrics` | ja | aktueller Stand, siehe unten | | `GET /api/history` | ja | Verlauf; `?minutes=N` begrenzt auf die letzten N Minuten | Token als Header: `Authorization: Bearer `. Fehlt er oder ist er falsch, kommt `401`. Bis die erste Messung vorliegt (etwa 1 s nach dem Start), kommt `503`. #### `GET /api/metrics` ```json { "timestamp": "2026-10-14T14:59:01+02:00", "intervalSeconds": 5, "host": { "hostname": "server", "os": "Ubuntu 24.04.1 LTS", "kernel": "6.8.0-45-generic", "uptimeSeconds": 15943 }, "cpu": { "model": "AMD Ryzen 7 5700X 8-Core Processor", "threads": 16, "percent": 6.2, "iowaitPercent": 0.0, "perCorePercent": [2.6, 2.0, 5.5, 21.0], "mhz": 2894, "load": { "1m": 0.81, "5m": 0.91, "15m": 0.8 } }, "memory": { "totalBytes": 16682397696, "usedBytes": 6809317376, "availableBytes": 9873080320, "cachedBytes": 5789290496, "percent": 40.8 }, "swap": { "totalBytes": 536866816, "usedBytes": 0, "percent": 0.0 }, "disks": [ { "mount": "/", "device": "/dev/sda2", "fs": "ext4", "totalBytes": 983040749568, "usedBytes": 883751505920, "freeBytes": 49278058496, "percent": 94.7 } ], "diskIo": { "readBytesPerSec": 44951, "writeBytesPerSec": 73557 }, "network": { "rxBytesPerSec": 10279, "txBytesPerSec": 51353, "interfaces": [ { "interface": "eth0", "virtual": false, "rxBytesPerSec": 10279, "txBytesPerSec": 51353, "rxBytesTotal": 704405487, "txBytesTotal": 89327228 } ] }, "temperatures": [ { "sensor": "k10temp", "label": "Tctl", "celsius": 51.0, "criticalCelsius": null }, { "sensor": "nvme", "label": "Composite", "celsius": 39.85, "criticalCelsius": 79.85 } ], "processes": { "total": 429, "running": 1 } } ``` Hinweise zu den Feldern: - `memory.usedBytes` ist der tatsächlich belegte Speicher, also ohne Cache, der jederzeit freigegeben werden kann. Das entspricht der Spalte „used“ von `free`. - `disks[].percent` wird wie bei `df` berechnet. `freeBytes` ist der für Benutzer verfügbare Platz. - `network.rxBytesPerSec`/`txBytesPerSec` summiert nur echte Schnittstellen. Docker-Bridges (`docker0`, `br-*`) stehen mit `"virtual": true` in der Liste, werden aber nicht mitgezählt, sonst zählt Container-Verkehr doppelt. Die `veth*`-Schnittstellen der einzelnen Container werden gar nicht gelistet. - `temperatures`: welche Sensoren es gibt, hängt von der Hardware ab. Die Liste kann leer sein (z. B. bei VMs) und `criticalCelsius` kann `null` sein. - `cpu.mhz` ist der Durchschnitt aller Threads und kann bei VMs `null` sein. #### `GET /api/history?minutes=30` ```json { "intervalSeconds": 5, "points": [ { "timestamp": "2026-10-14T14:58:59+02:00", "cpuPercent": 5.0, "memoryPercent": 40.9, "swapPercent": 0.0, "tempMaxCelsius": 51.0, "load1m": 0.81, "rxBytesPerSec": 2769, "txBytesPerSec": 6111, "readBytesPerSec": 0, "writeBytesPerSec": 2757518 } ] } ``` Älteste Punkte zuerst. Der Verlauf liegt nur im Speicher und beginnt nach einem Neustart des Containers von vorn. Für längere Zeiträume speichert das Dashboard die Werte selbst. ## Lokal testen (ohne Docker) ```bash HOST_PROC=/proc HOST_ROOT=/ SM_TOKEN=test PORT=9142 HOST=127.0.0.1 python3 -m app ``` ## Aufbau ``` app/collector.py liest /proc und /sys, berechnet Raten aus zwei Messungen app/server.py HTTP-API, Token-Prüfung, Mess-Thread, Verlauf app/__main__.py Start, sauberes Beenden bei "docker stop" Dockerfile, docker-compose.yml deploy/update-docker.sh Bauen/Aktualisieren, legt beim ersten Mal .env mit Token an .env Token und Einstellungen (nicht ins Git) ```