首次提交
This commit is contained in:
147
tools/查端点存活.py
Normal file
147
tools/查端点存活.py
Normal file
@@ -0,0 +1,147 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
逐个探测 config 里的 OCR 端点, 看有多少台【真的在服务】。
|
||||
用来确认"120台只活了20台"这类问题——死端点越多, 活端点被压得越狠 → 超时。
|
||||
|
||||
用法:
|
||||
python3 tools/查端点存活.py # 自动找一张真实帧图做OCR探测
|
||||
python3 tools/查端点存活.py --tcp # 只测TCP端口连通(更快, 不发OCR)
|
||||
python3 tools/查端点存活.py --timeout 5
|
||||
python3 tools/查端点存活.py --list-dead # 额外列出死端点IP
|
||||
"""
|
||||
import argparse
|
||||
import base64
|
||||
import glob
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
from collections import Counter
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import requests
|
||||
from config.config_utils import config
|
||||
|
||||
OCR_PATH = "/youtu/ocrapi/hpgeneralocr_v3"
|
||||
DEFAULT_PORT = 18081
|
||||
|
||||
|
||||
def norm(e):
|
||||
e = str(e).strip().rstrip("/")
|
||||
if not e:
|
||||
return None
|
||||
if not e.startswith("http"):
|
||||
e = "http://" + e
|
||||
host = e.split("://", 1)[1]
|
||||
if ":" not in host:
|
||||
e = e + ":" + str(DEFAULT_PORT)
|
||||
return e
|
||||
|
||||
|
||||
def host_port(ep):
|
||||
h = ep.split("://", 1)[1]
|
||||
host, _, port = h.partition(":")
|
||||
return host, int(port or DEFAULT_PORT)
|
||||
|
||||
|
||||
def find_image_b64():
|
||||
"""找一张真实帧图转 base64; 找不到返回 None(改用TCP探测)。"""
|
||||
for pat in ("./split/**/*.jpg", "./composite/**/*.jpg", "./split/*/*.jpg"):
|
||||
for p in glob.glob(pat, recursive=True):
|
||||
try:
|
||||
with open(p, "rb") as f:
|
||||
return base64.b64encode(f.read()).decode()
|
||||
except OSError:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def probe_ocr(ep, img_b64, timeout):
|
||||
url = ep + OCR_PATH
|
||||
payload = {"app_id": str(int(time.time() * 1000)), "image": img_b64,
|
||||
"ocr_template": "OCR", "options": {"pdf_page_index": 0, "detect_text": True}}
|
||||
t0 = time.time()
|
||||
try:
|
||||
r = requests.post(url, json=payload, timeout=timeout,
|
||||
headers={"Content-Type": "application/json"})
|
||||
dt = time.time() - t0
|
||||
return (ep, "alive", dt, "") if r.status_code == 200 else (ep, "http_err", dt, f"HTTP {r.status_code}")
|
||||
except requests.exceptions.ConnectTimeout:
|
||||
return (ep, "dead", time.time() - t0, "连接超时")
|
||||
except requests.exceptions.ReadTimeout:
|
||||
return (ep, "slow", time.time() - t0, "读超时(连得上不回)")
|
||||
except requests.exceptions.ConnectionError:
|
||||
return (ep, "dead", time.time() - t0, "拒绝/不可达")
|
||||
except Exception as e:
|
||||
return (ep, "dead", time.time() - t0, str(e)[:40])
|
||||
|
||||
|
||||
def probe_tcp(ep, timeout):
|
||||
host, port = host_port(ep)
|
||||
t0 = time.time()
|
||||
try:
|
||||
with socket.create_connection((host, port), timeout=timeout):
|
||||
return (ep, "alive", time.time() - t0, "TCP通")
|
||||
except socket.timeout:
|
||||
return (ep, "dead", time.time() - t0, "TCP超时")
|
||||
except Exception as e:
|
||||
return (ep, "dead", time.time() - t0, str(e)[:40])
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--timeout", type=float, default=5.0)
|
||||
ap.add_argument("--tcp", action="store_true", help="只测TCP端口连通(不发OCR)")
|
||||
ap.add_argument("--list-dead", action="store_true")
|
||||
args = ap.parse_args()
|
||||
|
||||
eps = config.get_tencent_ocr_config().get("endpoints") or []
|
||||
eps = [u for u in (norm(e) for e in eps) if u]
|
||||
|
||||
img = None
|
||||
if not args.tcp:
|
||||
img = find_image_b64()
|
||||
if img is None:
|
||||
print("没找到帧图, 改用 TCP 连通探测(--tcp)。")
|
||||
mode = "TCP连通" if (args.tcp or img is None) else "真实OCR请求"
|
||||
print(f"探测 {len(eps)} 个端点 (方式: {mode}, 超时 {args.timeout}s)...")
|
||||
|
||||
def work(e):
|
||||
if args.tcp or img is None:
|
||||
return probe_tcp(e, args.timeout)
|
||||
return probe_ocr(e, img, args.timeout)
|
||||
|
||||
res = []
|
||||
with ThreadPoolExecutor(max_workers=64) as ex:
|
||||
for f in as_completed([ex.submit(work, e) for e in eps]):
|
||||
res.append(f.result())
|
||||
|
||||
c = Counter(r[1] for r in res)
|
||||
alive = [r for r in res if r[1] in ("alive", "http_err")]
|
||||
lats = sorted(r[2] for r in alive)
|
||||
total = len(eps)
|
||||
good = c["alive"] + c["http_err"]
|
||||
print("=" * 52)
|
||||
print(f" 可用(alive/http_err): {good}")
|
||||
print(f" 慢(读超时): {c['slow']}")
|
||||
print(f" 死(连不上/拒绝): {c['dead']}")
|
||||
if lats:
|
||||
print(f" 可用端点延迟: 中位 {lats[len(lats)//2]*1000:.0f}ms 最慢 {lats[-1]*1000:.0f}ms")
|
||||
print("=" * 52)
|
||||
print(f"结论: {total} 个端点里 {good} 个可用 ({good/total*100:.0f}%)")
|
||||
if good < total * 0.8:
|
||||
print(f"⚠️ {total-good} 个端点不可用! 负载全压在 {good} 个上 → 这极可能就是OCR超时根因。")
|
||||
print(f" 把死端点服务拉起 / 或从config删掉死的, OCR容量最高可提升 {total/max(good,1):.1f} 倍。")
|
||||
else:
|
||||
print("端点基本都活着 → OCR超时不是'端点少', 是'整体容量/CPU抢占'问题。")
|
||||
|
||||
if args.list_dead:
|
||||
print("\n死/慢端点:")
|
||||
for ep, st, dt, msg in sorted(res, key=lambda x: x[1]):
|
||||
if st in ("dead", "slow"):
|
||||
print(f" {st:6s} {ep:32s} {msg}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user