# -*- 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()