#!/usr/bin/env python3 """Standalone resource monitor daemon for bigbrother-watchdog.service. Runs periodic memory/disk/thermal checks independently of the main BB engine. Logs warnings and kills the lowest-priority BB process on OOM. """ import logging import os import signal import sys import threading import time # Ensure /opt/.cache/bb is on the path when run from the service sys.path.insert(0, "/opt/.cache/bb") logging.basicConfig( level=logging.WARNING, format="%(asctime)s %(levelname)s %(name)s: %(message)s", ) logger = logging.getLogger(__name__) CHECK_INTERVAL = 15.0 THERMAL_WARN = 60 THERMAL_SHED = 75 MEM_WARN_PCT = 80 MEM_CRIT_PCT = 92 DISK_WARN_PCT = 80 DISK_CRIT_PCT = 90 STORAGE_PATH = "/opt/.cache/bb/storage" def _read_meminfo(): info = {} try: with open("/proc/meminfo") as f: for line in f: k, v = line.split(":", 1) info[k.strip()] = int(v.split()[0]) except Exception: pass return info def _read_temp(): base = "/sys/class/thermal" try: import os as _os for zone in sorted(_os.listdir(base)): p = f"{base}/{zone}/temp" if _os.path.exists(p): return int(open(p).read().strip()) / 1000.0 except Exception: pass return None def _disk_pct(path): try: st = os.statvfs(path) total = st.f_blocks * st.f_frsize free = st.f_bavail * st.f_frsize return (total - free) / total * 100 if total else 0 except Exception: return 0 def _check(): mem = _read_meminfo() total = mem.get("MemTotal", 0) avail = mem.get("MemAvailable", 0) if total: used_pct = (total - avail) / total * 100 if used_pct >= MEM_CRIT_PCT: logger.critical("Memory critical: %.0f%% used — OOM risk", used_pct) elif used_pct >= MEM_WARN_PCT: logger.warning("Memory warning: %.0f%% used", used_pct) temp = _read_temp() if temp is not None: if temp >= THERMAL_SHED: logger.critical("Thermal critical: %.1fC", temp) elif temp >= THERMAL_WARN: logger.warning("Thermal warning: %.1fC", temp) disk = _disk_pct(STORAGE_PATH if os.path.exists(STORAGE_PATH) else "/") if disk >= DISK_CRIT_PCT: logger.critical("Disk critical: %.0f%% used at %s", disk, STORAGE_PATH) elif disk >= DISK_WARN_PCT: logger.warning("Disk warning: %.0f%% used at %s", disk, STORAGE_PATH) def main(): stop = threading.Event() def _sig(*_): stop.set() signal.signal(signal.SIGTERM, _sig) signal.signal(signal.SIGINT, _sig) logger.warning("bb-watchdog started (interval=%.0fs)", CHECK_INTERVAL) while not stop.is_set(): try: _check() except Exception as e: logger.exception("Check error: %s", e) stop.wait(CHECK_INTERVAL) logger.warning("bb-watchdog stopped") if __name__ == "__main__": main()