""" system_stats_service.py — Lightweight host metrics → ClickHouse. Polls /proc every 30s. Zero dependencies beyond stdlib. Writes to dolphin.system_stats via HTTP INSERT. Run: python3 /root/ch-setup/system_stats_service.py (Place on mount at prod/system_stats_service.py before adding to supervisord) """ import time, json, urllib.request, logging CH_URL = "http://localhost:8123" CH_USER = "dolphin" CH_PASS = "dolphin_ch_2026" POLL_S = 30 logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s") log = logging.getLogger("system_stats") _prev_net: dict = {} def read_mem() -> dict: """Parse /proc/meminfo → used/available GB.""" info = {} with open("/proc/meminfo") as f: for line in f: k, v = line.split(":") info[k.strip()] = int(v.split()[0]) # kB total = info["MemTotal"] / 1024 / 1024 available = info["MemAvailable"] / 1024 / 1024 used = total - available return {"mem_used_gb": round(used, 2), "mem_available_gb": round(available, 2), "mem_pct": round(used / total * 100, 1)} def read_load() -> dict: """Parse /proc/loadavg.""" with open("/proc/loadavg") as f: parts = f.read().split() return {"load_1m": float(parts[0]), "load_5m": float(parts[1]), "load_15m": float(parts[2])} def read_net() -> dict: """Parse /proc/net/dev → MB/s delta across non-lo interfaces.""" global _prev_net ifaces = {} with open("/proc/net/dev") as f: for line in f.readlines()[2:]: parts = line.split() iface = parts[0].rstrip(":") if iface == "lo": continue ifaces[iface] = {"rx": int(parts[1]), "tx": int(parts[9])} now = time.monotonic() result = {"net_rx_mb_s": 0.0, "net_tx_mb_s": 0.0, "net_iface": "all"} if _prev_net: dt = now - _prev_net["_ts"] rx_total = sum(ifaces[i]["rx"] - _prev_net.get(i, {}).get("rx", ifaces[i]["rx"]) for i in ifaces) tx_total = sum(ifaces[i]["tx"] - _prev_net.get(i, {}).get("tx", ifaces[i]["tx"]) for i in ifaces) result["net_rx_mb_s"] = round(max(0, rx_total) / 1024 / 1024 / dt, 3) result["net_tx_mb_s"] = round(max(0, tx_total) / 1024 / 1024 / dt, 3) _prev_net = {**ifaces, "_ts": now} return result def ch_insert(row: dict): body = (json.dumps(row) + "\n").encode() url = f"{CH_URL}/?database=dolphin&query=INSERT+INTO+system_stats+FORMAT+JSONEachRow" req = urllib.request.Request(url, data=body, method="POST") req.add_header("X-ClickHouse-User", CH_USER) req.add_header("X-ClickHouse-Key", CH_PASS) req.add_header("Content-Type", "application/octet-stream") urllib.request.urlopen(req, timeout=5) def main(): log.info("system_stats_service starting — poll every %ds", POLL_S) while True: try: ts_ms = int(time.time() * 1_000) # DateTime64(3) expects milliseconds row = {"ts": ts_ms, **read_mem(), **read_load(), **read_net()} ch_insert(row) log.debug("inserted: %s", row) except Exception as e: log.warning("error: %s", e) time.sleep(POLL_S) if __name__ == "__main__": main()