#!/usr/bin/env python3 """Poll server upstream-sync state while a client-to-RCT probe is active.""" from __future__ import annotations import json import os import pathlib import subprocess import sys import time def parse_relayctl_fields(text: str) -> dict: """Parse relayctl key/value output into a dictionary. Inputs: text from `lesavka-relayctl upstream-sync`. Outputs: field names mapped to string values. Why: the sampler should preserve raw operator output while also making timing fields easy for follow-up scripts to query with `jq`. """ fields: dict[str, str] = {} for line in text.splitlines(): if "=" not in line: continue key, value = line.split("=", 1) fields[key.strip()] = value.strip() return fields def process_alive(pid: int) -> bool: """Return whether a local probe process still exists. Inputs: process id to monitor. Outputs: true while the process can be signaled with zero. Why: the harness needs a passwordless background sampler that exits naturally when the active probe finishes. """ try: os.kill(pid, 0) except OSError: return False return True def sample_until_probe_exits( relayctl: str, server: str, tls_domain: str, probe_pid: int, interval_s: float, jsonl_path: pathlib.Path, text_path: pathlib.Path, ) -> int: """Write upstream-sync samples while the probe process is alive. Inputs: relayctl path, server address, TLS domain, probe pid, interval, and output paths. Outputs: process exit code only; sample artifacts are written to disk. Why: if the RCT black-box result fails, these samples show whether the server planner saw stale client queues, late presentation, or healthy ingress. """ jsonl_path.parent.mkdir(parents=True, exist_ok=True) text_path.parent.mkdir(parents=True, exist_ok=True) with jsonl_path.open("w", encoding="utf-8") as jsonl, text_path.open( "w", encoding="utf-8" ) as text: while process_alive(probe_pid): sample_ns = time.time_ns() env = os.environ.copy() env["LESAVKA_TLS_DOMAIN"] = tls_domain result = subprocess.run( [relayctl, "--server", server, "upstream-sync"], text=True, capture_output=True, env=env, check=False, ) raw = result.stdout.strip() if result.stderr.strip(): raw = raw + ("\n" if raw else "") + result.stderr.strip() row = { "schema": "lesavka.client-rct-upstream-sync-sample.v1", "sample_unix_ns": sample_ns, "ok": result.returncode == 0, "returncode": result.returncode, "fields": parse_relayctl_fields(raw), "raw": raw, } jsonl.write(json.dumps(row, sort_keys=True) + "\n") jsonl.flush() text.write(f"--- sample_unix_ns={sample_ns} ok={row['ok']} ---\n{raw}\n") text.flush() time.sleep(interval_s) return 0 def main() -> int: """CLI entrypoint for the upstream-sync sampler.""" if len(sys.argv) != 8: print( "usage: client_rct_upstream_sync_sampler.py RELAYCTL SERVER TLS_DOMAIN PROBE_PID INTERVAL_SECONDS JSONL_OUT TXT_OUT", file=sys.stderr, ) return 2 relayctl, server, tls_domain, pid_raw, interval_raw, jsonl_out, txt_out = sys.argv[1:] return sample_until_probe_exits( relayctl, server, tls_domain, int(pid_raw), float(interval_raw), pathlib.Path(jsonl_out), pathlib.Path(txt_out), ) if __name__ == "__main__": raise SystemExit(main())