Skip to content

Commit 590727f

Browse files
Merge pull request #67 from 404GamerNotFound/codex/find-additional-stats-for-hacs-integration
Add swap, hardware, and local IP metrics
2 parents 132a5a8 + 00abd43 commit 590727f

File tree

12 files changed

+210
-12
lines changed

12 files changed

+210
-12
lines changed

README.de.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ Zusätzlich steht nun ein **interaktives Terminal** über die Weboberfläche zur
2525
- Sammelt:
2626
- CPU-Auslastung (%)
2727
- Speicherauslastung (%)
28+
- Swap-Nutzung (%)
2829
- Gesamter RAM (MB)
2930
- Festplattenauslastung (% für `/`)
3031
- Netzwerkdurchsatz (Bytes/s, ein- und ausgehend)
3132
- Laufzeit (Sekunden)
3233
- Temperatur (°C, falls verfügbar)
34+
- Hardware- und Sensorwerte (z. B. Lüfterdrehzahlen, Spannungen, GPU-/SoC-Temperaturen, Stromverbrauch, sofern `lm-sensors` verfügbar)
3335
- CPU-Kerne
3436
- Last (1/5/15 Minuten)
3537
- CPU-Frequenz (MHz)
@@ -150,6 +152,7 @@ Für jeden Server sind folgende Entitäten verfügbar:
150152

151153
- `sensor.<name>_cpu` – CPU-Auslastung (%)
152154
- `sensor.<name>_mem` – Speicherauslastung (%)
155+
- `sensor.<name>_swap` – Swap-Nutzung (%)
153156
- `sensor.<name>_disk` – Festplattenauslastung (%)
154157
- `sensor.<name>_net_in` – Netzwerkeingang (Bytes/s)
155158
- `sensor.<name>_net_out` – Netzwerkausgang (Bytes/s)
@@ -169,6 +172,7 @@ Für jeden Server sind folgende Entitäten verfügbar:
169172
- `sensor.<name>_vnc` – "ja", wenn ein VNC-Server erkannt wurde
170173
- `sensor.<name>_web` – "ja", wenn ein HTTP- oder HTTPS-Dienst lauscht
171174
- `sensor.<name>_ssh` – "ja", wenn der SSH-Dienst lauscht
175+
- `sensor.<name>_local_ip` – Lokale IP-Adresse
172176
- Für jeden laufenden Container: `sensor.<name>_container_<container>_cpu` (CPU-Auslastung %) und `sensor.<name>_container_<container>_mem` (Speicherauslastung %)
173177

174178
---

README.es.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ Además de la recopilación de estadísticas, el complemento incluye ahora un te
2626
- Recopila:
2727
- Uso de CPU (%)
2828
- Uso de memoria (%)
29+
- Uso de swap (%)
2930
- RAM total (MB)
3031
- Uso de disco (% para `/`)
3132
- Rendimiento de red (bytes/s de entrada y salida)
3233
- Tiempo de actividad (segundos)
3334
- Temperatura (°C, si está disponible)
35+
- Valores de hardware y sensores (velocidades de ventilador, voltajes, temperaturas de GPU/SoC, consumo eléctrico, si `lm-sensors` está disponible)
3436
- Núcleos de CPU
3537
- Carga promedio (1/5/15 min)
3638
- Frecuencia de CPU (MHz)
@@ -144,6 +146,7 @@ Para cada servidor estarán disponibles las siguientes entidades:
144146

145147
- `sensor.<name>_cpu` – Uso de CPU (%)
146148
- `sensor.<name>_mem` – Uso de memoria (%)
149+
- `sensor.<name>_swap` – Uso de swap (%)
147150
- `sensor.<name>_disk` – Uso de disco (%)
148151
- `sensor.<name>_net_in` – Tráfico de entrada (bytes/s)
149152
- `sensor.<name>_net_out` – Tráfico de salida (bytes/s)
@@ -163,6 +166,7 @@ Para cada servidor estarán disponibles las siguientes entidades:
163166
- `sensor.<name>_vnc` – "sí" si se detecta un servidor VNC
164167
- `sensor.<name>_web` – "sí" si escucha un servicio HTTP o HTTPS
165168
- `sensor.<name>_ssh` – "sí" si el servicio SSH está activo
169+
- `sensor.<name>_local_ip` – Dirección IP local
166170
- Para cada contenedor en ejecución: `sensor.<name>_container_<container>_cpu` (uso de CPU %) y `sensor.<name>_container_<container>_mem` (uso de memoria %)
167171

168172
---

README.fr.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ En plus de la collecte de statistiques, le module inclut désormais un terminal
2626
- Collecte :
2727
- Utilisation du CPU (%)
2828
- Utilisation de la mémoire (%)
29+
- Utilisation du swap (%)
2930
- RAM totale (MB)
3031
- Utilisation du disque (% pour `/`)
3132
- Débit réseau (octets/s, entrant et sortant)
3233
- Temps de fonctionnement (secondes)
3334
- Température (°C, si disponible)
35+
- Valeurs matérielles et capteurs (vitesses des ventilateurs, tensions, températures GPU/SoC, consommation électrique, si `lm-sensors` est disponible)
3436
- Cœurs CPU
3537
- Charge moyenne (1/5/15 min)
3638
- Fréquence CPU (MHz)
@@ -144,6 +146,7 @@ Pour chaque serveur, les entités suivantes seront disponibles :
144146

145147
- `sensor.<name>_cpu` – Utilisation du CPU (%)
146148
- `sensor.<name>_mem` – Utilisation de la mémoire (%)
149+
- `sensor.<name>_swap` – Utilisation du swap (%)
147150
- `sensor.<name>_disk` – Utilisation du disque (%)
148151
- `sensor.<name>_net_in` – Trafic entrant (octets/s)
149152
- `sensor.<name>_net_out` – Trafic sortant (octets/s)
@@ -163,6 +166,7 @@ Pour chaque serveur, les entités suivantes seront disponibles :
163166
- `sensor.<name>_vnc` – "oui" si un serveur VNC est détecté
164167
- `sensor.<name>_web` – "oui" si un service HTTP ou HTTPS est à l'écoute
165168
- `sensor.<name>_ssh` – "oui" si le service SSH est actif
169+
- `sensor.<name>_local_ip` – Adresse IP locale
166170
- Pour chaque conteneur en cours d'exécution : `sensor.<name>_container_<container>_cpu` (utilisation CPU %) et `sensor.<name>_container_<container>_mem` (utilisation mémoire %)
167171

168172
---

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ In addition to statistics collection, the add-on now includes an **interactive w
2828
- Collects:
2929
- CPU usage (%)
3030
- Memory usage (%)
31+
- Swap usage (%)
3132
- Total RAM (MB)
3233
- Disk usage (% for `/`)
3334
- Network throughput (bytes/s, in and out)
3435
- Uptime (seconds)
3536
- Temperature (°C, if available)
37+
- Hardware sensor values (fan speeds, voltages, GPU/SoC temperatures, power usage, if available via lm-sensors)
3638
- CPU cores
3739
- Load average (1/5/15 min)
3840
- CPU frequency (MHz)
@@ -153,8 +155,9 @@ disabled_entities:
153155

154156
For each server, the following entities will be available:
155157

156-
- `sensor.<name>_cpu` – CPU usage (%)
157-
- `sensor.<name>_mem` – Memory usage (%)
158+
- `sensor.<name>_cpu` – CPU usage (%)
159+
- `sensor.<name>_mem` – Memory usage (%)
160+
- `sensor.<name>_swap` – Swap usage (%)
158161
- `sensor.<name>_disk` – Disk usage (%)
159162
- `sensor.<name>_net_in` – Network inbound (bytes/s)
160163
- `sensor.<name>_net_out` – Network outbound (bytes/s)
@@ -174,6 +177,7 @@ For each server, the following entities will be available:
174177
- `sensor.<name>_vnc` – "yes" if a VNC server is detected
175178
- `sensor.<name>_web` – "yes" if an HTTP or HTTPS service is listening
176179
- `sensor.<name>_ssh` – "yes" if the SSH service is listening
180+
- `sensor.<name>_local_ip` – Local IP address
177181
- For each running container: `sensor.<name>_container_<container>_cpu` (CPU usage %) and `sensor.<name>_container_<container>_mem` (memory usage %)
178182

179183
---

addon/vserver_ssh_stats/app/collector.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
# Verfolgte Container pro Server für MQTT Discovery
3030
_container_discovered: Dict[str, Set[str]] = defaultdict(set)
31+
_sensor_discovered: Dict[str, Set[str]] = defaultdict(set)
3132
net_cache = NetStatsCache()
3233

3334

@@ -36,6 +37,26 @@ def _sanitize(name: str) -> str:
3637
return re.sub(r"[^a-zA-Z0-9_]+", "_", name).lower()
3738

3839

40+
def _flatten_sensors(data: Any) -> Dict[str, float]:
41+
"""Flatten lm-sensors JSON output."""
42+
result: Dict[str, float] = {}
43+
44+
def _recurse(prefix: str, obj: Any) -> None:
45+
if isinstance(obj, dict):
46+
for k, v in obj.items():
47+
ksan = _sanitize(str(k))
48+
new_prefix = f"{prefix}_{ksan}" if prefix else ksan
49+
_recurse(new_prefix, v)
50+
else:
51+
try:
52+
result[f"sensor_{prefix}"] = float(obj)
53+
except (TypeError, ValueError):
54+
pass
55+
56+
_recurse("", data)
57+
return result
58+
59+
3960
def _setup_logging() -> None:
4061
"""Configure module wide logging."""
4162
logging.basicConfig(
@@ -104,6 +125,7 @@ def ensure_discovery(name: str) -> None:
104125
for key, unit, dc, str_value in [
105126
("cpu", "%", None, False),
106127
("mem", "%", None, False),
128+
("swap", "%", None, False),
107129
("disk", "%", None, False),
108130
("net_in", "B/s", None, False),
109131
("net_out", "B/s", None, False),
@@ -123,6 +145,7 @@ def ensure_discovery(name: str) -> None:
123145
("vnc", None, None, True),
124146
("web", None, None, True),
125147
("ssh", None, None, True),
148+
("local_ip", None, None, True),
126149
]:
127150
if key in DISABLED_ENTITIES:
128151
continue
@@ -145,6 +168,30 @@ def ensure_container_discovery(name: str, containers: List[Dict[str, Any]]) -> N
145168
continue
146169
publish_discovery(name, key, unit)
147170

171+
172+
def ensure_sensor_discovery(name: str, sample: Dict[str, Any]) -> None:
173+
"""Publish discovery topics for hardware sensors."""
174+
if not client:
175+
return
176+
known = _sensor_discovered[name]
177+
for key in sample.keys():
178+
if not key.startswith("sensor_") or key in known or key in DISABLED_ENTITIES:
179+
continue
180+
known.add(key)
181+
lower = key.lower()
182+
unit = None
183+
device_class = None
184+
if "temp" in lower:
185+
unit = "°C"
186+
device_class = "temperature"
187+
elif "fan" in lower:
188+
unit = "RPM"
189+
elif "power" in lower:
190+
unit = "W"
191+
elif lower.startswith("sensor_in") or "volt" in lower:
192+
unit = "V"
193+
publish_discovery(name, key, unit, device_class)
194+
148195
# ---------- SSH ----------
149196
def run_ssh(
150197
host: str,
@@ -201,11 +248,13 @@ def sample_server(srv: Dict[str, Any]) -> Dict[str, Any]:
201248
# Netzraten berechnen (Bytes/s)
202249
now = time.time()
203250
net_in, net_out = net_cache.compute(srv["name"], data["rx"], data["tx"], now)
251+
sensors = _flatten_sensors(data.get("sensors", {}))
204252

205253
# Antwort reduzieren
206-
return {
254+
result = {
207255
"cpu": int(data["cpu"]),
208256
"mem": int(data["mem"]),
257+
"swap": int(data.get("swap", 0)),
209258
"disk": int(data["disk"]),
210259
"uptime": int(data["uptime"]),
211260
"temp": (None if data["temp"] is None else float(data["temp"])),
@@ -225,8 +274,11 @@ def sample_server(srv: Dict[str, Any]) -> Dict[str, Any]:
225274
"vnc": data.get("vnc", ""),
226275
"web": data.get("web", ""),
227276
"ssh": data.get("ssh", ""),
277+
"local_ip": data.get("local_ip", ""),
228278
"container_stats": data.get("container_stats", []),
229279
}
280+
result.update(sensors)
281+
return result
230282

231283
def main():
232284
global client
@@ -242,7 +294,9 @@ def main():
242294
# initiale Netzbasis holen (damit ab dem 2. Tick Raten stimmen)
243295
for s in SERVERS:
244296
try:
245-
_ = sample_server(s)
297+
initial = sample_server(s)
298+
if client:
299+
ensure_sensor_discovery(s["name"], initial)
246300
except Exception:
247301
pass
248302

@@ -263,6 +317,7 @@ def main():
263317
mqtt_payload.pop(k, None)
264318
if client:
265319
ensure_container_discovery(s["name"], cont_stats)
320+
ensure_sensor_discovery(s["name"], payload)
266321
for c in cont_stats:
267322
cname = _sanitize(c.get("name", ""))
268323
for metric in ("cpu", "mem"):
@@ -290,6 +345,7 @@ def main():
290345
err = {
291346
"cpu": 0,
292347
"mem": 0,
348+
"swap": 0,
293349
"disk": 0,
294350
"uptime": 0,
295351
"temp": None,
@@ -305,6 +361,7 @@ def main():
305361
"vnc": "",
306362
"web": "",
307363
"ssh": "",
364+
"local_ip": "",
308365
"container_stats": [],
309366
}
310367
err_payload = err.copy()

addon/vserver_ssh_stats/app/remote_script.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@
1919
mem_avail=$(awk '/MemAvailable/ {print $2}' /proc/meminfo)
2020
if [ -z "$mem_avail" ]; then mem_avail=$(awk '/MemFree/ {print $2}' /proc/meminfo); fi
2121
mem=$(( (100*(mem_total - mem_avail) + mem_total/2) / mem_total ))
22+
# Swap %
23+
swap_total=$(awk '/SwapTotal/ {print $2}' /proc/meminfo)
24+
swap_free=$(awk '/SwapFree/ {print $2}' /proc/meminfo)
25+
if [ -n "$swap_total" ] && [ "$swap_total" -gt 0 ]; then
26+
swap=$(( (100*(swap_total - swap_free) + swap_total/2) / swap_total ))
27+
else
28+
swap=0
29+
fi
2230
# RAM total MB
2331
ram=$(( (mem_total + 512) / 1024 ))
2432
@@ -116,11 +124,25 @@
116124
if [ -n "$t" ]; then temp=$(awk -v v="$t" 'BEGIN{printf "%.1f", (v>=1000?v/1000:v)}'); fi
117125
fi
118126
127+
# Hardware sensors (best-effort via lm-sensors)
128+
sensors_json="{}"
129+
if command -v sensors >/dev/null 2>&1; then
130+
sj=$(sensors -j 2>/dev/null | tr -d '\n')
131+
if [ -n "$sj" ]; then sensors_json=$sj; fi
132+
fi
133+
119134
# NET (Summen Bytes RX/TX über alle nicht-lo Interfaces)
120135
rx=$(awk -F'[: ]+' '/:/{if($1!="lo"){rx+=$3; tx+=$11}} END{print rx+0}' /proc/net/dev)
121136
tx=$(awk -F'[: ]+' '/:/{if($1!="lo"){rx+=$3; tx+=$11}} END{print tx+0}' /proc/net/dev)
122137
138+
# Lokale IP-Adresse (erste nicht-lo IPv4)
139+
local_ip=$(hostname -I 2>/dev/null | awk '{print $1}')
140+
if [ -z "$local_ip" ]; then
141+
local_ip=$(ip -4 addr show scope global | awk '/inet /{print $2}' | cut -d/ -f1 | head -n1)
142+
fi
143+
local_ip_json=$(printf '%s' "$local_ip" | sed 's/"/\\"/g')
144+
123145
if [ -n "$temp" ]; then temp_json=$temp; else temp_json=null; fi
124-
printf '{"cpu":%s,"mem":%s,"disk":%s,"uptime":%s,"temp":%s,"rx":%s,"tx":%s,"ram":%s,"cores":%s,"load_1":%s,"load_5":%s,"load_15":%s,"cpu_freq":%s,"os":"%s","pkg_count":%s,"pkg_list":"%s","docker":%s,"containers":"%s","container_stats":%s,"vnc":"%s","web":"%s","ssh":"%s"}\n' \
125-
"$cpu" "$mem" "$disk" "$uptime" "$temp_json" "$rx" "$tx" "$ram" "$cores" "$load_1" "$load_5" "$load_15" "$cpu_freq_json" "$os_json" "$pkg_count" "$pkg_list_json" "$docker" "$containers_json" "$container_stats_json" "$vnc" "$web" "$ssh_enabled"
146+
printf '{"cpu":%s,"mem":%s,"swap":%s,"disk":%s,"uptime":%s,"temp":%s,"rx":%s,"tx":%s,"ram":%s,"cores":%s,"load_1":%s,"load_5":%s,"load_15":%s,"cpu_freq":%s,"os":"%s","pkg_count":%s,"pkg_list":"%s","docker":%s,"containers":"%s","container_stats":%s,"vnc":"%s","web":"%s","ssh":"%s","local_ip":"%s","sensors":%s}\n' \
147+
"$cpu" "$mem" "$swap" "$disk" "$uptime" "$temp_json" "$rx" "$tx" "$ram" "$cores" "$load_1" "$load_5" "$load_15" "$cpu_freq_json" "$os_json" "$pkg_count" "$pkg_list_json" "$docker" "$containers_json" "$container_stats_json" "$vnc" "$web" "$ssh_enabled" "$local_ip_json" "$sensors_json"
126148
'''

addon/vserver_ssh_stats/app/simple_collector.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,26 @@ def _sanitize(name: str) -> str:
1919
"""Return a lowercase, safe name for keys."""
2020
return re.sub(r"[^a-zA-Z0-9_]+", "_", name).lower()
2121

22+
23+
def _flatten_sensors(data: Any) -> Dict[str, float]:
24+
"""Flatten lm-sensors JSON output."""
25+
result: Dict[str, float] = {}
26+
27+
def _recurse(prefix: str, obj: Any) -> None:
28+
if isinstance(obj, dict):
29+
for k, v in obj.items():
30+
ksan = _sanitize(str(k))
31+
new_prefix = f"{prefix}_{ksan}" if prefix else ksan
32+
_recurse(new_prefix, v)
33+
else:
34+
try:
35+
result[f"sensor_{prefix}"] = float(obj)
36+
except (TypeError, ValueError):
37+
pass
38+
39+
_recurse("", data)
40+
return result
41+
2242
# ---------- SSH ----------
2343
def run_ssh(
2444
host: str,
@@ -56,9 +76,11 @@ def sample(host: str, username: str, password: Optional[str], key: Optional[str]
5676
now = time.time()
5777
net_in, net_out = net_cache.compute(host, data["rx"], data["tx"], now)
5878
cont_stats = data.get("container_stats", [])
79+
sensors = _flatten_sensors(data.get("sensors", {}))
5980
result = {
6081
"cpu": int(data["cpu"]),
6182
"mem": int(data["mem"]),
83+
"swap": int(data.get("swap", 0)),
6284
"disk": int(data["disk"]),
6385
"uptime": int(data["uptime"]),
6486
"temp": (None if data["temp"] is None else float(data["temp"])),
@@ -74,12 +96,14 @@ def sample(host: str, username: str, password: Optional[str], key: Optional[str]
7496
"vnc": data.get("vnc", ""),
7597
"web": data.get("web", ""),
7698
"ssh": data.get("ssh", ""),
99+
"local_ip": data.get("local_ip", ""),
77100
"container_stats": cont_stats,
78101
}
79102
for c in cont_stats:
80103
cname = _sanitize(c.get("name", ""))
81104
result[f"container_{cname}_cpu"] = c.get("cpu", 0)
82105
result[f"container_{cname}_mem"] = c.get("mem", 0)
106+
result.update(sensors)
83107
return result
84108

85109
def main():
@@ -136,6 +160,7 @@ def send_to_home_assistant(base_url: str, token: str, name: str, data: Dict[str,
136160
units = {
137161
"cpu": "%",
138162
"mem": "%",
163+
"swap": "%",
139164
"disk": "%",
140165
"net_in": "B/s",
141166
"net_out": "B/s",

addon/vserver_ssh_stats/config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: VServer SSH Stats
2-
version: "1.1.15"
2+
version: "1.1.18"
33
slug: vserver_ssh_stats
44
description: Holt CPU, RAM, Disk, Net per SSH (ohne Agent) und veröffentlicht sie via MQTT.
55
startup: services
@@ -35,7 +35,7 @@ schema:
3535
mqtt_pass: str
3636
interval_seconds: int
3737
disabled_entities:
38-
- match(cpu|mem|disk|net_in|net_out|uptime|temp|ram|cores|load_1|load_5|load_15|cpu_freq|os|pkg_count|pkg_list)
38+
- match(cpu|mem|swap|disk|net_in|net_out|uptime|temp|ram|cores|load_1|load_5|load_15|cpu_freq|os|pkg_count|pkg_list|local_ip|sensor_.*)
3939
servers:
4040
- name: str
4141
host: str

custom_components/vserver_ssh_stats/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212
"requirements": [
1313
"paramiko>=3.4.0"
1414
],
15-
"version": "1.1.15"
15+
"version": "1.1.18"
1616
}

0 commit comments

Comments
 (0)