stats: /api/stats 增加 probe 可路由探测缓存信息与 denied_detail(带剩余TTL), 供 CF 集中面板展示; denied 原字段形状不变(兼容自身面板)
Browse files
server.py
CHANGED
|
@@ -201,6 +201,8 @@ class TokenPool:
|
|
| 201 |
self.surplus_dropped = 0
|
| 202 |
self.expired_dropped = 0
|
| 203 |
self.known_supported: set[str] = set() # 后台探测到的可路由集合
|
|
|
|
|
|
|
| 204 |
self.routable_seen: set[str] = set() # 实测跑通过的模型
|
| 205 |
self.denied: dict[str, float] = {} # 上游403否定缓存 model->ts
|
| 206 |
self.requests = collections.deque(maxlen=50)
|
|
@@ -607,6 +609,8 @@ class TokenPool:
|
|
| 607 |
data = json.loads(PROBE_CACHE.read_text(encoding="utf-8"))
|
| 608 |
if time.time() - data.get("ts", 0) < cfg.PROBE_CACHE_TTL:
|
| 609 |
self.known_supported.update(data.get("models", []))
|
|
|
|
|
|
|
| 610 |
log(f"[probe] 可路由集合用缓存 {len(self.known_supported)} 个")
|
| 611 |
return
|
| 612 |
except Exception:
|
|
@@ -632,6 +636,8 @@ class TokenPool:
|
|
| 632 |
async with httpx.AsyncClient() as c:
|
| 633 |
await asyncio.gather(*[probe(i) for i in cands])
|
| 634 |
log(f"[probe] 匿名可路由候选 {len(self.known_supported)} 个")
|
|
|
|
|
|
|
| 635 |
try:
|
| 636 |
PROBE_CACHE.write_text(json.dumps(
|
| 637 |
{"ts": time.time(),
|
|
@@ -703,6 +709,16 @@ class TokenPool:
|
|
| 703 |
"workers_max": self.max_worker_pages,
|
| 704 |
"routable": self.routable(),
|
| 705 |
"denied": sorted(m for m in self.denied if self.is_denied(m)),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 706 |
"config": {
|
| 707 |
"shelf_size": self.shelf_size,
|
| 708 |
"token_max_age": self.token_max_age,
|
|
|
|
| 201 |
self.surplus_dropped = 0
|
| 202 |
self.expired_dropped = 0
|
| 203 |
self.known_supported: set[str] = set() # 后台探测到的可路由集合
|
| 204 |
+
self.probe_ts: float | None = None # 最近一次可路由探测完成时刻(墙钟, 缓存命中时取文件ts)
|
| 205 |
+
self.probe_from_cache = False # 本次探测结果是否直接来自磁盘缓存
|
| 206 |
self.routable_seen: set[str] = set() # 实测跑通过的模型
|
| 207 |
self.denied: dict[str, float] = {} # 上游403否定缓存 model->ts
|
| 208 |
self.requests = collections.deque(maxlen=50)
|
|
|
|
| 609 |
data = json.loads(PROBE_CACHE.read_text(encoding="utf-8"))
|
| 610 |
if time.time() - data.get("ts", 0) < cfg.PROBE_CACHE_TTL:
|
| 611 |
self.known_supported.update(data.get("models", []))
|
| 612 |
+
self.probe_ts = data.get("ts") or time.time()
|
| 613 |
+
self.probe_from_cache = True
|
| 614 |
log(f"[probe] 可路由集合用缓存 {len(self.known_supported)} 个")
|
| 615 |
return
|
| 616 |
except Exception:
|
|
|
|
| 636 |
async with httpx.AsyncClient() as c:
|
| 637 |
await asyncio.gather(*[probe(i) for i in cands])
|
| 638 |
log(f"[probe] 匿名可路由候选 {len(self.known_supported)} 个")
|
| 639 |
+
self.probe_ts = time.time()
|
| 640 |
+
self.probe_from_cache = False
|
| 641 |
try:
|
| 642 |
PROBE_CACHE.write_text(json.dumps(
|
| 643 |
{"ts": time.time(),
|
|
|
|
| 709 |
"workers_max": self.max_worker_pages,
|
| 710 |
"routable": self.routable(),
|
| 711 |
"denied": sorted(m for m in self.denied if self.is_denied(m)),
|
| 712 |
+
"denied_detail": [ # 带剩余 TTL, 供集中面板展示
|
| 713 |
+
{"model": m, "remain_s": max(0, int(self.denied_ttl - (time.time() - ts)))}
|
| 714 |
+
for m, ts in sorted(self.denied.items(), key=lambda kv: -kv[1])
|
| 715 |
+
if self.is_denied(m)
|
| 716 |
+
],
|
| 717 |
+
"probe": { # 可路由探测缓存状态
|
| 718 |
+
"age_s": (int(time.time() - self.probe_ts) if self.probe_ts else None),
|
| 719 |
+
"models": len(self.known_supported),
|
| 720 |
+
"from_cache": self.probe_from_cache,
|
| 721 |
+
},
|
| 722 |
"config": {
|
| 723 |
"shelf_size": self.shelf_size,
|
| 724 |
"token_max_age": self.token_max_age,
|