#!/usr/bin/env python3 # /opt/weval-l99/screens-health-check.py # Check health de tous les ecrans cartographies, ecrit JSON atomique. # Zero regression: standalone, pas de deps externes, timeout court. import json, os, sys, time, re, tempfile from concurrent.futures import ThreadPoolExecutor, as_completed from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError CARTO_FILE = "/var/www/html/cartographie-screens.html" OUT_FILE = "/var/www/html/api/screens-health.json" WORKERS = 40 # parallelism TIMEOUT = 4 # per-check timeout (seconds) SLOW_MS = 1500 # >1500ms = SLOW def extract_data(): """Extract DATA array from cartographie-screens.html.""" if not os.path.exists(CARTO_FILE): return [] with open(CARTO_FILE, "r", encoding="utf-8") as f: html = f.read() m = re.search(r"const DATA = (\[.*?\]);", html, re.DOTALL) if not m: return [] try: return json.loads(m.group(1)) except Exception: return [] def check_one(entry): url = entry.get("url") name = entry.get("name") t0 = time.time() status = "DOWN" code = 0 elapsed_ms = 0 try: req = Request(url, method="HEAD", headers={"User-Agent": "WEVIA-HealthCheck/1.0"}) resp = urlopen(req, timeout=TIMEOUT) code = resp.status elapsed_ms = int((time.time() - t0) * 1000) if 200 <= code < 400: status = "SLOW" if elapsed_ms > SLOW_MS else "UP" else: status = "DOWN" except HTTPError as e: code = e.code elapsed_ms = int((time.time() - t0) * 1000) # 401/403 = UP but protected, still alive status = "UP" if code in (401, 403) else "DOWN" except (URLError, Exception): elapsed_ms = int((time.time() - t0) * 1000) status = "DOWN" return {"name": name, "url": url, "server": entry.get("server"), "status": status, "code": code, "ms": elapsed_ms} def main(): data = extract_data() if not data: print("NO_DATA extracted from carto, abort") sys.exit(1) print(f"Checking {len(data)} screens with {WORKERS} workers, timeout {TIMEOUT}s") t_start = time.time() results = [] with ThreadPoolExecutor(max_workers=WORKERS) as ex: futures = {ex.submit(check_one, e): e for e in data} for fut in as_completed(futures): try: results.append(fut.result()) except Exception as ex: e = futures[fut] results.append({"name": e.get("name"), "url": e.get("url"), "server": e.get("server"), "status": "ERROR", "code": 0, "ms": 0}) elapsed = time.time() - t_start # Count by status counts = {"UP": 0, "SLOW": 0, "DOWN": 0, "ERROR": 0} for r in results: counts[r["status"]] = counts.get(r["status"], 0) + 1 # Index by URL for fast lookup in frontend out = { "generated_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "elapsed_sec": round(elapsed, 1), "total": len(results), "counts": counts, "by_url": {r["url"]: {"status": r["status"], "code": r["code"], "ms": r["ms"]} for r in results} } # Atomic write (rename) tmp = tempfile.NamedTemporaryFile(mode="w", delete=False, dir="/var/www/html/api/", suffix=".tmp", encoding="utf-8") json.dump(out, tmp, separators=(",", ":")) tmp.close() os.replace(tmp.name, OUT_FILE) os.chmod(OUT_FILE, 0o644) print(f"DONE in {elapsed:.1f}s: UP={counts['UP']} SLOW={counts['SLOW']} DOWN={counts['DOWN']} ERROR={counts['ERROR']}") print(f"Written: {OUT_FILE}") if __name__ == "__main__": main()