Add pi3b hardware tier and resource guards for condo Pi deployment

- Add pi3b tier (700MB RAM limit, 8 passive/2 active modules, no bettercap/mitmproxy)
- Update detect_hardware_tier() to detect Raspberry Pi 3B by model string and BCM2837
- Add pi3b section to hardware_tiers.yaml
- Add systemd MemoryMax/CPUQuota/OOMScoreAdj to all three service files
- Fix bigbrother-watchdog.service ExecStart (was calling non-existent ResourceMonitor().run())
- Add scripts/run_watchdog.py as standalone health monitor daemon
This commit is contained in:
n0mad1k
2026-03-22 20:19:57 -04:00
parent a0164c0275
commit 263e5a50d0
6 changed files with 162 additions and 2 deletions
+114
View File
@@ -0,0 +1,114 @@
#!/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("bb.watchdog")
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()