fix: preserve chart history across reconnects, show gaps for missing data

Only wipe real (non-RTT) plugin data on an actual client reboot (boot
flag), not on every ordinary OVERDUE/DOWN -> UP recovery. A transient
network blip no longer erases CPU/memory/etc. history.

Also split the shared time-series chart into separate line/area segments
wherever the gap between samples is much larger than the typical spacing,
so missing data (host overdue, or history that simply hasn't accumulated
across a drop) renders as a visual gap instead of an interpolated line.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DEimzMv4Q5EjFg3hoiZ69T
This commit is contained in:
2026-08-17 09:13:49 -04:00
co-authored by Claude Sonnet 5
parent 4ac5a34a43
commit 18e33656c0
3 changed files with 77 additions and 21 deletions
+36 -6
View File
@@ -14,10 +14,12 @@ class _FakeTransport:
self.sent.append((data, addr))
def _htb(name, rtt=None, interval=0):
def _htb(name, rtt=None, interval=0, boot=0):
d = {"name": name, "interval": interval, "id": 0}
if rtt is not None:
d["rtt"] = rtt
if boot:
d["boot"] = boot
return parse_message(dicttos("HTB", d))
@@ -94,10 +96,11 @@ def test_request_update_fires_on_recovery_even_with_rtt_history():
assert ack.get("request_update")
def test_recovery_clears_real_plugin_data_but_preserves_rtt_history():
"""Regression for Finding 2: host.plugin_data.clear() on recovery must
wipe real client-collected plugin data while leaving rtt_* history
samples intact.
def test_ordinary_recovery_preserves_real_plugin_data_and_rtt_history():
"""An ordinary reconnect (no boot flag) — e.g. OVERDUE->UP after a
transient network blip — must NOT wipe already-collected real plugin
data (e.g. cpu_monitor, os_info) or rtt_* history. Only an actual
client reboot invalidates that data (see the boot-flag test below).
"""
hbdclass.Host.hosts.pop("rtt-hist-host5", None)
transport = _FakeTransport()
@@ -116,8 +119,35 @@ def test_recovery_clears_real_plugin_data_but_preserves_rtt_history():
host.add_plugin_data("os_info", {"os": "linux"}, timestamp=time.time())
assert "os_info" in host.plugin_data
# Recovery heartbeat.
# Ordinary recovery heartbeat — no boot flag.
handle_datagram(_htb("rtt-hist-host5", rtt=13.0), ("127.0.0.1", 50000), transport, ctx)
assert "os_info" in host.plugin_data
assert len(host.plugin_data["rtt_ipv4"]) == 4
def test_boot_recovery_clears_real_plugin_data_but_preserves_rtt_history():
"""A recovery heartbeat carrying the boot flag (client process actually
restarted) must still wipe stale real plugin data, while rtt_* history
(still a valid measurement, unaffected by a client reboot) survives.
"""
hbdclass.Host.hosts.pop("rtt-hist-host6", None)
transport = _FakeTransport()
ctx = _base_ctx()
for rtt in (10.0, 11.0, 12.0):
handle_datagram(_htb("rtt-hist-host6", rtt=rtt), ("127.0.0.1", 50000), transport, ctx)
host = hbdclass.Host.hosts["rtt-hist-host6"]
assert len(host.plugin_data["rtt_ipv4"]) == 3
conn = host.connections["IPv4"]
conn.state = hbdclass.Connection.DOWN
host.add_plugin_data("os_info", {"os": "linux"}, timestamp=time.time())
assert "os_info" in host.plugin_data
# Recovery heartbeat with boot=1 — client process actually restarted.
handle_datagram(_htb("rtt-hist-host6", rtt=13.0, boot=1), ("127.0.0.1", 50000), transport, ctx)
assert "os_info" not in host.plugin_data
assert len(host.plugin_data["rtt_ipv4"]) == 4