首次提交

This commit is contained in:
2026-09-28 16:09:31 +08:00
commit 4a6839319a
71 changed files with 12144 additions and 0 deletions

159
tools/throughput_jsonl.py Normal file
View File

@@ -0,0 +1,159 @@
# -*- coding: utf-8 -*-
"""
分析 数据.jsonl 的处理吞吐:单台机器一天能处理多少小时的视频数据。
两种口径:
A) 墙钟吞吐(真实):用 process_start/process_end 求整批的实际墙钟跨度,
总视频时长 ÷ 墙钟跨度 = 机器真实吞吐(含当时的并发/排队效果)。
B) 单视频平均(理想并发):按每个视频 duration/elapsed 的实时倍率,
再乘并发数估算(假设无 OCR/CPU 排队,即"OCR 速度够"的上限)。
用法: python throughput_jsonl.py [数据.jsonl路径] [并发数]
默认: python throughput_jsonl.py 数据.jsonl 1
"""
import json
import sys
from datetime import datetime
JSONL_PATH = sys.argv[1] if len(sys.argv) > 1 else "数据.jsonl"
WORKERS = int(sys.argv[2]) if len(sys.argv) > 2 else 1
OCR_QPS_PER_MACHINE = float(sys.argv[3]) if len(sys.argv) > 3 else 14.0 # 单台 OCR 的 QPS
def parse_dt(s):
if not s:
return None
try:
return datetime.fromisoformat(s)
except (ValueError, TypeError):
return None
def fmt_h(hours):
return f"{hours:.1f} 小时"
def main():
try:
f = open(JSONL_PATH, encoding="utf-8")
except FileNotFoundError:
print(f"找不到文件: {JSONL_PATH}")
return
n = 0
total_dur = 0.0 # 全部视频时长(秒)
total_elapsed = 0.0 # 全部单视频处理耗时(秒),含并发
starts, ends = [], []
per_video_ratio = [] # 每视频 duration/elapsed
stage_sum = {} # 各阶段耗时累计
with_timing = 0
total_ocr = 0 # 全部 OCR 请求次数累计
with_ocr = 0
with f:
for line in f:
line = line.strip()
if not line:
continue
try:
rec = json.loads(line)
except json.JSONDecodeError:
continue
n += 1
dur = rec.get("duration") or 0
try:
dur = float(dur)
except (TypeError, ValueError):
dur = 0.0
total_dur += dur
el = rec.get("elapsed_seconds")
if el:
total_elapsed += float(el)
if dur > 0:
per_video_ratio.append(dur / float(el))
st = parse_dt(rec.get("process_start"))
en = parse_dt(rec.get("process_end"))
if st:
starts.append(st)
if en:
ends.append(en)
sd = rec.get("stage_durations") or {}
if sd:
with_timing += 1
for k, v in sd.items():
try:
stage_sum[k] = stage_sum.get(k, 0.0) + float(v)
except (TypeError, ValueError):
pass
ic = rec.get("image_counts") or {}
toc = ic.get("total_ocr_count")
if toc:
try:
total_ocr += int(toc)
with_ocr += 1
except (TypeError, ValueError):
pass
print(f"文件: {JSONL_PATH}")
print("=" * 52)
print(f"记录数: {n}")
print(f"视频总时长: {total_dur/3600:.1f} 小时 ({total_dur:.0f} 秒)")
print(f"单视频耗时累加: {total_elapsed/3600:.1f} 小时 ({total_elapsed:.0f} 秒)")
if with_ocr:
print(f"OCR 请求总数: {total_ocr} (平均 {total_ocr/with_ocr:.0f} 次/视频, {with_ocr} 条有统计)")
# ---- 口径A:真实墙钟吞吐 ----
print("\n[A] 真实墙钟吞吐(按 process_start/end 的整批实际跨度)")
if starts and ends:
span = (max(ends) - min(starts)).total_seconds()
if span > 0:
tp = total_dur / span # 视频秒/墙钟秒
print(f" 整批墙钟跨度: {span/3600:.2f} 小时")
print(f" 实时倍率: {tp:.2f}x (每 1 秒墙钟处理 {tp:.2f} 秒视频)")
print(f" → 一天可处理: {fmt_h(tp*24)} 的视频")
vids_per_day = n / span * 86400 if span else 0
print(f" → 一天可处理: {vids_per_day:.0f} 个视频")
# ---- OCR 需求:这台机器实际产生多少 OCR/秒、能喂几台 OCR ----
if total_ocr:
ocr_per_sec = total_ocr / span
machines_fed = ocr_per_sec / OCR_QPS_PER_MACHINE
print(f"\n [OCR 配比](单台 OCR 按 {OCR_QPS_PER_MACHINE:.0f} QPS 计)")
print(f" 本机 OCR 速率: {ocr_per_sec:.2f} OCR/秒")
print(f" → 本机能喂满: {machines_fed:.2f} 台 OCR")
if machines_fed > 0:
print(f" → 一台 OCR 可喂: {1/machines_fed:.1f} 台这样的处理机")
else:
print(" 墙钟跨度为0,无法计算")
else:
print(" 缺少 process_start/process_end 字段,跳过")
# ---- 口径B:按单视频平均实时倍率 × 并发 ----
print(f"\n[B] 理想吞吐(单视频平均倍率 × 并发{WORKERS},假设无排队=OCR够快)")
if per_video_ratio:
avg_ratio = sum(per_video_ratio) / len(per_video_ratio)
# 也给一个总量口径:总视频时长/总耗时(更稳,受长短视频加权)
weighted = total_dur / total_elapsed if total_elapsed else 0
print(f" 单视频平均倍率: {avg_ratio:.2f}x")
print(f" 加权倍率(总/总):{weighted:.2f}x")
print(f" → 单并发一天: {fmt_h(weighted*24)} 的视频")
print(f" → {WORKERS}并发一天: {fmt_h(weighted*24*WORKERS)} 的视频(理想线性,实际受CPU限制)")
else:
print(" 缺少 elapsed_seconds 字段,跳过")
# ---- 各阶段占比 ----
if stage_sum:
print(f"\n[阶段耗时占比]({with_timing} 条有计时)")
main_t = stage_sum.get("main", sum(v for k, v in stage_sum.items() if k != "main"))
for k in ["split_frame", "merge", "getsrt"]:
if k in stage_sum:
pct = stage_sum[k] / main_t * 100 if main_t else 0
print(f" {k:12s}: {stage_sum[k]/3600:6.1f} 小时 ({pct:.0f}%)")
if __name__ == "__main__":
main()