feat(mini): send p4/p6 presence flags and connectivity metric from hbc_mini.py
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uc9V1v7iXruubpoLiSE24M
This commit is contained in:
+41
-17
@@ -10,14 +10,20 @@ Built-in plugins (always available):
|
||||
os_info — OS name, kernel, distro, Python version
|
||||
ping_monitor — ICMP ping RTT to configured hosts
|
||||
nagios_runner — run Nagios-compatible check scripts
|
||||
network_monitor — per-interface TX/RX bytes/s (Linux); per-protocol connectivity (all platforms)
|
||||
|
||||
Linux-only plugins (via /proc, no extra tools needed):
|
||||
cpu_monitor — CPU %, load average, core count
|
||||
memory_monitor — RAM and swap usage
|
||||
network_monitor — per-interface TX/RX bytes/s
|
||||
|
||||
Cross-platform (via df subprocess):
|
||||
disk_monitor — per-mount usage and capacity
|
||||
|
||||
Presence probe (built in): reports per protocol (IPv4/IPv6) whether the host has internet
|
||||
connectivity, as p4/p6 flags on every heartbeat, so hbd stops treating a missing IPv6 (or
|
||||
IPv4) path as an outage. Config, in the network_monitor section of ~/.hbc.json:
|
||||
probe_enabled (default true), probe_v4 (default 1.1.1.1), probe_v6 (default
|
||||
2606:4700:4700::1111; IP literals only), probe_interval (default 60 s)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -969,7 +975,7 @@ def _read_net_dev() -> Dict[str, Tuple[int, int]]:
|
||||
|
||||
class NetworkMonitorPlugin(MonitorPlugin):
|
||||
name = "network_monitor"
|
||||
description = "Network I/O rates via /proc/net/dev (Linux)"
|
||||
description = "Network I/O rates via /proc/net/dev (Linux) and per-protocol internet connectivity"
|
||||
interval = 300
|
||||
|
||||
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
||||
@@ -978,19 +984,25 @@ class NetworkMonitorPlugin(MonitorPlugin):
|
||||
self.interval = cfg.get("interval", 300)
|
||||
self.skip_ifaces: set = set(cfg.get("skip_interfaces", ["lo"]))
|
||||
self._prev: Optional[Tuple[float, Dict[str, Tuple[int, int]]]] = None
|
||||
self._have_dev = False
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
if platform.system() != "Linux":
|
||||
self.skip_reason = "Linux only (/proc/net/dev not available)"
|
||||
dev = _read_net_dev() if platform.system() == "Linux" else {}
|
||||
self._have_dev = bool(dev)
|
||||
if not self._have_dev and _probe is None:
|
||||
self.skip_reason = "no /proc/net/dev (Linux only) and the connectivity probe is disabled"
|
||||
return False
|
||||
dev = _read_net_dev()
|
||||
if not dev:
|
||||
self.skip_reason = "/proc/net/dev not readable"
|
||||
return False
|
||||
self._prev = (time.time(), dev) # prime first delta
|
||||
if self._have_dev:
|
||||
self._prev = (time.time(), dev) # prime first delta
|
||||
return True
|
||||
|
||||
async def _collect_metrics(self) -> Dict[str, Any]:
|
||||
data: Dict[str, Any] = await self._collect_interfaces() if self._have_dev else {}
|
||||
if _probe is not None:
|
||||
data["connectivity"] = _probe.as_metric()
|
||||
return data
|
||||
|
||||
async def _collect_interfaces(self) -> Dict[str, Any]:
|
||||
now = time.time()
|
||||
curr = _read_net_dev()
|
||||
if not curr or self._prev is None:
|
||||
@@ -1251,15 +1263,21 @@ async def _handle_update(conn: AsyncConnection):
|
||||
# Heartbeat sender
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def _heartbeat_sender(conn: AsyncConnection, interval: int):
|
||||
def _build_heartbeat_msg(conn: AsyncConnection, interval: int,
|
||||
probe: Optional[_Connectivity] = None) -> Dict[str, Any]:
|
||||
"""HTB payload; carries p4/p6 presence flags when the probe knows them."""
|
||||
msg: Dict[str, Any] = {"acks": conn.ackcount, "rtt": conn.rtts[-1], "interval": interval}
|
||||
if probe is not None:
|
||||
msg.update(probe.flags())
|
||||
return msg
|
||||
|
||||
|
||||
async def _heartbeat_sender(conn: AsyncConnection, interval: int,
|
||||
probe: Optional[_Connectivity] = None):
|
||||
log = logging.getLogger("hbc.hb")
|
||||
while _running:
|
||||
try:
|
||||
await conn.sendto({
|
||||
"acks": conn.ackcount,
|
||||
"rtt": conn.rtts[-1],
|
||||
"interval": interval,
|
||||
})
|
||||
await conn.sendto(_build_heartbeat_msg(conn, interval, probe))
|
||||
except Exception as e:
|
||||
log.error("send: %s", e)
|
||||
await _sleep(interval)
|
||||
@@ -1367,7 +1385,7 @@ def _reconfigure_syslog(level: int):
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def _async_main(args, cfg: Dict[str, Any]) -> int:
|
||||
global _running, _shutdown_event, _active_tasks, send_shutdown
|
||||
global _running, _shutdown_event, _active_tasks, send_shutdown, _probe
|
||||
_running = True
|
||||
_shutdown_event = asyncio.Event()
|
||||
_active_tasks = []
|
||||
@@ -1424,6 +1442,10 @@ async def _async_main(args, cfg: Dict[str, Any]) -> int:
|
||||
c.close()
|
||||
return 0
|
||||
|
||||
# Per-protocol internet presence probe (shared with the network_monitor plugin)
|
||||
net_cfg = dict((cfg.get("plugins") or {}).get("network_monitor") or cfg.get("network_monitor") or {})
|
||||
_probe = _Connectivity.from_config(net_cfg) if net_cfg.get("probe_enabled", True) else None
|
||||
|
||||
plugins = await _load_plugins(cfg)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
@@ -1437,8 +1459,10 @@ async def _async_main(args, cfg: Dict[str, Any]) -> int:
|
||||
|
||||
loop.add_signal_handler(signal.SIGHUP, _sighup)
|
||||
|
||||
if _probe is not None:
|
||||
_active_tasks.append(asyncio.create_task(_probe.run()))
|
||||
for conn in connections:
|
||||
_active_tasks.append(asyncio.create_task(_heartbeat_sender(conn, interval)))
|
||||
_active_tasks.append(asyncio.create_task(_heartbeat_sender(conn, interval, _probe)))
|
||||
|
||||
if plugins and connections:
|
||||
_active_tasks.append(asyncio.create_task(_plugin_collector(connections[0], plugins)))
|
||||
|
||||
Reference in New Issue
Block a user