Files
ocr_data/tools/hourly_stats.py
2026-09-28 16:09:31 +08:00

175 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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