diff --git a/docs/ARCHITEKTUR.md b/docs/ARCHITEKTUR.md index 3e0d436..49b94c7 100644 --- a/docs/ARCHITEKTUR.md +++ b/docs/ARCHITEKTUR.md @@ -58,7 +58,7 @@ Client an MC2 vorbei direkt auf `:8080`. Nur den Radar-Kandidaten startet der Ra |---|---|---|---| | `mission-control-2` (`:9001`) | `backend/app.py` | Oberfläche aus `frontend/dist`; 62 `/api`-Routen; reicht `/v1` roh an den Gateway weiter (`MC_V1_UPSTREAM`); reicht das Hermes-Dashboard unter `/hermes-ui/` durch (HTTP und WebSocket); Erinnerungen; startet Update- und Download-Aufträge als eigene systemd-Einheiten `mc2-job-` (`services/jobengine.py`) | Briefkasten, Erinnerungen, Routing-Policy, Hugging-Face-Zugang, ausgeblendete Hinweise | | `mc2-gateway` (`127.0.0.1:9010`) | `backend/gateway_app.py` | `/v1` mit `model: auto` und Bild-Weiche, Kontext-Warnung, `/gw/health` | Token-Zähler (`~/.hermes/token_stats.json`) | -| `mc2-steward` | `backend/steward.py` | Re-Warm (alle 90 s, lädt das Hirn nach, wenn nichts geladen ist), Config-Watch (5 s), Wächter (jede Minute) | `mc2-waechter.json` (einziger Schreiber) | +| `mc2-steward` | `backend/steward.py` | Re-Warm (alle 90 s, lädt das Hirn nach, wenn nichts geladen ist), Config-Watch (5 s), Wächter (jede Minute), Messwerte (ein Punkt je Minute, seit 24.09.) | `mc2-waechter.json` (einziger Schreiber), `mc2-messwerte/box-*.jsonl` | | `mc2-radar` (Timer 00:30) | `backend/radar_lauf.py` | Suche und Nachttest neuer Modelle | `mc2-radar.json`, Baseline, `/srv/models/radar/` | | Bash-Schicht | `deploy/*.sh` | Updates (`autoupdate.sh` mit `update-swap.sh`, `update-engine.sh`, Postchecks, `self-repair.sh`), Sicherung (`backup.sh`, `restore.sh`), Meldungen (`notify.sh`, `morgenmeldung.sh`), Vorwärmen (`warmup.sh`), `projekte-sync.sh`, Deploy (`deploy.sh`, `pruefen.sh`) | Pins, Meldeprotokoll, Nacht-Warteschlange, Deploy-Log | @@ -172,6 +172,7 @@ Fremde Dienste, die der Box-Wart nur steuert oder überwacht: `llama-swap` (Syst | `/srv/models/mc2-deploy.log` | `deploy.sh` | eine Zeile je Deploy, auch gescheiterte | | `~/mc2-notify.log` | `notify.sh` (anhängen), `morgenmeldung.sh` (über 3 MB auf 2 MB kürzen) | Quelle des Update-Verlaufs für Läufe vor dem 24.09.; der Wortlaut „QUEUED für Morgen-Digest" muss bleiben | | `/srv/models/mc2-update-verlauf.jsonl` | `autoupdate.sh`, MC2 (Update-Knöpfe) | nur anhängen, eine JSON-Zeile je Baustein | +| `/srv/models/mc2-messwerte/box-JJJJ-MM-TT.jsonl` | nur der Steward (Box); im Homelab `/var/lib/mc2/mc2-messwerte/-…` nur MC2 | nur anhängen unter `flock`, eine Zeile je Minute; Tage älter als 8 löscht der Schreiber selbst (siehe „Monitoring“) | | `~/.hermes/night-queue.txt` | `notify.sh` (anhängen), `morgenmeldung.sh` (übernehmen, senden) | nicht gesendeter Stapel bleibt als `.senden` liegen | ## Meldeweg @@ -201,6 +202,7 @@ Partner-Instanz. In der Rolle `homelab` gibt es nur `/api/health` und die Partne | Modelle (`routers/models.py`) | `GET /models`, `GET /discover`, `POST /models/register`, `GET /hf/search`, `GET /hf/quants`, `POST /models/install`, `GET /jobs`, `POST /jobs/{id}/cancel`, `POST /models/{id}/role`, `POST /models/unload`, `POST /models/{id}/unload`, `POST /models/{id}/load`, `DELETE /models/{id}` | | Wartung (`routers/maintenance.py`) | `GET /maintenance/updates`, `GET /maintenance/update-details`, `POST` `check-updates`, `os-update`, `engine-update`, `swap-update`, `hermes-update`, `update-all`, `restart`; `GET /maintenance/logs`; `GET`/`POST /maintenance/geheimnisse` | | System (`routers/system.py`) | `GET /system/status`, `GET /system/services`, `POST /system/backup`, `GET /system/backups`, `POST /system/restart`, `GET /system/token-stats` | +| Messwerte (`routers/messwerte.py`, seit 24.09.) | `GET /messwerte?zeitraum=1h\|24h\|7d` | | Lucy und Sprache (`routers/voice.py`) | `POST /alarm`, `POST /voice/announce`, `GET /voice/announcements`, `POST /voice/stt`, `POST /voice/turn`, `POST /voice/chat`, `GET /lucy/stimme/health`, `POST /lucy/stimme/tts`, `POST /lucy/stimme/tts/stream` | | Sonstiges | `GET /health`, `GET /stream` (Live-Strom, SSE), `GET /routing`, `GET`/`POST /reminders`, `DELETE /reminders/{id}`, `GET /zeitmaschine`, `POST /zeitmaschine/restore` | | Partner (`routers/partner.py`) | `GET /partner` (Lebenszeichen), `/partner/{pfad}` (Durchreiche, alle Methoden) | @@ -234,7 +236,7 @@ flowchart LR feste Liste von Aktionen aus (`bericht`, `snapshot`, `update`, `os_update`, `suchen`, `zurueck`, `snapshot_loeschen`, `sichern`, `sicherung_zurueck`, `sicherung_loeschen`, `host_update`, `host_neustart`) und prüft selbst, ob ein Gast das Etikett `community-script` oder `watcher` trägt und nicht `watcher-aus` — dem Server - vertraut er dabei nicht. + vertraut er dabei nicht. Seit 24.09. schickt er außerdem jede Minute Messwerte (eigener Faden, siehe „Monitoring“). - **Bericht** (nur lesend, auch von Hand: `python3 ausfuehrer.py --bericht`): Host-Version und Paket-Updates, je Gast Status, IP, Etiketten, Snapshots, ob ein Snapshot geht (Bind-Mounts wie beim PBS verhindern ihn), die Community-Script-Kennung (aus `/usr/bin/update` im Gast), die App-Version (je App eigener Weg: `/root/.`, @@ -257,7 +259,8 @@ flowchart LR `apps.py` (App-Katalog: Name, GitHub-Quelle, Weboberfläche, Update-Weg), `inventar.py` (Bericht + neueste Versionen von GitHub + eigene Webprüfung → Ziele im gemeinsamen Modell; Paketlisten älter als 14 Tage = „unklar“ mit Knopf „Nach Updates suchen“), `karenz.py` (Wartezeit nach einer Skriptänderung), `updates.py` („Jetzt updaten“), - `pflege.py` (wöchentliches Suchen). Schnittstellen `routers/homelab.py` unter `/api/homelab/…`. + `pflege.py` (wöchentliches Suchen), `messwerte.py` (Minutenpunkte des Ausführers, siehe „Monitoring“). + Schnittstellen `routers/homelab.py` unter `/api/homelab/…`. - **„Jetzt updaten“** (nur per Knopf): Snapshot, wo keiner geht Sicherung (wo auch die nicht geht: ohne Rückweg, mit Warnung in der Rückfrage; scheitert der Schritt, beginnt das Update nicht) → `update` des Community-Scripts (`PHS_SILENT=1`) bzw. Pakete → 20 s warten → frischer Bericht → Prüfung (Gast läuft, Weboberfläche antwortet, @@ -284,8 +287,9 @@ flowchart LR mit `MC_ARCANE_ECHT=1`. Die Arcane-VM braucht dafür kein Etikett, weil der Ausführer nicht beteiligt ist. - **Wächter** in der Rolle `homelab`: Platte, Partner (die KI-Box), Ausführer (kein Bericht seit 30 min = rot), jede Weboberfläche der freigegebenen Gäste (außer mitten in ihrem Update-Lauf; das Ergebnis meldet der Lauf), die - Platten der laufenden Container (ab 80 % gelb, ab 90 % rot — ext4 hält 5 % für root zurück; der Ausführer schickt `platte` mit) und das - wöchentliche Suchen (gelb, wenn es wiederholt scheitert). + Platten der laufenden Container (ab 80 % gelb, ab 90 % rot — ext4 hält 5 % für root zurück; der Ausführer schickt `platte` mit), das + wöchentliche Suchen (gelb, wenn es wiederholt scheitert) und seit 24.09. den Proxmox-Host selbst aus den Messwerten + (gelb, wenn er 10 Minuten lang über 95 % RAM oder 90 °C CPU liegt). - **Oberfläche** (Bereich „Homelab“, nur Daten aus `/api/homelab/…`): - **Cockpit** (`/homelab`): oben die Lage wie das Warnpanel der KI-Box — die große Leuchte (Störung vor Hinweis vor laufendem Update vor offenen Updates) und eine Lampe je Gerät —, darunter die Hinweise des Homelab-Wächters und @@ -298,3 +302,64 @@ flowchart LR (`/etc/mc2/telegram.env`), nachts sammelt eine eigene Morgenmeldung („[Morgenmeldung Homelab]“). - **Einrichtung** (Reihenfolge, jeder Schritt braucht das User-OK): `container-anlegen.sh` → `ausrollen.sh ` → `ausfuehrer-einrichten.sh ` → `box-partner.sh `; Details in [BETRIEB.md](BETRIEB.md). + +## Monitoring: Messwerte mit Verlauf (seit 24.09.) + +Der Live-Strom der KI-Box (`/api/stream`, jede Sekunde ein Messpunkt) zeigt nur den Augenblick. Für den Verlauf +schreiben beide Bereiche jede Minute einen Punkt, gelesen wird er für die letzte Stunde, den letzten Tag oder die +letzte Woche. + +```mermaid +flowchart LR + ST["mc2-steward (Box)
jede Minute"] -->|box-…jsonl| MR[("mc2-messwerte/
kern/messreihen.py")] + AF["Ausführer (pve)
eigener Faden, jede Minute"] -->|POST …/ausfuehrer/messwerte| HL["Homelab-Teil
services/homelab/messwerte.py"] + HL -->|pve-…, ct-…, vm-…jsonl| MR2[("mc2-messwerte/
im Container")] + MR -->|GET /api/messwerte| UI["Oberfläche"] + MR2 -->|GET /api/homelab/messwerte| UI + MR2 -->|RAM, Temperatur| W["Wächter (homelab)"] +``` + +- **Speicher** `backend/kern/messreihen.py` (beide Rollen): je Quelle und Tag eine Datei + `/mc2-messwerte/-JJJJ-MM-TT.jsonl` (Tag nach Berliner Zeit), eine JSON-Zeile je Minute + (`{"t": Unix-Sekunden, "cpu": …, …}`, `null` = nicht gemessen). Schreiben hängt eine Zeile an, unter Thread-Sperre + und `flock`; fehlt am Dateiende der Zeilenumbruch (Absturz), beginnt die neue Zeile auf einer eigenen. Das erste + Schreiben eines neuen Tages löscht Dateien, deren Tag mehr als 8 Tage zurückliegt. Lesen verdichtet: `1h` in + 60-s-Schritten, `24h` als 5-min-Mittel, `7d` als 30-min-Mittel (60, 288 bzw. 336 Werte, Schritte auf vollen + Vielfachen, der letzte ist der laufende). Ein Schritt ohne Messung bleibt `null`, nichts wird aufgefüllt; kaputte + Zeilen werden übersprungen. Die Summen vergangener Tage merkt sich der Prozess je Datei, solange sie sich nicht + ändert. Gemessen wird zur halben Minute, damit jeder 60-s-Schritt genau einen Punkt bekommt. +- **KI-Box** (`services/messwerte.py`, Taktgeber im Steward; `MC_MESSWERTE_ENABLED=0` schaltet ab, ein Trocken- oder + Probelauf schreibt nie mit): `cpu` = Mittel der Minute (aus eigenen `psutil.cpu_times`-Ständen gerechnet wie + `cpu_percent`, denn psutil merkt sich den letzten Aufruf je Thread), `ram` und `platte` (Modell-Laufwerk) beim Messen, + `gpu`, `temp_cpu`, `temp_gpu` als Mittel von Proben alle 5 s (`MC_MESSWERTE_PROBE_S`), `netz_rx`/`netz_tx` in + Bytes/s über alle Schnittstellen außer `lo`, `tokens` = Prompt- plus Antwort-Tokens pro Minute (Differenz der + Gesamtzähler aus `services/token_stats.py`). Ein Zähler, der kleiner wird, ergibt in dieser Minute `null`. +- **Homelab:** Der Ausführer schickt in einem eigenen Faden jede Minute `POST /api/homelab/ausfuehrer/messwerte` + (gleiche Anmeldung wie seine anderen Endpunkte), unabhängig vom 10-min-Bericht und von Aufträgen, die bis zu einer + Stunde laufen. Host-Werte liest er selbst aus `/proc` (`stat` für das CPU-Mittel der Minute, `meminfo`, `loadavg`, + `uptime`) und `statvfs("/")`, denn `pvesh get /nodes//status` liefert in einem frischen pvesh-Prozess immer + `cpu: 0` (nachgesehen am 24.09.); belegt wie bei Proxmox: RAM = gesamt − verfügbar, rootfs = gesamt − frei. Netz des + Hosts = Summe der physischen Schnittstellen aus `/proc/net/dev` (wie die Knoten-Anzeige in Proxmox; `vmbr0` sähe den + Verkehr der Gäste nach draußen nicht), ohne physische Schnittstelle `vmbr0`. CPU-Temperatur aus hwmon (`k10temp` + bzw. `zenpower` mit Tdie vor Tctl, `coretemp` mit „Package id 0“), sonst `null`; auf dem Proxmox-PC gibt es nur + `k10temp`/Tctl, kein `thermal_zone` und kein lm-sensors. Die Gäste kommen aus einem einzigen Aufruf + `pvesh get /cluster/resources --type vm` (`cpu` schon auf die eigenen Kerne gerechnet, `mem`, `disk`, …), ihre + Netz-Zähler aber aus denselben `/proc/net/dev`-Zeilen (`vethiN`, `tapiN`; die Stände in + `/cluster/resources` sind bis zu 10 s alt und verzerrten die Minutenrate). Fehler bleiben still; ein Fehler steht + einmal im Journal, bis wieder ein Punkt durchgeht. Von Hand: `python3 ausfuehrer.py --messwerte`. +- **Homelab-Teil** (`services/homelab/messwerte.py`): rechnet die kumulativen Zähler in Bytes/s um. Keine Rate + (`null`) gibt es, wenn der Zähler oder die Laufzeit kleiner wurde (Neustart von Gast oder Host), der vorige Stand + fehlt (Neustart des Homelab-Teils; die Stände liegen nur im Speicher) oder mehr als 5 Minuten zurückliegt, oder der + Sprung über 5 GB/s liegt. Gestoppte Gäste haben keine Messwerte (`null`, keine Nullen); die Platte einer VM sieht + Proxmox nicht (`null`). Der eigene Container (Etikett `mc2`) wird wie im Inventar nicht erfasst. Quellen `pve`, + `ct-`, `vm-`. +- **Schnittstellen:** `GET /api/messwerte?zeitraum=1h|24h|7d` (nur Rolle `box`) liefert + `{zeitraum, schritt_s, reihen: {cpu, ram, gpu, temp_cpu, temp_gpu, platte, netz_rx, netz_tx, tokens}, aktuell}`; + `GET /api/homelab/messwerte?zeitraum=…` liefert `{zeitraum, schritt_s, geraete: [{id, name, art, reihen, aktuell}]}` + mit den Geräten wie im Inventar (Host, dann die Gäste nach Nummer; Namen aus `apps.py`). Jede Reihe ist eine Liste + `[t, wert]` (t = Beginn des Schritts); `temp_cpu` gibt es als Reihe nur beim Host, in `aktuell` stehen `temp_cpu` + und `load1` bei Gästen auf `null`. `aktuell` ist der letzte Minutenpunkt, solange er höchstens 3 Minuten alt ist, + sonst lauter `null`. Ein unbekannter Zeitraum ergibt 400. Neue Punkte stößt der Live-Strom nicht an: Die Oberfläche + fragt die Messwerte selbst nach (einmal je Minute genügt). +- **Wächter** (Rolle `homelab`, `pruefe_host`): gelb, wenn der Proxmox-Host in den letzten 10 Minuten (mindestens 8 + Minutenpunkte) durchgehend über 95 % RAM (`MC_WAECHTER_HOST_RAM`) oder über 90 °C CPU (`MC_WAECHTER_HOST_TEMP`) lag. diff --git a/docs/BETRIEB.md b/docs/BETRIEB.md index 63963b6..30a197e 100644 --- a/docs/BETRIEB.md +++ b/docs/BETRIEB.md @@ -81,11 +81,13 @@ soll kein Alarm sein). | Festgehaltene Updates | – | jeder Pin | | Partner-Instanz (nur mit `MC_PARTNER_URL`) | „ antwortet nicht" | – | | Sicherung (seit 24.09.) | – | `pbs-backup` scheitert; Probe-Wiederherstellung rot, älter als 40 Tage oder noch nie gelaufen (sobald ihr Timer eingerichtet ist) | +| Proxmox-Host (nur Rolle `homelab`, seit 24.09.) | – | 10 Minuten lang über 95 % RAM oder über 90 °C CPU (aus den Messwerten, mindestens 8 Minutenpunkte) | | Abgestürzte Prüfung | – | „Eine Prüfung des Wächters lief nicht" | -In der Rolle `homelab` prüft der Wächter Platte, Partner, den Ausführer und die Weboberflächen der freigegebenen -Gäste (einen Gast mitten in seinem Update-Lauf nicht: Das Ergebnis meldet der Lauf selbst) und meldet mit -„[Homelab-Problem]". Im selben Takt stößt er das wöchentliche Suchen an (siehe „Homelab-Teil im Betrieb“); scheitert +In der Rolle `homelab` prüft der Wächter Platte, Partner, den Ausführer, die Weboberflächen der freigegebenen +Gäste (einen Gast mitten in seinem Update-Lauf nicht: Das Ergebnis meldet der Lauf selbst) und seit 24.09. RAM und +CPU-Temperatur des Proxmox-Hosts (gelb, Schwellen über `MC_WAECHTER_HOST_RAM` und `MC_WAECHTER_HOST_TEMP` in +`/etc/mc2/homelab.env`) und meldet mit „[Homelab-Problem]". Im selben Takt stößt er das wöchentliche Suchen an (siehe „Homelab-Teil im Betrieb“); scheitert es für einen Gast zweimal hintereinander, gibt es einen gelben Hinweis „: Die Suche nach Updates scheitert“. - **Selbstreparatur:** Nur ein abgestürzter Dienst (`ActiveState=failed`) wird neu gestartet, höchstens 2× je Stunde @@ -257,6 +259,41 @@ laufen lassen. Achtung: `ausfuehrer-einrichten.sh` schreibt `/etc/mc2-ausfuehrer nach dem Start. Stand in `/var/lib/mc2/homelab-pflege.json` (Nacht, je Gast: letzter Tag, Fehlschläge, letzter Fehler). Schreibt nur der Steward; zum Zurücksetzen die Datei löschen und `systemctl restart mc2-homelab-steward`. +## Messwerte (Monitoring, seit 24.09.) + +Jede Minute ein Punkt je Gerät, gelesen als letzte Stunde, letzter Tag oder letzte Woche (Aufbau in +[ARCHITEKTUR.md](ARCHITEKTUR.md), Abschnitt „Monitoring“). + +- **Wo:** Box `/srv/models/mc2-messwerte/box-JJJJ-MM-TT.jsonl` (schreibt `mc2-steward`). Homelab + `/var/lib/mc2/mc2-messwerte/pve-…`, `ct--…`, `vm--…` (schreibt `mc2-homelab`, was der Ausführer jede Minute + schickt; Eigentümer `mc2`). Je Tag (Berliner Zeit) eine Datei, eine JSON-Zeile je Minute. +- **Wie groß:** eine Zeile hat rund 140 Bytes (Box, Gäste) bzw. 175 Bytes (Proxmox-Host), am Tag 1440 Zeilen. Box: + rund 0,2 MB je Tag, höchstens rund 2 MB. Homelab mit Host und 7 Gästen: rund 1,7 MB je Tag, höchstens rund 15 MB; + jeder weitere Gast rund 0,2 MB je Tag. +- **Wie lange:** Das erste Schreiben eines neuen Tages löscht die Tagesdateien, deren Tag mehr als 8 Tage zurückliegt; + es bleiben höchstens 9 je Gerät (heute und die 8 Tage davor). Nicht in der Sicherung (`backup.sh` nimmt nur + `mc2-*.json`); das Aufräumen der Modell-Platte bietet den Ordner nie zum Löschen an. Den Ordner von Hand zu löschen + ist harmlos: Er entsteht mit dem nächsten Punkt neu, der Verlauf beginnt von vorn. +- **Lücken:** Läuft der Steward, die Box oder der Ausführer nicht, bleiben die Minuten leer (`null`) und werden nicht + aufgefüllt. Nach einem Neustart des Homelab-Teils fehlt für eine Minute die Netz-Rate; nach dem Neustart eines Gasts + oder des Hosts ebenso (die Zähler beginnen von vorn). +- **Ansehen:** + +```bash +tail -n 3 /srv/models/mc2-messwerte/box-$(date +%F).jsonl # Box: die letzten Minuten +curl -s 'http://127.0.0.1:9001/api/messwerte?zeitraum=1h' | jq '.aktuell' # Box: letzter Punkt +curl -s 'http://192.168.178.31:9001/api/homelab/messwerte?zeitraum=1h' | jq '.geraete[] | {id, aktuell}' +python3 /usr/local/lib/mc2/ausfuehrer.py --messwerte # auf pve: ein Punkt, nur lesend (CPU über eine Sekunde) +journalctl -u mc2-ausfuehrer -n 50 | grep Messwerte # auf pve: steht hier etwas, gingen Punkte nicht durch +``` + +- **Ausführer:** Ein neuer Stand kommt nicht mit dem Deploy, sondern mit `bash deploy/homelab/ausfuehrer-einrichten.sh + ` (danach ein von Hand gesetztes `sicherung_speicher` wieder eintragen, siehe oben). Ein alter Ausführer schickt + keine Messwerte; dann bleibt der Homelab-Verlauf leer, sonst ändert sich nichts. +- **Schalter:** `MC_MESSWERTE_ENABLED=0` im Steward der Box schaltet das Schreiben ab; `MC_MESSWERTE_PROBE_S` (Standard + 5) ist der Abstand der Proben für GPU-Last und Temperaturen. Ein Trocken- oder Probelauf (`MC_WAECHTER_TROCKEN=1`, + `MC_PROBELAUF=1`) schreibt nie mit. + ## Sicherung - **Wann:** `mc2-backup.timer` täglich 03:30 (+≤5 min); außerdem vor jedem Hermes-Update, vor jedem Zurückspielen und @@ -383,7 +420,7 @@ Update erneut. | `~/mission-control-v2` | Checkout von `main` (nur über `deploy.sh` ändern) | | `~/mission-control-v2/backend/.venv` | Python-Umgebung des Box-Warts (Python 3.14) | | `~/.config/systemd/user/` | User-Units und Drop-ins | -| `/srv/models/` | Modelle, Box-Wart-Zustand (`mc2-*.json`, darunter `mc2-einstellungen.json` und `mc2-stand.json`), Sicherungen (`mc2-backups/`), Radar-Downloads (`radar/`) | +| `/srv/models/` | Modelle, Box-Wart-Zustand (`mc2-*.json`, darunter `mc2-einstellungen.json` und `mc2-stand.json`), Sicherungen (`mc2-backups/`), Radar-Downloads (`radar/`), Messwerte (`mc2-messwerte/`) | | `~/.config/systemd/user/mc2-autoupdate.timer.d/zeitfenster.conf` | Update-Zeitfenster aus der Oberfläche (nur MC2 schreibt es) | | `/etc/llama-swap/config.yaml` | lebende llama-swap-Config (+ `.bak-*`) | | `/opt/llamacpp-vulkan/`, `/usr/local/bin/llama-server` | Motor (llama.cpp Vulkan) |