Opens a larger view. Escape closes it.

home-server

perf-daemon.py

#!/usr/bin/env python3
"""
perf-daemon — automatic performance profile switching based on Docker container events.

Priority (highest wins when multiple workloads run simultaneously):
  gaming (4): sunshine
  llm    (3): ollama, open-webui
  video  (2): jellyfin, tdarr
  balanced(1): everything else (no action)
"""

import subprocess, logging, sys, time, signal

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [perf-daemon] %(message)s",
    handlers=[logging.StreamHandler(sys.stdout)]
)
log = logging.getLogger("perf-daemon")

# Container name → workload type
CONTAINER_MAP = {
    "sunshine":  "gaming",
    "ollama":    "llm",
    "open-webui": "llm",
    "jellyfin":  "video",
    "tdarr":     "video",
}

PRIORITY = {"gaming": 4, "llm": 3, "video": 2, "balanced": 1}

active: dict[str, str] = {}  # container_name → workload_type
_current_profile = None


def running_containers() -> list[str]:
    try:
        r = subprocess.run(
            ["docker", "ps", "--format", "{{.Names}}"],
            capture_output=True, text=True, timeout=5
        )
        return r.stdout.strip().splitlines()
    except Exception:
        return []


def best_profile() -> str:
    if not active:
        return "balanced"
    return max(active.values(), key=lambda w: PRIORITY.get(w, 0))


def apply(profile: str) -> None:
    global _current_profile
    if profile == _current_profile:
        return
    log.info(f"Switching to '{profile}' (active: {dict(active)})")
    try:
        subprocess.run(["/usr/local/bin/perf-mode", profile], timeout=10)
        _current_profile = profile
    except Exception as e:
        log.error(f"Failed to apply profile '{profile}': {e}")


def handle_event(action: str, name: str) -> None:
    wl = CONTAINER_MAP.get(name)
    if wl is None:
        return
    prev = best_profile()
    if action == "start":
        active[name] = wl
        log.info(f"Container '{name}' started → workload '{wl}'")
    elif action in ("die", "stop", "kill"):
        if name in active:
            del active[name]
            log.info(f"Container '{name}' stopped → workload '{wl}' removed")
    new = best_profile()
    if new != prev:
        apply(new)


def init_from_running() -> None:
    log.info("Scanning currently running containers...")
    for name in running_containers():
        wl = CONTAINER_MAP.get(name)
        if wl:
            active[name] = wl
            log.info(f"  Found running: '{name}' → '{wl}'")
    profile = best_profile()
    log.info(f"Initial profile: '{profile}'")
    apply(profile)


def main() -> None:
    signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))

    init_from_running()

    log.info("Listening for Docker events...")
    while True:
        try:
            proc = subprocess.Popen(
                [
                    "docker", "events",
                    "--filter", "type=container",
                    "--filter", "event=start",
                    "--filter", "event=die",
                    "--filter", "event=stop",
                    "--filter", "event=kill",
                    "--format", "{{.Action}} {{.Actor.Attributes.name}}",
                ],
                stdout=subprocess.PIPE, text=True
            )
            for line in proc.stdout:
                line = line.strip()
                if not line:
                    continue
                parts = line.split(" ", 1)
                if len(parts) == 2:
                    handle_event(parts[0], parts[1])
        except Exception as e:
            log.error(f"Docker events error: {e} — retrying in 5s")
            time.sleep(5)


if __name__ == "__main__":
    main()