首次提交

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

157
tools/compare_jsonl.py Normal file
View File

@@ -0,0 +1,157 @@
# -*- coding: utf-8 -*-
"""
并排对比两个(或多个)机器的 数据.jsonl,定位谁慢、慢在哪个阶段。
用法:
python temp/compare_jsonl.py 2号机器数据.jsonl 3号机器数据.jsonl
python temp/compare_jsonl.py a.jsonl b.jsonl c.jsonl ...
"""
import json
import sys
from datetime import datetime
def parse_dt(s):
try:
return datetime.fromisoformat(s)
except (ValueError, TypeError):
return None
def analyze(path):
"""读一个 jsonl,返回汇总指标。"""
s = {
"n": 0, "ok": 0, "err": 0, "no_sub": 0,
"dur": 0.0, "elapsed": 0.0,
"stage": {"split_frame": 0.0, "merge": 0.0, "getsrt": 0.0},
"ocr": 0, "n_ocr": 0,
"starts": [], "ends": [],
}
try:
f = open(path, encoding="utf-8")
except FileNotFoundError:
return None
with f:
for line in f:
line = line.strip()
if not line:
continue
try:
r = json.loads(line)
except json.JSONDecodeError:
continue
s["n"] += 1
st = r.get("status")
if st == "success":
s["ok"] += 1
elif st == "no_subtitle":
s["no_sub"] += 1
else:
s["err"] += 1
try:
s["dur"] += float(r.get("duration") or 0)
except (TypeError, ValueError):
pass
el = r.get("elapsed_seconds")
if el:
try:
s["elapsed"] += float(el)
except (TypeError, ValueError):
pass
sd = r.get("stage_durations") or {}
for k in s["stage"]:
try:
s["stage"][k] += float(sd.get(k) or 0)
except (TypeError, ValueError):
pass
ic = r.get("image_counts") or {}
toc = ic.get("total_ocr_count")
if toc:
try:
s["ocr"] += int(toc)
s["n_ocr"] += 1
except (TypeError, ValueError):
pass
ds = parse_dt(r.get("process_start"))
de = parse_dt(r.get("process_end"))
if ds:
s["starts"].append(ds)
if de:
s["ends"].append(de)
return s
def derive(s):
"""从汇总算出对比指标。"""
d = {}
n = max(1, s["n"])
d["记录数"] = s["n"]
d["成功率%"] = round(s["ok"] / n * 100, 1)
d["视频总时长h"] = round(s["dur"] / 3600, 1)
# 墙钟跨度 + 实时倍率
span = 0.0
if s["starts"] and s["ends"]:
span = (max(s["ends"]) - min(s["starts"])).total_seconds()
d["墙钟跨度h"] = round(span / 3600, 2)
d["实时倍率x"] = round(s["dur"] / span, 2) if span > 0 else 0
d["视频/天"] = round(s["n"] / span * 86400) if span > 0 else 0
d["有效并发"] = round(s["elapsed"] / span, 1) if span > 0 else 0
# 平均每视频各阶段秒数
d["抽帧s/视频"] = round(s["stage"]["split_frame"] / n, 1)
d["拼接s/视频"] = round(s["stage"]["merge"] / n, 1)
d["getsrt s/视频"] = round(s["stage"]["getsrt"] / n, 1)
total_stage = sum(s["stage"].values()) or 1
d["抽帧占比%"] = round(s["stage"]["split_frame"] / total_stage * 100)
d["拼接占比%"] = round(s["stage"]["merge"] / total_stage * 100)
d["getsrt占比%"] = round(s["stage"]["getsrt"] / total_stage * 100)
d["平均OCR次/视频"] = round(s["ocr"] / s["n_ocr"]) if s["n_ocr"] else 0
d["平均耗时s/视频"] = round(s["elapsed"] / n, 1)
return d
def main():
paths = sys.argv[1:]
if len(paths) < 2:
print("用法: python temp/compare_jsonl.py a.jsonl b.jsonl [c.jsonl ...]")
return
cols, names = [], []
for p in paths:
s = analyze(p)
if s is None:
print(f"找不到文件: {p}")
return
cols.append(derive(s))
names.append(p.replace("数据.jsonl", "").replace(".jsonl", "") or p)
keys = list(cols[0].keys())
w = max(14, max(len(n) for n in names) + 2)
print("=" * (18 + w * len(cols)))
print(f"{'指标':<16}" + "".join(f"{n:>{w}}" for n in names))
print("-" * (18 + w * len(cols)))
for k in keys:
print(f"{k:<16}" + "".join(f"{str(c[k]):>{w}}" for c in cols))
print("=" * (18 + w * len(cols)))
# 自动点评(两台时)
if len(cols) == 2:
a, b = cols
slow_i = 0 if a["实时倍率x"] < b["实时倍率x"] else 1
fast_i = 1 - slow_i
print(f"\n【分析】{names[slow_i]} 更慢(实时倍率 "
f"{cols[slow_i]['实时倍率x']}x vs {cols[fast_i]['实时倍率x']}x)")
# 找慢在哪个阶段
for stage, key in [("抽帧", "抽帧s/视频"), ("拼接", "拼接s/视频"), ("getsrt(OCR)", "getsrt s/视频")]:
sv, fv = cols[slow_i][key], cols[fast_i][key]
if fv and sv > fv * 1.3:
print(f" ▸ {stage} 慢: {sv}s vs {fv}s (慢 {sv/fv:.1f}x)")
if cols[slow_i]["有效并发"] < cols[fast_i]["有效并发"] * 0.8:
print(f" ▸ 并发低: {cols[slow_i]['有效并发']} vs {cols[fast_i]['有效并发']}"
f"(CPU/内存/OCR 排队?)")
if cols[slow_i]["平均OCR次/视频"] > cols[fast_i]["平均OCR次/视频"] * 1.3:
print(f" ▸ OCR 次数多: {cols[slow_i]['平均OCR次/视频']} vs "
f"{cols[fast_i]['平均OCR次/视频']}(视频更长/composite更多)")
if __name__ == "__main__":
main()