首次提交
This commit is contained in:
150
tools/video_bench.py
Normal file
150
tools/video_bench.py
Normal file
@@ -0,0 +1,150 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
下载一个视频 → 抽帧 → 用这些【真实帧】对 OCR 端点做压测。
|
||||
比随机生成的图更贴近线上负载(真实字幕内容,OCR 干的活和实际一致)。
|
||||
|
||||
用法(项目根目录运行):
|
||||
python temp/video_bench.py <视频...> --url http://IP:18081 [选项]
|
||||
|
||||
<视频...> 一个或多个,cos://bucket/path/x.mp4 或本地 ./video/x.mp4
|
||||
多个视频时各抽一部分帧合并成图池(max-frames 在视频间均分)
|
||||
也可用 --list 文件 从文件读视频列表(每行一个)
|
||||
|
||||
选项:
|
||||
--url URL OCR 端点(可多次,soak 只用第一个)
|
||||
--concurrency N 并发(默认 100;单台拐点约 80)
|
||||
--mode soak|sweep soak=持续满速(默认);sweep=自动加压找极限
|
||||
--fps F 抽帧频率(默认 1,压测用不需要 3fps,省时省内存)
|
||||
--max-frames N 最多用多少帧做图池(默认 2000)
|
||||
--interval N soak 打印间隔秒(默认 5)
|
||||
--ffmpeg PATH ffmpeg 路径(默认 ./bin/ffmpeg)
|
||||
--coscli PATH coscli 路径(默认 ./coscli-linux-amd64)
|
||||
--cos-config PATH coscli 配置(默认 .cos.yaml)
|
||||
--keep 跑完不删临时视频/帧
|
||||
"""
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
# 复用同目录 bench 脚本的压测逻辑
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import bench_ocr_endpoints as bench # noqa: E402
|
||||
|
||||
|
||||
def download_video(src, coscli, cos_config, dst):
|
||||
"""cos:// 路径用 coscli 下载到 dst;本地路径直接返回。"""
|
||||
if not src.startswith("cos://"):
|
||||
if not os.path.exists(src):
|
||||
print(f"本地视频不存在: {src}")
|
||||
return None
|
||||
return src
|
||||
print(f"下载视频: {src}")
|
||||
r = subprocess.run([coscli, "-c", cos_config, "cp", src, dst],
|
||||
capture_output=True, text=True)
|
||||
if r.returncode != 0 or not os.path.exists(dst):
|
||||
print(f"下载失败: {r.stderr.strip()[:300]}")
|
||||
return None
|
||||
print(f" → {dst} ({os.path.getsize(dst)/1024/1024:.0f} MB)")
|
||||
return dst
|
||||
|
||||
|
||||
def extract_frames(ffmpeg, video, frame_dir, fps):
|
||||
"""用 ffmpeg 抽帧到 frame_dir,返回帧文件列表。"""
|
||||
os.makedirs(frame_dir, exist_ok=True)
|
||||
print(f"抽帧 (fps={fps}) ...")
|
||||
cmd = [ffmpeg, "-i", video, "-vf", f"fps={fps}", "-q:v", "2",
|
||||
os.path.join(frame_dir, "f_%06d.jpg"), "-loglevel", "error", "-y"]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
print(f"抽帧失败: {r.stderr.strip()[:400]}")
|
||||
return []
|
||||
frames = sorted(glob.glob(os.path.join(frame_dir, "*.jpg")))
|
||||
print(f" 抽出 {len(frames)} 帧")
|
||||
return frames
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("videos", nargs="*", default=[], help="一个或多个视频(cos://... 或本地路径)")
|
||||
ap.add_argument("--list", default=None, help="从文件读视频列表(每行一个,可代替直接传)")
|
||||
ap.add_argument("--url", action="append", default=None, help="OCR 端点(可多次)")
|
||||
ap.add_argument("--concurrency", type=int, default=100)
|
||||
ap.add_argument("--mode", choices=["soak", "sweep"], default="soak")
|
||||
ap.add_argument("--fps", type=float, default=1.0)
|
||||
ap.add_argument("--max-frames", type=int, default=2000)
|
||||
ap.add_argument("--interval", type=int, default=5)
|
||||
ap.add_argument("--ffmpeg", default="./bin/ffmpeg")
|
||||
ap.add_argument("--coscli", default="./coscli-linux-amd64")
|
||||
ap.add_argument("--cos-config", default=".cos.yaml")
|
||||
ap.add_argument("--keep", action="store_true")
|
||||
args = ap.parse_args()
|
||||
|
||||
endpoints = bench.get_endpoints(args.url)
|
||||
if not endpoints:
|
||||
print("没有 OCR 端点,用 --url 指定")
|
||||
return
|
||||
|
||||
# 收集视频列表:命令行参数 + --list 文件
|
||||
videos = list(args.videos)
|
||||
if args.list and os.path.exists(args.list):
|
||||
with open(args.list, encoding="utf-8") as f:
|
||||
videos += [ln.strip() for ln in f if ln.strip()]
|
||||
if not videos:
|
||||
print("没有视频,传一个或多个视频路径,或用 --list 文件")
|
||||
return
|
||||
# 每个视频均分帧额度,避免一个长视频占满图池
|
||||
per_video_cap = max(1, args.max_frames // len(videos))
|
||||
|
||||
work = tempfile.mkdtemp(prefix="video_bench_")
|
||||
try:
|
||||
all_frames = []
|
||||
for i, src in enumerate(videos):
|
||||
print(f"\n[{i+1}/{len(videos)}] {src}")
|
||||
vlocal = os.path.join(work, f"video_{i}.mp4")
|
||||
fdir = os.path.join(work, f"frames_{i}")
|
||||
v = download_video(src, args.coscli, args.cos_config, vlocal)
|
||||
if not v:
|
||||
continue
|
||||
fr = extract_frames(args.ffmpeg, v, fdir, args.fps)
|
||||
if len(fr) > per_video_cap:
|
||||
fr = fr[:per_video_cap]
|
||||
print(f" 取前 {per_video_cap} 帧")
|
||||
all_frames += fr
|
||||
# 处理完即删该视频,省空间
|
||||
if v == vlocal:
|
||||
try:
|
||||
os.remove(vlocal)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
if not all_frames:
|
||||
print("没抽到任何帧")
|
||||
return
|
||||
|
||||
# 帧 → base64 图池
|
||||
print(f"\n加载 {len(all_frames)} 帧为图池 ...")
|
||||
pool = [bench.img2base64(f) for f in all_frames]
|
||||
print("=" * 60)
|
||||
print(f"图池: {len(pool)} 张真实帧(来自 {len(videos)} 个视频) | "
|
||||
f"端点: {endpoints[0]} | 并发: {args.concurrency}")
|
||||
print("=" * 60)
|
||||
|
||||
# 4) 压测
|
||||
if args.mode == "sweep":
|
||||
bench.sweep_endpoint(endpoints[0], pool[0], 10, 320, 6)
|
||||
else:
|
||||
bench.soak_endpoint(endpoints[0], pool, args.concurrency, args.interval)
|
||||
|
||||
finally:
|
||||
if args.keep:
|
||||
print(f"\n临时文件保留在: {work}")
|
||||
else:
|
||||
shutil.rmtree(work, ignore_errors=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user