From dfc30ed68777d2911794b4e74f4dd8fa2c4c906c Mon Sep 17 00:00:00 2001 From: Michael B Reiser Date: Thu, 29 Oct 2026 12:54:52 -0400 Subject: [PATCH] =?UTF-8?q?harness:=20gate=20the=20host=20monitor's=20own?= =?UTF-8?q?=20CPU=20on=20the=20mean=20(=E2=89=A4=203=20%=20of=20a=20core),?= =?UTF-8?q?=20not=20the=20median?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On macOS/Linux, rig_nodes 0.3.1 reads the whole process table only every 5 s (--proc-rescan), so those windows are a minority and a median hides them: the lab Mac measured median 0.56 %, mean 1.9 %, max 7.4 %. The mean is the honest cost; 3 % keeps margin over the Mac and stays well inside the 5 % budget. The median and max are still reported. Co-Authored-By: Claude Opus 5.5 --- rig/tests/test_ros2_poc.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/rig/tests/test_ros2_poc.py b/rig/tests/test_ros2_poc.py index 4787dfd0..d15210d3 100644 --- a/rig/tests/test_ros2_poc.py +++ b/rig/tests/test_ros2_poc.py @@ -52,7 +52,7 @@ bag on its own (recovered:true, whole bag left) host monitor (2026-10-28; rig_nodes.host_monitor) /rig/host in the bag ≥ 0.8 Hz, fields = the installed HostStats (typed) / the read fields (JSON), the graph's - nodes pinned; its own CPU ≤ 2 % of a core, ≤ 5 % of the + nodes pinned; its own CPU ≤ 3 % of a core (mean), ≤ 5 % of the compressed bag — skipped (info) when the installed rig_nodes has no host monitor @@ -301,6 +301,7 @@ def host_monitor_values(bag: Path) -> dict: return {"samples": len(host), "schema": sorted(s or "std_msgs/msg/String" for s in schemas), "run_s": round(run_s, 1) if run_s else None, "fields_ok": bool(good) and len(good) == len(objs) and not bad, "bad_fields": bad, "cores_ok": cores_ok, "ncpu": len(good[0]["core_busy_mean"]) if good else None, "groups": sorted({p["group"] for o in good for p in o.get("procs", []) if p.get("group")}), + "self_cpu_mean": round(statistics.mean(self_cpu), 4) if self_cpu else None, "self_cpu_median": round(statistics.median(self_cpu), 4) if self_cpu else None, "self_cpu_max": round(max(self_cpu), 4) if self_cpu else None, "bytes_per_sample": round(sum(map(len, host)) / len(host)) if host else None, @@ -783,8 +784,11 @@ def side_actor() -> None: hm.get("cores_ok", False), f"{hm.get('ncpu')} CPUs") check("host monitor: the graph's processes pinned by script (py:bridge_node, py:fictrac_udp_adapter, py:host_monitor)", {"py:bridge_node", "py:fictrac_udp_adapter", "py:host_monitor"} <= set(hm.get("groups") or []), str(hm.get("groups"))) - check("host monitor cost: its own CPU ≤ 2 % of one core (median after the first 2 samples)", - hm.get("self_cpu_median") is not None and hm["self_cpu_median"] <= 0.02, f"median {hm.get('self_cpu_median')} max {hm.get('self_cpu_max')} cores") + # the MEAN, not the median: on macOS/Linux the full process-table read every 5 s + # (rig_nodes 0.3.1 --proc-rescan) is a minority of windows, and a median hides it + check("host monitor cost: its own CPU ≤ 3 % of one core (mean after the first 2 samples)", + hm.get("self_cpu_mean") is not None and hm["self_cpu_mean"] <= 0.03, + f"mean {hm.get('self_cpu_mean')} median {hm.get('self_cpu_median')} max {hm.get('self_cpu_max')} cores") check("host monitor storage: /rig/host ≤ 5 % of the run's compressed payload (zstd, as the bag)", hm.get("share_compressed") is not None and hm["share_compressed"] <= 0.05, f"{hm.get('share_compressed')} ({hm.get('host_kb_per_h_zstd')} KB/h)") check("bridge node still alive at the end of the main run", bridge.alive())