feat: sample Raspberry Pi primary interface traffic rates
This commit is contained in:
@@ -55,3 +55,7 @@ Logs follow the system journal retention policy; no additional database is creat
|
|||||||
## Naming and compatibility
|
## Naming and compatibility
|
||||||
|
|
||||||
This project targets Raspberry Pi. Other Linux platforms are not covered by its support claim and may have separately maintained adapters. Repository naming does not change the existing `pigway-agent` adapter ID or `pigway-pi-control` service, configuration and API identifiers.
|
This project targets Raspberry Pi. Other Linux platforms are not covered by its support claim and may have separately maintained adapters. Repository naming does not change the existing `pigway-agent` adapter ID or `pigway-pi-control` service, configuration and API identifiers.
|
||||||
|
|
||||||
|
## v3.8.0 网络流量
|
||||||
|
|
||||||
|
主接口每个网络采集周期读取 sysfs 的 rx_bytes/tx_bytes,以单调时间差计算字节/秒,API 新增 rx_bytes_per_second 和 tx_bytes_per_second。首次采样、接口切换、计数器重置或读取失败返回空值;不修改监控告警、硬件插件控制或 API 可选安装行为。2026-10-14 已在 Gateway 部署,实机上下行速率与 Console 标准历史指标验证通过。
|
||||||
|
|||||||
+6
-3
@@ -1,14 +1,17 @@
|
|||||||
ecf1884bb89b86ed4573d4d922a971abde43772bf7b2ffa6a999a12fbfb72caf .gitignore
|
ecf1884bb89b86ed4573d4d922a971abde43772bf7b2ffa6a999a12fbfb72caf .gitignore
|
||||||
d0e219b4b02b073f83e17ba3bb6e8c96dd4152ecdf11d946d53319613097e294 README.md
|
56b7a3ed6a6a8dbbcf02a80b6219a5e6aab0df2ef04cdff90895784c311e5cfa README.md
|
||||||
64a9708701f8c8d872a587e8a2845afff463797b06a2791319daadd705933d31 README.zh-CN.md
|
64a9708701f8c8d872a587e8a2845afff463797b06a2791319daadd705933d31 README.zh-CN.md
|
||||||
852889cbbf57d5e857794432426e3343b5d08b1395b3098a6e19fd648b4f4267 app/pigway_pi_control.py
|
c5853f283c0951b43f2bacbc49e66c26d3a605a184d427828e042eb9236d2740 app/network_rates.py
|
||||||
|
003e6766552823e8cd8ec56888c385d0607df0c14ab7dff166472fcf6914ff9c app/pigway_pi_control.py
|
||||||
757eb6a83ca07740a2389af9b51c6fd5d940b4eb86abbc603f231409416d45db app/pigway_pi_control_api.py
|
757eb6a83ca07740a2389af9b51c6fd5d940b4eb86abbc603f231409416d45db app/pigway_pi_control_api.py
|
||||||
3a8a368bbf299eb2d6cccc968a4aacf070058bddebdc5176083a97ac579d720d app/plugin_api.py
|
3a8a368bbf299eb2d6cccc968a4aacf070058bddebdc5176083a97ac579d720d app/plugin_api.py
|
||||||
d5a783b64a10fa43191a813ef73ef8f762e359b0bd42ec155724fa46216298b0 config/pigway-pi-control.conf
|
d5a783b64a10fa43191a813ef73ef8f762e359b0bd42ec155724fa46216298b0 config/pigway-pi-control.conf
|
||||||
|
9d45f22221012a7fdb3406ce74a6113f618e9930fe460efb3869efd099e3b1d8 docs/DEPLOYMENT-v3.8.0-2026-10-14.md
|
||||||
e3bf3e7070ea62c0526791bed922688c3fb49fee36ca9c375d080119968feb72 docs/HARDWARE_PLUGIN_API.md
|
e3bf3e7070ea62c0526791bed922688c3fb49fee36ca9c375d080119968feb72 docs/HARDWARE_PLUGIN_API.md
|
||||||
b54ec1f3e9fb15473910686c54142a85d387e44ea5df427a4cf1b06052e789b3 docs/RASPBERRY_PI_OS_TRIXIE_FRESH_INSTALL.md
|
b54ec1f3e9fb15473910686c54142a85d387e44ea5df427a4cf1b06052e789b3 docs/RASPBERRY_PI_OS_TRIXIE_FRESH_INSTALL.md
|
||||||
9222a2b6a38f6b6d7d88b30c05413c68c05f69ee499c8479eca05ee7c62e87c3 docs/RASPBERRY_PI_OS_TRIXIE_FRESH_INSTALL.zh-CN.md
|
9222a2b6a38f6b6d7d88b30c05413c68c05f69ee499c8479eca05ee7c62e87c3 docs/RASPBERRY_PI_OS_TRIXIE_FRESH_INSTALL.zh-CN.md
|
||||||
d2706de954d0c3f1b4311c1bac52ef09b912919b96126fa6000a88786e969438 install.sh
|
c104443a4c951ea7916f74473510362f684df7d916bf2a436b779513ba3fe396 install.sh
|
||||||
8a593c290da5bd0d037aee86c35264512afd574a2a0f60f2c29e9db63e0109bd systemd/pigway-pi-control-api.service
|
8a593c290da5bd0d037aee86c35264512afd574a2a0f60f2c29e9db63e0109bd systemd/pigway-pi-control-api.service
|
||||||
1cb55de6b104a233066495e9786319305fa4f1b6f25483c108ab4f5ac9aa5700 systemd/pigway-pi-control.service
|
1cb55de6b104a233066495e9786319305fa4f1b6f25483c108ab4f5ac9aa5700 systemd/pigway-pi-control.service
|
||||||
|
8b97229abfb31c68464eb5754bc3a8fec7a6a78e216d74a86b7cc89486700740 tests/test_network_rates.py
|
||||||
9f59e7313ee58e687ae6dd29d175c67082674254ac5c5f54168b2735b64c20a4 uninstall.sh
|
9f59e7313ee58e687ae6dd29d175c67082674254ac5c5f54168b2735b64c20a4 uninstall.sh
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"""Sample interface counters without summing physical and virtual interfaces."""
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkRates:
|
||||||
|
def __init__(self, root='/sys/class/net'):
|
||||||
|
self.root = Path(root)
|
||||||
|
self.previous = None
|
||||||
|
|
||||||
|
def sample(self, interface, now):
|
||||||
|
try:
|
||||||
|
if not re.fullmatch(r'[\w.:-]{1,64}', interface):
|
||||||
|
raise ValueError('invalid interface')
|
||||||
|
directory = self.root / interface / 'statistics'
|
||||||
|
rx = int((directory / 'rx_bytes').read_text())
|
||||||
|
tx = int((directory / 'tx_bytes').read_text())
|
||||||
|
if rx < 0 or tx < 0:
|
||||||
|
raise ValueError('negative counter')
|
||||||
|
except (OSError, ValueError, TypeError):
|
||||||
|
self.previous = None
|
||||||
|
return None, None
|
||||||
|
previous = self.previous
|
||||||
|
self.previous = interface, now, rx, tx
|
||||||
|
if previous is None or previous[0] != interface or now <= previous[1]:
|
||||||
|
return None, None
|
||||||
|
elapsed = now - previous[1]
|
||||||
|
return (None if rx < previous[2] else (rx - previous[2]) / elapsed,
|
||||||
|
None if tx < previous[3] else (tx - previous[3]) / elapsed)
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import time,socket,subprocess,configparser,signal,re,argparse,json,os,platform
|
import time,socket,subprocess,configparser,signal,re,argparse,json,os,platform
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
APP_VERSION="3.7.2"
|
APP_VERSION="3.8.0"
|
||||||
import sys
|
import sys
|
||||||
sys.path.append("/usr/local/share/pigway-pi-control")
|
sys.path.append("/usr/local/share/pigway-pi-control")
|
||||||
from shutil import disk_usage
|
from shutil import disk_usage
|
||||||
from plugin_api import Publisher,registered,discover
|
from plugin_api import Publisher,registered,discover
|
||||||
|
from network_rates import NetworkRates
|
||||||
|
|
||||||
CFG="/etc/pigway-pi-control.conf"
|
CFG="/etc/pigway-pi-control.conf"
|
||||||
STATUS_PATH="/run/pigway-pi-control/status.json"
|
STATUS_PATH="/run/pigway-pi-control/status.json"
|
||||||
@@ -104,6 +105,8 @@ prev_cpu=None
|
|||||||
page=0; page_since=time.monotonic()
|
page=0; page_since=time.monotonic()
|
||||||
cpu_hi=mem_hi=0
|
cpu_hi=mem_hi=0
|
||||||
net_ok=link_ok=True
|
net_ok=link_ok=True
|
||||||
|
network_rates=NetworkRates()
|
||||||
|
net_rx_bytes_per_second=None; net_tx_bytes_per_second=None
|
||||||
net_iface=""; net_kind="NET"; net_metric="DOWN"; net_speed_mbps=None; 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
|
link_failure_since=internet_failure_since=None
|
||||||
wifi_weak_since=None; wifi_weak_active=False
|
wifi_weak_since=None; wifi_weak_active=False
|
||||||
@@ -435,6 +438,8 @@ def write_status(cpu,temp,mem,disk):
|
|||||||
"power":health,
|
"power":health,
|
||||||
"network":{"interface":net_iface,"kind":net_kind,"metric":net_metric,
|
"network":{"interface":net_iface,"kind":net_kind,"metric":net_metric,
|
||||||
"link_speed_mbps":net_speed_mbps,
|
"link_speed_mbps":net_speed_mbps,
|
||||||
|
"rx_bytes_per_second":net_rx_bytes_per_second,
|
||||||
|
"tx_bytes_per_second":net_tx_bytes_per_second,
|
||||||
"signal_percent":signal,
|
"signal_percent":signal,
|
||||||
"ip_label":ip_label,"ip":ip,"link":bool(link_ok),"internet":bool(net_ok)},
|
"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)}
|
"watches":[{"type":w[0],"name":w[1],"target":w[2],"up":bool(ok)}
|
||||||
@@ -484,6 +489,7 @@ try:
|
|||||||
if now>=monitor_ready_at and now-last_net >= NETWORK_INTERVAL:
|
if now>=monitor_ready_at and now-last_net >= NETWORK_INTERVAL:
|
||||||
old_id=(net_iface,ip)
|
old_id=(net_iface,ip)
|
||||||
net_iface,net_kind,net_metric,net_speed_mbps,ip_label,ip=primary_network()
|
net_iface,net_kind,net_metric,net_speed_mbps,ip_label,ip=primary_network()
|
||||||
|
net_rx_bytes_per_second,net_tx_bytes_per_second=network_rates.sample(net_iface,now)
|
||||||
raw_link_ok=ip!="NO IP"; raw_net_ok=internet() if raw_link_ok else False
|
raw_link_ok=ip!="NO IP"; raw_net_ok=internet() if raw_link_ok else False
|
||||||
if raw_link_ok:
|
if raw_link_ok:
|
||||||
link_failure_since=None;link_ok=True
|
link_failure_since=None;link_ok=True
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Agent v3.8.0 网络流量
|
||||||
|
|
||||||
|
2026-10-14 Gateway 已部署。单独的 NetworkRates 模块读取主接口 sysfs 计数器;网络更新周期计算速率,写入已有状态/API。首次采样、接口切换、复位、读取失败不伪造零值。idle 返回真实 0。安装器加入模块依赖,保持 API 可选安装。采样测试覆盖初始、零流量、传输、复位、切换及失效;Python 编译、安装器语法检查通过。实机服务 active,状态版本 3.8.0、eth0 上传/下载数值有效,未发现本轮循环错误;Console 两设备在线,接入插件生成对应历史指标。
|
||||||
|
|
||||||
|
旧程序备份 `/home/way/pigway-agent-backup-v3.8.0-20261014/pigway-pi-control`,目录 0700。仅更新监控程序和新增模块并重启监控服务,没有变更硬件插件或 API 配置。
|
||||||
+2
-1
@@ -12,13 +12,14 @@ for arg in "$@"; do
|
|||||||
*) echo "ERROR: unknown option: $arg (hardware is installed separately)" >&2; exit 2 ;;
|
*) echo "ERROR: unknown option: $arg (hardware is installed separately)" >&2; exit 2 ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
for required in app/pigway_pi_control.py app/plugin_api.py app/pigway_pi_control_api.py config/pigway-pi-control.conf systemd/pigway-pi-control.service systemd/pigway-pi-control-api.service; do
|
for required in app/pigway_pi_control.py app/plugin_api.py app/network_rates.py app/pigway_pi_control_api.py config/pigway-pi-control.conf systemd/pigway-pi-control.service systemd/pigway-pi-control-api.service; do
|
||||||
[ -f "$required" ] || { echo "ERROR: required file missing: $required" >&2; exit 1; }
|
[ -f "$required" ] || { echo "ERROR: required file missing: $required" >&2; exit 1; }
|
||||||
done
|
done
|
||||||
[ "$(id -u)" -eq 0 ] || { echo 'ERROR: run installer as root' >&2; exit 1; }
|
[ "$(id -u)" -eq 0 ] || { echo 'ERROR: run installer as root' >&2; exit 1; }
|
||||||
# The monitor has no I2C, GPIO, Pillow, smbus2 or cooling-board requirement.
|
# The monitor has no I2C, GPIO, Pillow, smbus2 or cooling-board requirement.
|
||||||
command -v python3 >/dev/null || { apt-get update; apt-get install -y python3; }
|
command -v python3 >/dev/null || { apt-get update; apt-get install -y python3; }
|
||||||
mkdir -p /usr/local/share/pigway-pi-control
|
mkdir -p /usr/local/share/pigway-pi-control
|
||||||
|
install -m 0644 app/network_rates.py /usr/local/share/pigway-pi-control/network_rates.py
|
||||||
install -m 0644 app/plugin_api.py /usr/local/share/pigway-pi-control/plugin_api.py
|
install -m 0644 app/plugin_api.py /usr/local/share/pigway-pi-control/plugin_api.py
|
||||||
install -m 0755 app/pigway_pi_control.py /usr/local/sbin/pigway-pi-control
|
install -m 0755 app/pigway_pi_control.py /usr/local/sbin/pigway-pi-control
|
||||||
install -m 0755 app/pigway_pi_control_api.py /usr/local/sbin/pigway-pi-control-api
|
install -m 0755 app/pigway_pi_control_api.py /usr/local/sbin/pigway-pi-control-api
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import sys,unittest,tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'app'))
|
||||||
|
from network_rates import NetworkRates
|
||||||
|
|
||||||
|
class RatesTests(unittest.TestCase):
|
||||||
|
def test_initial_idle_transfer_reset_and_switch(self):
|
||||||
|
with tempfile.TemporaryDirectory() as root:
|
||||||
|
def write(interface,rx,tx):
|
||||||
|
p=Path(root)/interface/'statistics';p.mkdir(parents=True,exist_ok=True)
|
||||||
|
(p/'rx_bytes').write_text(str(rx));(p/'tx_bytes').write_text(str(tx))
|
||||||
|
write('eth0',1000,2000);sampler=NetworkRates(root)
|
||||||
|
self.assertEqual(sampler.sample('eth0',10),(None,None))
|
||||||
|
self.assertEqual(sampler.sample('eth0',15),(0,0))
|
||||||
|
write('eth0',1500,3000)
|
||||||
|
self.assertEqual(sampler.sample('eth0',20),(100,200))
|
||||||
|
write('eth0',10,20)
|
||||||
|
self.assertEqual(sampler.sample('eth0',25),(None,None))
|
||||||
|
write('wlan0',100,200)
|
||||||
|
self.assertEqual(sampler.sample('wlan0',30),(None,None))
|
||||||
|
self.assertEqual(sampler.sample('missing',35),(None,None))
|
||||||
|
self.assertEqual(sampler.sample('eth0',40),(None,None))
|
||||||
|
self.assertEqual(sampler.sample('../eth0',45),(None,None))
|
||||||
Reference in New Issue
Block a user