From eea538ab179437f93786cfeb5947adab4a72306c Mon Sep 17 00:00:00 2001 From: Josue Zamudio Date: Mon, 31 Aug 2026 15:17:20 +0000 Subject: [PATCH] Background CDN probe: log throughput + state flips every 3 min 64KB Range fetch from a fixed episode (JJK S2 E25) on a daemon thread; logs '[probe] CDN OK/DEGRADED (N KB/s)' on state change so server.log shows exactly when a degraded window ends. 18/18 tests, verified live. --- server/app.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/server/app.py b/server/app.py index 11de78b..25de3ad 100644 --- a/server/app.py +++ b/server/app.py @@ -7,6 +7,7 @@ Cloudflare workaround) and rewrites playlist URIs to go through itself. import base64 import json import os +import threading import time from collections import Counter, OrderedDict from datetime import datetime @@ -24,6 +25,42 @@ from . import db, upstream, users ROOT = Path(__file__).parent.parent app = FastAPI(title="ani-web") + +def _cdn_probe(): + """Background probe: pull 64KB from the CDN every 3 min and log state + flips, so server.log shows when a degraded window ends (CDN RECOVERED). + Probe target: any valid episode (36019 = JJK S2 E25). + ponytail: single fixed episode; if it ever disappears, the probe logs + DEGRADED forever — swap the ID, don't build a picker.""" + def first(pl, base): + l = next(x.strip() for x in pl.splitlines() + if x.strip() and not x.startswith("#")) + return l if l.startswith("http") else base + l + + state = None + while True: + try: + m = upstream.master_m3u8(36019, "jpn") + if not m: + raise RuntimeError("no master m3u8") + base = m.rsplit("/", 1)[0] + "/" + v = first(upstream._get(m, timeout=10).text, base) + seg = first(upstream._get(v, timeout=10).text, base) + t = time.time() + r = upstream._session().get(seg, headers={"Range": "bytes=0-65535"}, + impersonate="chrome", timeout=10) + kbps = len(r.content) / 1024 / max(time.time() - t, 0.01) + except Exception: + kbps = 0.0 + now = "OK" if kbps >= 40 else "DEGRADED" + if now != state: + print(f"[probe] CDN {now} ({kbps:.0f} KB/s)", flush=True) + state = now + time.sleep(180) + + +threading.Thread(target=_cdn_probe, daemon=True).start() + # ---------- auth: multi-user, signed cookies ---------- users.migrate_legacy() # file-based legacy import; db.init() absorbs its output db.init()