From a76d0fc8409337c95c69900249fabea9733a6b51 Mon Sep 17 00:00:00 2001 From: Andreas Wrede Date: Sun, 3 May 2026 06:08:11 -0400 Subject: [PATCH] feat: generic ping_monitor thresholds; round RTT to nearest ms - threshold.py: add _find_threshold() with suffix fallback so thresholds like ping_monitor.rtt_avg match ping_monitor.8_8_8_8_rtt_avg etc.; each pinged host keeps its own alert state - hbdclass.py: format RTT as integer ms (round()) - live.html: JS RTT display rounded to nearest ms (Math.round) Co-Authored-By: Claude Sonnet 4.6 (1M context) --- hbd/server/hbdclass.py | 2 +- hbd/server/templates/live.html | 2 +- hbd/server/threshold.py | 28 +++++++++++++++++++++++++--- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/hbd/server/hbdclass.py b/hbd/server/hbdclass.py index 29bb410..4b97a49 100644 --- a/hbd/server/hbdclass.py +++ b/hbd/server/hbdclass.py @@ -95,7 +95,7 @@ class Connection: if not Null: d["addr"] = self.addr if self.rtts[-1]: - d["rtt"] = "%0.1f" % self.rtts[-1] + d["rtt"] = "%d" % round(self.rtts[-1]) elif self.state == Connection.UNKNOWN: d["rtt"] = "" else: diff --git a/hbd/server/templates/live.html b/hbd/server/templates/live.html index 40b980f..60396b3 100644 --- a/hbd/server/templates/live.html +++ b/hbd/server/templates/live.html @@ -408,7 +408,7 @@ ); if (data.connections[i].state == "up") { state = 'up'; - latency = Number.parseFloat(data.connections[i].rtts[0]).toFixed(2); + latency = String(Math.round(Number.parseFloat(data.connections[i].rtts[0]))); } else { if (data.connections[i].state == "unknown") { state = ""; diff --git a/hbd/server/threshold.py b/hbd/server/threshold.py index f8baca1..fa4f5a1 100644 --- a/hbd/server/threshold.py +++ b/hbd/server/threshold.py @@ -803,6 +803,29 @@ class ThresholdChecker: self._check_pending_or_renotify(host_name, alert_state, metric_path, value, threshold, None) return None + def _find_threshold( + self, thresholds: Dict[str, "ThresholdConfig"], metric_path: str + ) -> Optional["ThresholdConfig"]: + """Return the threshold for *metric_path*, falling back to suffix matches. + + Allows generic thresholds like ``ping_monitor.rtt_avg`` to match + fully-qualified paths like ``ping_monitor.8_8_8_8_rtt_avg``. + The exact match is always tried first; then successive leading + underscore-delimited segments are stripped from the field name until + a match is found or no segments remain. + """ + if metric_path in thresholds: + return thresholds[metric_path] + plugin, sep, field = metric_path.partition(".") + if not sep: + return None + parts = field.split("_") + for i in range(1, len(parts)): + candidate = plugin + "." + "_".join(parts[i:]) + if candidate in thresholds: + return thresholds[candidate] + return None + def check_plugin_data( self, host_name: str, @@ -831,11 +854,10 @@ class ThresholdChecker: for metric_name, value in data.items(): metric_path = f"{plugin_name}.{metric_name}" - if metric_path not in thresholds: + threshold = self._find_threshold(thresholds, metric_path) + if threshold is None: continue - threshold = thresholds[metric_path] - # Get or create alert state if metric_path not in alert_states: alert_states[metric_path] = AlertState(metric_path)