diff --git a/scripts/proxmox-ve.sh b/scripts/proxmox-ve.sh index c66e2dc..027dae0 100755 --- a/scripts/proxmox-ve.sh +++ b/scripts/proxmox-ve.sh @@ -1,13 +1,19 @@ #!/usr/bin/env bash # proxmox-ve.sh — VM- und Container-Metriken direkt vom PVE-Host. # -# Läuft als root auf dem PVE-Knoten und nutzt `pvesh` (lokale API mit vollen -# Rechten). Damit entfällt der API-Token komplett — kein Sys.Audit, keine ACL. +# Läuft als root auf dem PVE-Knoten und nutzt `pvesh` (lokale API, volle +# Rechte). Damit entfällt der API-Token komplett — kein Sys.Audit, keine ACL. # -# Ausgabe im Prometheus-Format (data_format = "prometheus" in der Telegraf-Conf): -# proxmox_vm_cpu_usage{vmid="1001",name="tunnel",type="qemu",status="running"} 0.42 +# Warum nicht inputs.proxmox: Das Plugin fragt zuerst /nodes//dns ab. +# Fehlt dort Sys.Audit, liefert es KOMPLETT nichts — ohne Logmeldung. # -# Aufruf durch Telegraf (inputs.exec) oder manuell zum Testen. +# Ausgabe im Prometheus-Format (data_format = "prometheus"): +# proxmox_vm_cpu_usage{vmid="1001",name="tunnel",type="qemu",status="running"} 0.38 +# +# Getestet gegen die reale Ausgabe von pvesh get /cluster/resources: +# - Top-Level ist ein ARRAY (kein {"data":...}-Wrapper) +# - qemu nutzt "cpus", lxc zusätzlich "maxswap"/"swap" +# - mem kann maxmem überschreiten (Ballooning/VirtIO) set -eu @@ -18,46 +24,62 @@ pvesh get /cluster/resources --output-format json > "$TMP" 2>/dev/null || true [ -s "$TMP" ] || exit 0 python3 - "$TMP" <<'PY' -import json, sys, re +import json, sys def esc(s): return str(s).replace('\\', '\\\\').replace('"', '\\"').replace('\n', ' ') try: - data = json.load(open(sys.argv[1])) + raw = json.load(open(sys.argv[1])) except Exception as e: print(f"# JSON-Fehler: {e}", file=sys.stderr); sys.exit(0) -rows = data.get("data", data) if isinstance(data, dict) else data +# pvesh liefert je nach Aufruf {"data":[...]} ODER direkt [...] +rows = raw.get("data", raw) if isinstance(raw, dict) else raw if not isinstance(rows, list): sys.exit(0) def emit(name, labels, value): - if value is None: return - lab = ",".join(f'{k}="{esc(v)}"' for k, v in labels.items() if v is not None) + if value is None: + return + lab = ",".join(f'{k}="{esc(v)}"' for k, v in labels.items() if v is not None and v != "") print(f"{name}{{{lab}}} {value}") for r in rows: typ = r.get("type") - if typ not in ("qemu", "lxc"): # node/storage überspringen + if typ not in ("qemu", "lxc"): # node/storage/sdn überspringen continue - if r.get("template"): # Templates nicht als Gast zählen + if r.get("template"): # Templates nicht als Gast zählen continue - if str(r.get("vmid", "")) == "": # nur Gäste mit vmid + if r.get("vmid") in (None, ""): continue + L = {"vmid": r.get("vmid"), "name": r.get("name"), "node": r.get("node"), "type": typ, "status": r.get("status")} - # cpu ist ein Anteil 0..1 der zugewiesenen Kerne + cpu = r.get("cpu") if cpu is not None: - emit("proxmox_vm_cpu_usage", L, round(float(cpu) * 100, 2)) - emit("proxmox_vm_cpu_cores", L, r.get("maxcpu")) + emit("proxmox_vm_cpu_usage_percent", L, round(float(cpu) * 100, 2)) + + # qemu: "cpus"; lxc: "cpus" oder "maxcpu" + emit("proxmox_vm_cpu_cores", L, r.get("cpus") or r.get("maxcpu")) + emit("proxmox_vm_mem_bytes", L, r.get("mem")) emit("proxmox_vm_mem_max_bytes", L, r.get("maxmem")) + emit("proxmox_vm_swap_bytes", L, r.get("swap")) + emit("proxmox_vm_swap_max_bytes", L, r.get("maxswap")) emit("proxmox_vm_disk_bytes", L, r.get("disk")) emit("proxmox_vm_disk_max_bytes", L, r.get("maxdisk")) - emit("proxmox_vm_net_in_bytes", L, r.get("netin")) - emit("proxmox_vm_net_out_bytes", L, r.get("netout")) + emit("proxmox_vm_diskread_bytes_total", L, r.get("diskread")) + emit("proxmox_vm_diskwrite_bytes_total", L, r.get("diskwrite")) + emit("proxmox_vm_net_in_bytes_total", L, r.get("netin")) + emit("proxmox_vm_net_out_bytes_total", L, r.get("netout")) emit("proxmox_vm_uptime_seconds", L, r.get("uptime")) - emit("proxmox_vm_running", L, 1 if r.get("status") == "running" else 0) + emit("proxmox_vm_running", L, 1 if r.get("status") == "running" else 0) + + # RAM-Auslastung in Prozent, robust gegen mem > maxmem (Ballooning) + mem, maxmem = r.get("mem"), r.get("maxmem") + if mem is not None and maxmem: + emit("proxmox_vm_mem_usage_percent", L, + round(min(100.0, float(mem) / float(maxmem) * 100.0), 2)) PY