#!/usr/bin/env python3
"""Checkmk-Local-Check für BOINC.

Ablage auf dem überwachten Rechner: /usr/lib/check_mk_agent/local/boinc (ausführbar).
Meldet: läuft der Client, Fehler der letzten 24 Stunden, Zeit seit dem letzten Ergebnis, Aufgaben.
"""
import glob
import os
import re
import shutil
import subprocess
import time
from datetime import datetime

DATEN = "/var/lib/boinc-client"
FEHLER_WARN, FEHLER_CRIT = 1, 3
STUNDEN_WARN, STUNDEN_CRIT = 18, 36
FEHLERMUSTER = re.compile(
    r"computation error|compute error|aborted|exited with zero status but no 'finished' file|"
    r"process exited with code [1-9]|too many (exit|error)|disk usage limit exceeded",
    re.IGNORECASE,
)

boinccmd = "/usr/local/bin/boinccmd" if os.path.exists("/usr/local/bin/boinccmd") else shutil.which("boinccmd")


def cmd(*args):
    try:
        r = subprocess.run([boinccmd, *args], cwd=DATEN, capture_output=True, text=True, timeout=20)
        return r.stdout
    except Exception:
        return ""


def ausgabe(status, name, metriken, text):
    print(f'{status} "{name}" {metriken or "-"} {text}')


jetzt = time.time()

# 1. Client
aktiv = subprocess.run(["systemctl", "is-active", "boinc-client"], capture_output=True, text=True).stdout.strip()
status_text = cmd("--get_cc_status")
if aktiv != "active" or not boinccmd:
    ausgabe(2, "BOINC Client", "", f"Dienst boinc-client ist {aktiv or 'unbekannt'}")
    raise SystemExit(0)
angehalten = "suspended:" in status_text and "not suspended" not in status_text.split("CPU status")[-1].split("GPU status")[0]
ausgabe(1 if angehalten else 0, "BOINC Client", "", "Client läuft, Rechnen angehalten" if angehalten else "Client läuft und rechnet")

# 2. Fehler der letzten 24 Stunden aus den Meldungen
fehler = 0
beispiel = ""
for zeile in cmd("--get_messages").splitlines():
    m = re.match(r"\d+: (\d{2}-\w{3}-\d{4} \d{2}:\d{2}:\d{2}) ", zeile)
    if not m:
        continue
    try:
        t = datetime.strptime(m.group(1), "%d-%b-%Y %H:%M:%S").timestamp()
    except ValueError:
        continue
    if jetzt - t <= 86400 and FEHLERMUSTER.search(zeile):
        fehler += 1
        beispiel = zeile.split("] ", 1)[-1][:90]
st = 2 if fehler >= FEHLER_CRIT else 1 if fehler >= FEHLER_WARN else 0
ausgabe(st, "BOINC Fehler 24h", f"fehler={fehler};{FEHLER_WARN};{FEHLER_CRIT}",
        f"{fehler} Fehlermeldungen in 24 h" + (f", zuletzt: {beispiel}" if beispiel else ""))

# 3. Ergebnisse aus dem Job-Log
letzte, anzahl_24h = 0.0, 0
for log in glob.glob(os.path.join(DATEN, "job_log_*.txt")):
    with open(log, encoding="utf-8", errors="replace") as f:
        for zeile in f:
            teile = zeile.split()
            if teile and teile[0].isdigit():
                t = float(teile[0])
                letzte = max(letzte, t)
                if jetzt - t <= 86400:
                    anzahl_24h += 1
if letzte:
    stunden = (jetzt - letzte) / 3600
    st = 2 if stunden > STUNDEN_CRIT else 1 if stunden > STUNDEN_WARN else 0
    ausgabe(st, "BOINC Ergebnisse",
            f"stunden_seit_ergebnis={stunden:.1f};{STUNDEN_WARN};{STUNDEN_CRIT}|ergebnisse_24h={anzahl_24h}",
            f"letztes Ergebnis vor {stunden:.1f} h, {anzahl_24h} in den letzten 24 h")
else:
    ausgabe(1, "BOINC Ergebnisse", "ergebnisse_24h=0", "noch kein fertiges Ergebnis im Job-Log")

# 4. Aufgaben
aufgaben = cmd("--get_tasks")
gesamt = len(re.findall(r"^\s+name: ", aufgaben, re.MULTILINE))
laufend = aufgaben.count("active_task_state: EXECUTING")
ausgabe(1 if gesamt and not laufend else 0, "BOINC Aufgaben", f"laufend={laufend}|gesamt={gesamt}",
        f"{laufend} laufen, {gesamt} insgesamt" + (" – keine läuft" if gesamt and not laufend else ""))
