ffd384f64b
Replace all sensor.* logger namespaces with __name__ (generic module identifiers instead of discoverable 'sensor.*' prefixes). Change hardcoded 'bb' API user to 'admin' in config and code defaults. Change hardcoded relay_user 'bb' to 'operator' — prevents network profiling from exposing tool identity via SSH config. Fixes #457, #458, #459
115 lines
2.9 KiB
Python
115 lines
2.9 KiB
Python
#!/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()
|