Files
DOLPHIN/prod/system_stats_service.py

90 lines
3.2 KiB
Python
Raw Normal View History

"""
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()