# -*- coding: utf-8 -*- """ 按【小时】统计 数据.jsonl:每小时处理多少条视频、视频总时长、成功情况、处理倍速。 小时取 process_start(没有则回退 timestamp)的 YYYY-MM-DD HH。 "处理倍速" = 该小时内处理掉的【视频内容总时长】 / 1 墙钟小时。 例: 一个墙钟小时里处理了 40 小时的视频内容 → 倍速 40x → 等效有效并发≈40。 这个数最能反映真实吞吐/并发利用率(对照 max-workers 看是否吃满)。 用法(项目根目录运行): python temp/hourly_stats.py [数据.jsonl路径] [--last N] --last N : 只看最近 N 个有数据的小时(默认全部) """ import json import sys import unicodedata from collections import defaultdict from datetime import datetime, timedelta def _parse(ts): try: return datetime.fromisoformat(ts) except (TypeError, ValueError): return None def _dw(s): """字符串显示宽度(中文/全角算2)。""" return sum(2 if unicodedata.east_asian_width(c) in ("W", "F") else 1 for c in str(s)) def _padr(s, w): """按显示宽度右对齐。""" s = str(s) return " " * max(0, w - _dw(s)) + s def _padl(s, w): """按显示宽度左对齐。""" s = str(s) return s + " " * max(0, w - _dw(s)) # 列宽(显示宽度) COLW = {"小时": 16, "视频数": 8, "成功": 6, "无字幕": 8, "错误": 6, "总时长h": 10, "成功h": 9, "处理倍速": 10, "有效并发": 10} def _row(hour, count, success, no_sub, error, dur_h, ok_h, speed, eff): return (_padl(hour, COLW["小时"]) + _padr(count, COLW["视频数"]) + _padr(success, COLW["成功"]) + _padr(no_sub, COLW["无字幕"]) + _padr(error, COLW["错误"]) + _padr(dur_h, COLW["总时长h"]) + _padr(ok_h, COLW["成功h"]) + _padr(speed, COLW["处理倍速"]) + _padr(eff, COLW["有效并发"])) args = [a for a in sys.argv[1:]] last_n = None if "--last" in args: i = args.index("--last") try: last_n = int(args[i + 1]) del args[i:i + 2] except (IndexError, ValueError): last_n = None JSONL_PATH = args[0] if args else "数据.jsonl" def main(): try: f = open(JSONL_PATH, encoding="utf-8") except FileNotFoundError: print(f"找不到文件: {JSONL_PATH}") return hours = defaultdict(lambda: {"count": 0, "success": 0, "no_subtitle": 0, "error": 0, "dur": 0.0, "dur_ok": 0.0, "elapsed": 0.0}) busy = defaultdict(float) # 每个墙钟小时内, 所有视频实际占用的总秒数(精确并发用) no_date = 0 with f: for line in f: line = line.strip() if not line: continue try: rec = json.loads(line) except json.JSONDecodeError: continue ts = rec.get("process_start") or rec.get("timestamp") or "" hour = ts[:13] if len(ts) >= 13 else None # YYYY-MM-DD HH(按开始时间归桶) if not hour: no_date += 1 continue h = hours[hour] h["count"] += 1 status = rec.get("status") if status == "success": h["success"] += 1 elif status == "no_subtitle": h["no_subtitle"] += 1 else: h["error"] += 1 try: dur = float(rec.get("duration") or 0) h["dur"] += dur if status == "success": h["dur_ok"] += dur except (TypeError, ValueError): pass try: h["elapsed"] += float(rec.get("elapsed_seconds") or 0) except (TypeError, ValueError): pass # 精确并发: 把 [process_start, process_end] 的时长按实际跨越的每个小时切开累加, # 避免"长视频整段算进开始那个小时"导致失真。 st = _parse(rec.get("process_start")) en = _parse(rec.get("process_end")) if st and en and en > st: cur = st while cur < en: hk = cur.strftime("%Y-%m-%dT%H") nxt = cur.replace(minute=0, second=0, microsecond=0) + timedelta(hours=1) seg_end = min(en, nxt) busy[hk] += (seg_end - cur).total_seconds() cur = seg_end if not hours: print("没有可统计的数据") return keys = sorted(hours) if last_n: keys = keys[-last_n:] width = sum(COLW.values()) print(f"文件: {JSONL_PATH}") print("=" * width) print(_row("小时", "视频数", "成功", "无字幕", "错误", "总时长h", "成功h", "处理倍速", "有效并发")) print("-" * width) tot = {"count": 0, "success": 0, "no_subtitle": 0, "error": 0, "dur": 0.0, "dur_ok": 0.0, "elapsed": 0.0} busy_sum = 0.0 for hour in keys: h = hours[hour] speed = h["dur"] / 3600 # 处理倍速: 视频内容小时数 / 1墙钟小时 eff = busy[hour] / 3600 # 有效并发: 该小时所有视频实际占用秒数 / 3600(=平均并发) busy_sum += busy[hour] print(_row(hour, h["count"], h["success"], h["no_subtitle"], h["error"], f"{h['dur']/3600:.1f}", f"{h['dur_ok']/3600:.1f}", f"{speed:.1f}x", f"{eff:.1f}")) for k in tot: tot[k] += h[k] print("-" * width) nh = len(keys) print(_row("合计/均值", tot["count"], tot["success"], tot["no_subtitle"], tot["error"], f"{tot['dur']/3600:.1f}", f"{tot['dur_ok']/3600:.1f}", f"{tot['dur']/3600/nh:.1f}x", f"{busy_sum/3600/nh:.1f}")) if no_date: print(f"\n({no_date} 条无时间字段,已跳过)") print(f"\n共 {nh} 个小时 | 时均处理 {tot['count']/nh:.0f} 条 | " f"时均成功 {tot['success']/nh:.0f} 条") if tot["count"]: print(f"成功率 {tot['success']/tot['count']*100:.1f}%") print("提示: '有效并发'明显小于你设的 max-workers → 有串行瓶颈(GIL/锁/等待),不是并发不够。") if __name__ == "__main__": main()