feat: monitor ethernet link speed

This commit is contained in:
way
2026-10-11 00:34:21 +08:00
parent fc27079126
commit 26c313df46
7 changed files with 100 additions and 27 deletions
+32 -10
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
import time,socket,subprocess,configparser,signal,re,argparse,json,os,platform
from pathlib import Path
APP_VERSION="3.7.1"
APP_VERSION="3.7.2"
import sys
sys.path.append("/usr/local/share/pigway-pi-control")
from shutil import disk_usage
@@ -104,9 +104,10 @@ prev_cpu=None
page=0; page_since=time.monotonic()
cpu_hi=mem_hi=0
net_ok=link_ok=True
net_iface=""; net_kind="NET"; net_metric="DOWN"; ip_label="IP4"; ip="NO IP"
net_iface=""; net_kind="NET"; net_metric="DOWN"; net_speed_mbps=None; ip_label="IP4"; ip="NO IP"
link_failure_since=internet_failure_since=None
wifi_weak_since=None; wifi_weak_active=False
ethernet_slow_since=None; ethernet_slow_active=False
last_net=last_watch=0
watch_state=[]
@@ -149,8 +150,9 @@ def primary_network():
if not dev:
out=_run(["ip","-6","route","get","2606:4700:4700::1111"])
dev=re.search(r"\bdev\s+(\S+)",out); src=re.search(r"\bsrc\s+(\S+)",out); label="IP6"
if not dev: return "","NET","DOWN","IP4","NO IP"
if not dev: return "","NET","DOWN",None,"IP4","NO IP"
iface=dev.group(1); ipval=src.group(1) if src else ""
speed_mbps=None
if Path(f"/sys/class/net/{iface}/wireless").exists():
kind="WIF"; metric="--"
try:
@@ -162,12 +164,13 @@ def primary_network():
kind="ETH"; metric="UP"
try:
n=int(Path(f"/sys/class/net/{iface}/speed").read_text().strip())
speed_mbps=n if n>0 else None
metric=f"{n//1000}G" if n>=1000 and n%1000==0 else str(n)
except Exception: pass
return iface,kind,metric,label,(ipval or "NO IP")
return iface,kind,metric,speed_mbps,label,(ipval or "NO IP")
def get_ip():
return primary_network()[4]
return primary_network()[5]
def internet():
try:
@@ -219,6 +222,8 @@ DISK_CRIT=cf("alerts","disk_critical",95.0)
WIFI_WEAK=cf("alerts","wifi_weak",-75.0)
WIFI_RECOVER=cf("alerts","wifi_recover",-68.0)
WIFI_WEAK_SECONDS=cf("alerts","wifi_weak_seconds",30.0)
ETHERNET_MIN_SPEED=cf("alerts","ethernet_min_speed_mbps",1000,int)
ETHERNET_SPEED_SECONDS=cf("alerts","ethernet_speed_seconds",15.0)
DARK_PWR=cf("dark_mode","pi_pwr_led",True,lambda x: str(x).strip().lower() in ("off","0","false","no"))
DARK_ACT=cf("dark_mode","pi_act_led",True,lambda x: str(x).strip().lower() in ("off","0","false","no"))
@@ -229,7 +234,7 @@ ALERT_CATEGORIES={
"POWER_UNDERVOLT":"POWER",
"MEMORY_HIGH":"MEMORY",
"DISK_CRITICAL":"STORAGE","DISK_HIGH":"STORAGE",
"NETWORK_DOWN":"NETWORK","INTERNET_DOWN":"NETWORK","WIFI_WEAK":"NETWORK",
"NETWORK_DOWN":"NETWORK","INTERNET_DOWN":"NETWORK","WIFI_WEAK":"NETWORK","ETHERNET_SPEED_LOW":"NETWORK",
"SERVICE_DOWN":"SERVICE","PROCESS_DOWN":"SERVICE",
}
@@ -257,9 +262,9 @@ def diagnose():
"under_voltage_occurred","freq_capped_occurred","throttled_occurred","soft_temp_limit_occurred"):
print(f"{key}={power.get(key,'UNKNOWN')}")
if "error" in power: print("error="+power["error"])
iface,kind,metric,label,address=primary_network()
iface,kind,metric,speed,label,address=primary_network()
print("Network:")
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(f"primary_interface={iface or 'NONE'} kind={kind} metric={metric} link_speed_mbps={speed if speed is not None else 'N/A'} signal_percent={wifi_percent(metric) if kind=='WIF' else 'N/A'} ip_label={label} IP={address} internet={internet()}")
print("Plugins (read-only API discovery; no hardware access):")
for plugin in discover(cfg):
print(f"plugin={plugin['id']} linked={plugin['linked']} online={plugin['online']}")
@@ -286,6 +291,7 @@ ALERT_PRIORITIES={
"MEMORY_HIGH":45,
"DISK_HIGH":40,
"WIFI_WEAK":30,
"ETHERNET_SPEED_LOW":35,
"NORMAL_HOME":0,
}
@@ -299,7 +305,7 @@ def transition(key,active,on_event,off_event,**fields):
log("WARN" if active else "INFO",on_event if active else off_event,**fields)
def update_active_alerts(cpu,temp,mem,disk,sample_elapsed=0.0):
global wifi_weak_since,wifi_weak_active
global wifi_weak_since,wifi_weak_active,ethernet_slow_since,ethernet_slow_active
global cpu_hi,mem_hi
cpu_hi=cpu_hi+sample_elapsed if cpu>=CPU_HIGH else 0 if cpu<CPU_RECOVER else cpu_hi
mem_hi=mem_hi+sample_elapsed if mem>=MEM_HIGH else 0 if mem<MEM_RECOVER else mem_hi
@@ -350,6 +356,21 @@ def update_active_alerts(cpu,temp,mem,disk,sample_elapsed=0.0):
else: alert_manager.clear("WIFI_WEAK")
else:
wifi_weak_since=None; wifi_weak_active=False; alert_manager.clear("WIFI_WEAK")
if net_kind=="ETH" and net_speed_mbps is not None and ETHERNET_MIN_SPEED>0:
if not ethernet_slow_active:
if net_speed_mbps < ETHERNET_MIN_SPEED:
if ethernet_slow_since is None: ethernet_slow_since=time.monotonic()
if time.monotonic()-ethernet_slow_since >= ETHERNET_SPEED_SECONDS: ethernet_slow_active=True
else: ethernet_slow_since=None
elif net_speed_mbps >= ETHERNET_MIN_SPEED:
ethernet_slow_active=False; ethernet_slow_since=None
if ethernet_slow_active:
alert_manager.request("ETHERNET_SPEED_LOW",1,"ETHERNET SLOW",f"LINK {net_speed_mbps} Mbps",f"EXPECT {ETHERNET_MIN_SPEED}+","CHECK CABLE")
else: alert_manager.clear("ETHERNET_SPEED_LOW")
else:
ethernet_slow_since=None; ethernet_slow_active=False; alert_manager.clear("ETHERNET_SPEED_LOW")
transition("ethernet_speed_low",ethernet_slow_active,"ETHERNET_SPEED_LOW","ETHERNET_SPEED_RECOVERED",
iface=net_iface,speed_mbps=net_speed_mbps,minimum_mbps=ETHERNET_MIN_SPEED)
if health.get("under_voltage_now"):
alert_manager.request("POWER_UNDERVOLT",2,"POWER LOW","UNDERVOLTAGE","CHECK POWER","CRITICAL",{"raw":health.get("raw","")})
else: alert_manager.clear("POWER_UNDERVOLT")
@@ -413,6 +434,7 @@ def write_status(cpu,temp,mem,disk):
"root_readonly":bool(fs_readonly)},
"power":health,
"network":{"interface":net_iface,"kind":net_kind,"metric":net_metric,
"link_speed_mbps":net_speed_mbps,
"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)}
@@ -461,7 +483,7 @@ try:
if now>=monitor_ready_at and now-last_net >= NETWORK_INTERVAL:
old_id=(net_iface,ip)
net_iface,net_kind,net_metric,ip_label,ip=primary_network()
net_iface,net_kind,net_metric,net_speed_mbps,ip_label,ip=primary_network()
raw_link_ok=ip!="NO IP"; raw_net_ok=internet() if raw_link_ok else False
if raw_link_ok:
link_failure_since=None;link_ok=True