feat: add multi-host monitoring and configuration
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import time,socket,subprocess,configparser,signal,re,errno,threading,argparse,json
|
||||
import time,socket,subprocess,configparser,signal,re,errno,threading,argparse,json,os,platform
|
||||
from pathlib import Path
|
||||
APP_VERSION="3.6.0"
|
||||
APP_VERSION="3.7.0"
|
||||
from smbus2 import SMBus
|
||||
from PIL import Image
|
||||
from shutil import disk_usage
|
||||
@@ -29,6 +29,41 @@ def load_average():
|
||||
x=Path("/proc/loadavg").read_text().split();return (float(x[0]),float(x[1]),float(x[2]))
|
||||
except Exception:return (0.0,0.0,0.0)
|
||||
|
||||
def memory_usage():
|
||||
try:
|
||||
values={}
|
||||
for line in Path("/proc/meminfo").read_text().splitlines():
|
||||
key,value=line.split(":",1); values[key]=int(value.split()[0])*1024
|
||||
total=values["MemTotal"]; available=values.get("MemAvailable",values.get("MemFree",0))
|
||||
used=max(0,total-available)
|
||||
return total,used,available,(used*100.0/total if total else 0.0)
|
||||
except Exception:return 0,0,0,0.0
|
||||
|
||||
def wifi_percent(metric):
|
||||
try:return max(0,min(100,round((float(metric)+100.0)*2.0)))
|
||||
except Exception:return None
|
||||
|
||||
def device_details():
|
||||
info={}
|
||||
try:
|
||||
for line in Path("/proc/cpuinfo").read_text().splitlines():
|
||||
if ":" in line:
|
||||
key,value=(x.strip() for x in line.split(":",1))
|
||||
if key in ("Serial","Revision","Hardware","Model"): info[key.lower()]=value
|
||||
except Exception: pass
|
||||
try: info["model"]=Path("/proc/device-tree/model").read_bytes().rstrip(b"\0").decode()
|
||||
except Exception: pass
|
||||
try:
|
||||
os_release={}
|
||||
for line in Path("/etc/os-release").read_text().splitlines():
|
||||
if "=" in line:
|
||||
key,value=line.split("=",1); os_release[key]=value.strip().strip('"')
|
||||
info["os"]=os_release.get("PRETTY_NAME",platform.platform())
|
||||
except Exception: info["os"]=platform.platform()
|
||||
info.update({"hostname":socket.gethostname(),"kernel":platform.release(),
|
||||
"architecture":platform.machine(),"cpu_count":os.cpu_count() or 0})
|
||||
return info
|
||||
|
||||
def cpu_freq_mhz():
|
||||
for q in ("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq","/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq"):
|
||||
try:return int(Path(q).read_text().strip())/1000.0
|
||||
@@ -224,6 +259,12 @@ def cf(section,key,default,cast=float):
|
||||
try: return cast(cfg.get(section,key))
|
||||
except: return default
|
||||
|
||||
DEVICE_ID=cf("device","identifier","auto",str).strip()
|
||||
if not DEVICE_ID or DEVICE_ID.lower()=="auto": DEVICE_ID=socket.gethostname()
|
||||
DEVICE_NAME=cf("device","name",DEVICE_ID,str).strip() or DEVICE_ID
|
||||
if DEVICE_NAME.lower()=="auto": DEVICE_NAME=DEVICE_ID
|
||||
DEVICE_INFO=device_details()
|
||||
|
||||
SENSOR_INTERVAL=cf("timing","sensor_interval",1.0)
|
||||
OLED_REFRESH=cf("timing","oled_refresh_interval",1.0)
|
||||
PAGE_INTERVAL=cf("timing","page_interval",10.0)
|
||||
@@ -306,6 +347,7 @@ def check_watch(w):
|
||||
def diagnose():
|
||||
print("Agent:")
|
||||
print(f"version={APP_VERSION} uptime_seconds={uptime_seconds()} config={CFG}")
|
||||
print(f"machine_id={DEVICE_ID} machine_name={DEVICE_NAME} hostname={socket.gethostname()}")
|
||||
print("loop_errors=UNAVAILABLE i2c_errors=UNAVAILABLE scope=separate_diagnostic_process")
|
||||
print("System:")
|
||||
print(f"load_average={load_average()} cpu_freq_mhz={cpu_freq_mhz():.0f} root_readonly={root_readonly()}")
|
||||
@@ -318,7 +360,7 @@ def diagnose():
|
||||
if "error" in power: print("error="+power["error"])
|
||||
iface,kind,metric,label,address=primary_network()
|
||||
print("Network:")
|
||||
print(f"primary_interface={iface or 'NONE'} kind={kind} metric={metric} ip_label={label} IP={address} internet={internet()}")
|
||||
print(f"primary_interface={iface or 'NONE'} kind={kind} metric={metric} signal_percent={wifi_percent(metric) if kind=='WIF' else 'N/A'} ip_label={label} IP={address} internet={internet()}")
|
||||
print("I2C:")
|
||||
# SMBus receive-byte performs a read only; no register selection or display commands.
|
||||
try:
|
||||
@@ -491,10 +533,11 @@ def render_home(cpu,temp,mem,disk):
|
||||
f"DSK {disk:.1f}%",
|
||||
ip_label+" "+display_ip,
|
||||
]
|
||||
signal=wifi_percent(net_metric) if net_kind=="WIF" else None
|
||||
right=[
|
||||
f"MEM {mem:.1f}%",
|
||||
f"FAN {FAN_NAME[fan_level]}",
|
||||
f"{net_kind} {net_metric}",
|
||||
f"WIF {signal}%" if signal is not None else f"{net_kind} {net_metric}",
|
||||
"",
|
||||
]
|
||||
for row in range(4):
|
||||
@@ -729,14 +772,24 @@ def cleanup():
|
||||
|
||||
def write_status(cpu,temp,mem,disk):
|
||||
display_state=display.status_snapshot()
|
||||
memory_total,memory_used,memory_available,memory_calculated=memory_usage()
|
||||
disk_total,disk_used,disk_free=disk_usage("/")
|
||||
signal=wifi_percent(net_metric) if net_kind=="WIF" else None
|
||||
state={
|
||||
"version":APP_VERSION,"timestamp":int(time.time()),"uptime_seconds":uptime_seconds(),
|
||||
"machine":{"id":DEVICE_ID,"name":DEVICE_NAME,"hostname":socket.gethostname()},
|
||||
"device":DEVICE_INFO,
|
||||
"system":{"load_average":load_average(),"cpu_percent":round(cpu,1),
|
||||
"cpu_freq_mhz":round(cpu_freq_mhz()),"temperature_c":round(temp,1),
|
||||
"memory_percent":round(mem,1),"disk_percent":round(disk,1),
|
||||
"memory_percent":round(memory_calculated if memory_total else mem,1),
|
||||
"memory_total_bytes":memory_total,"memory_used_bytes":memory_used,
|
||||
"memory_available_bytes":memory_available,
|
||||
"disk_percent":round(disk,1),"disk_total_bytes":disk_total,
|
||||
"disk_used_bytes":disk_used,"disk_free_bytes":disk_free,
|
||||
"root_readonly":bool(fs_readonly),"fan_level":fan_level,"fan_name":FAN_NAME[fan_level]},
|
||||
"power":health,
|
||||
"network":{"interface":net_iface,"kind":net_kind,"metric":net_metric,
|
||||
"signal_percent":signal,
|
||||
"ip_label":ip_label,"ip":ip,"link":bool(link_ok),"internet":bool(net_ok)},
|
||||
"watches":[{"type":w[0],"name":w[1],"target":w[2],"up":bool(ok)}
|
||||
for w,ok in zip(watches,watch_state)],
|
||||
|
||||
Reference in New Issue
Block a user