fix(beacon): convert symlink to standalone executable with WAN bridge and phone detection
This commit is contained in:
@@ -1 +0,0 @@
|
||||
/home/kof/src/observator/otop/fleet-beacon.py
|
||||
Executable
+189
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
fleet-beacon.py — Multi-node Realtime Telemetry Beacon
|
||||
Part of Observatoř Top (otop)
|
||||
|
||||
Broadcasts lightweight UDP telemetry packets (port 9850) every 1.5s.
|
||||
Zero external dependencies, minimal CPU impact (< 0.05%).
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import socket
|
||||
import shutil
|
||||
import glob
|
||||
import urllib.request
|
||||
|
||||
PORT = 9850
|
||||
TARGETS = ["<broadcast>", "192.168.1.255", "192.168.1.100", "192.168.1.102", "10.0.0.255", "10.0.0.18", "10.0.0.14", "91.139.118.43", "192.168.43.255", "192.168.43.1", "127.0.0.1"]
|
||||
|
||||
def get_hostname():
|
||||
if os.path.exists("/data/data/com.termux") or "TERMUX_VERSION" in os.environ:
|
||||
return "phone"
|
||||
h = socket.gethostname().split('.')[0]
|
||||
if h in ("localhost", "android"):
|
||||
return "phone"
|
||||
return h
|
||||
|
||||
def read_cpu_percent(prev_stat):
|
||||
try:
|
||||
with open("/proc/stat", "r") as f:
|
||||
fields = [float(x) for x in f.readline().strip().split()[1:8]]
|
||||
# fields: user, nice, system, idle, iowait, irq, softirq
|
||||
idle = fields[3] + fields[4]
|
||||
total = sum(fields)
|
||||
if prev_stat is not None:
|
||||
prev_idle, prev_total = prev_stat
|
||||
d_idle = idle - prev_idle
|
||||
d_total = total - prev_total
|
||||
pct = 100.0 * (1.0 - (d_idle / max(1.0, d_total)))
|
||||
return round(max(0.0, min(100.0, pct)), 1), (idle, total)
|
||||
return 0.0, (idle, total)
|
||||
except Exception:
|
||||
return 0.0, prev_stat
|
||||
|
||||
def read_memory():
|
||||
total_gb, used_gb = 0.0, 0.0
|
||||
try:
|
||||
with open("/proc/meminfo", "r") as f:
|
||||
mem = {}
|
||||
for line in f:
|
||||
parts = line.split(":")
|
||||
if len(parts) == 2:
|
||||
mem[parts[0].strip()] = float(parts[1].strip().split()[0])
|
||||
total_kb = mem.get("MemTotal", 0.0)
|
||||
avail_kb = mem.get("MemAvailable", mem.get("MemFree", 0.0))
|
||||
total_gb = round(total_kb / (1024.0 * 1024.0), 1)
|
||||
used_gb = round((total_kb - avail_kb) / (1024.0 * 1024.0), 1)
|
||||
except Exception:
|
||||
pass
|
||||
return used_gb, total_gb
|
||||
|
||||
def read_disk():
|
||||
hostname = get_hostname()
|
||||
check_paths = ["/"]
|
||||
if hostname == "boxC":
|
||||
check_paths = ["/mnt/kof17", "/"]
|
||||
elif hostname == "bazina":
|
||||
check_paths = ["/mnt/ssd", "/mnt/seagate", "/"]
|
||||
|
||||
for p in check_paths:
|
||||
if os.path.exists(p):
|
||||
try:
|
||||
du = shutil.disk_usage(p)
|
||||
free_gb = round(du.free / (1024.0 ** 3), 1)
|
||||
total_gb = round(du.total / (1024.0 ** 3), 1)
|
||||
return p, free_gb, total_gb
|
||||
except Exception:
|
||||
continue
|
||||
return "/", 0.0, 0.0
|
||||
|
||||
def read_gpu():
|
||||
# AMD GPU busy percent (sysfs)
|
||||
for path in glob.glob("/sys/class/drm/card*/device/gpu_busy_percent"):
|
||||
try:
|
||||
with open(path, "r") as f:
|
||||
val = f.read().strip()
|
||||
return f"Navi10 {val}%"
|
||||
except Exception:
|
||||
pass
|
||||
return ""
|
||||
|
||||
def read_temp():
|
||||
for path in ["/sys/class/thermal/thermal_zone0/temp", "/sys/class/hwmon/hwmon0/temp1_input"]:
|
||||
if os.path.exists(path):
|
||||
try:
|
||||
with open(path, "r") as f:
|
||||
return int(int(f.read().strip()) / 1000)
|
||||
except Exception:
|
||||
pass
|
||||
return 0
|
||||
|
||||
def main():
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
|
||||
hostname = get_hostname()
|
||||
prev_stat = None
|
||||
print(f"[fleet-beacon] Starting telemetry beacon on {hostname} (port {PORT})...")
|
||||
|
||||
while True:
|
||||
cpu_pct, prev_stat = read_cpu_percent(prev_stat)
|
||||
mem_used, mem_total = read_memory()
|
||||
mount, disk_free, disk_total = read_disk()
|
||||
gpu_str = read_gpu()
|
||||
temp_c = read_temp()
|
||||
|
||||
alert = ""
|
||||
if hostname == "boxC" and mount == "/mnt/kof17" and disk_free < 3.0:
|
||||
alert = f"kof17 CRITICAL: {disk_free}G free"
|
||||
elif hostname == "penguin" and disk_free < 1.0:
|
||||
alert = f"penguin LOW DISK: {disk_free}G free"
|
||||
|
||||
payload = {
|
||||
"name": hostname,
|
||||
"hostname": hostname,
|
||||
"timestamp": time.time(),
|
||||
"cpu_percent": cpu_pct,
|
||||
"mem_used_gb": mem_used,
|
||||
"mem_total_gb": mem_total,
|
||||
"disk_mount": mount,
|
||||
"disk_free_gb": disk_free,
|
||||
"disk_total_gb": disk_total,
|
||||
"gpu_str": gpu_str,
|
||||
"temp_c": temp_c,
|
||||
"alert": alert
|
||||
}
|
||||
|
||||
msg = json.dumps(payload).encode("utf-8")
|
||||
|
||||
for target in TARGETS:
|
||||
try:
|
||||
sock.sendto(msg, (target, PORT))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Update local SHM cache
|
||||
try:
|
||||
shm_fleet = "/dev/shm/fleet_telemetry.json"
|
||||
data = {}
|
||||
if os.path.exists(shm_fleet):
|
||||
try:
|
||||
with open(shm_fleet, "r") as f:
|
||||
data = json.load(f)
|
||||
except Exception:
|
||||
data = {}
|
||||
data[hostname] = payload
|
||||
tmp_path = f"{shm_fleet}.tmp.{os.getpid()}"
|
||||
with open(tmp_path, "w") as f:
|
||||
json.dump(data, f)
|
||||
os.replace(tmp_path, shm_fleet)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# WAN HTTP bridge fallback for mobile/remote nodes (e.g. Staroměstská)
|
||||
if hostname != "boxC":
|
||||
try:
|
||||
req = urllib.request.Request(
|
||||
"http://91.139.118.43/telemetry.php",
|
||||
data=msg,
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=1.5) as resp:
|
||||
if resp.status == 200:
|
||||
remote_data = json.loads(resp.read().decode("utf-8"))
|
||||
if isinstance(remote_data, dict) and "boxC" in remote_data:
|
||||
shm_fleet = "/dev/shm/fleet_telemetry.json"
|
||||
tmp_path = f"{shm_fleet}.tmp.{os.getpid()}"
|
||||
with open(tmp_path, "w") as f:
|
||||
json.dump(remote_data, f)
|
||||
os.replace(tmp_path, shm_fleet)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
time.sleep(1.5)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user