Files

148 lines
5.0 KiB
Python
Raw Permalink 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 -*-
r'''塌陷窗口分析器:专门看"连接总数 <= 3"的那些窗口里发生了什么。
== 为什么需要专门看窗口 ==
总体中位数两次都会被稳态主导(都是 61 秒 / 1.2 秒),区分不出 A/B 好坏。
真正的差别只在 QUIC 塌陷的那几分钟里体现:
A 组 quic8+h2 2:塌陷时只剩 2 条在扛
B 组 quic6+h2 4:塌陷时还有 4 条
所以核心指标是:
* 「连接总数 <= 3」的采样占比(= 风险暴露时间)
* 这些窗口里公网可用率
用法:
python _build/rp_collapse.py <标签> <conns文件> <health文件>
'''
from __future__ import annotations
import sys
from datetime import datetime
from pathlib import Path
# 塌陷判定:总连接数 <= 这个值就算"容量很薄"
COLLAPSE_TOTAL = 3
def parse_ts(path: str, cols: int):
rows = []
for line in Path(path).read_text(encoding="utf-8", errors="replace").splitlines():
parts = line.split("\t")
if len(parts) < cols:
continue
try:
t = datetime.strptime(parts[0].strip(), "%Y-%m-%d %H:%M:%S")
except ValueError:
continue
rows.append((t, parts))
return rows
def main() -> int:
if len(sys.argv) < 4:
print("用法: rp_collapse.py <标签> <conns> <health>")
return 2
label, conns_path, health_path = sys.argv[1], sys.argv[2], sys.argv[3]
conns = parse_ts(conns_path, 3)
health = parse_ts(health_path, 3)
if not conns:
print(" %s: 连接采样为空" % label)
return 1
# ---- 连接数统计 ----
samples = []
for t, p in conns:
try:
q = int(p[1])
except ValueError:
q = None
try:
h = int(p[2])
except ValueError:
h = None
samples.append((t, q, h))
valid = [(t, q, h) for t, q, h in samples if q is not None and h is not None]
if not valid:
print(" %s: 没有有效的连接采样" % label)
return 1
span = (valid[-1][0] - valid[0][0]).total_seconds()
total_list = [q + h for _t, q, h in valid]
collapse = [(t, q, h) for t, q, h in valid if q + h <= COLLAPSE_TOTAL]
collapse_secs = len(collapse) * 60.0 # 每 60 秒一个采样
collapse_pct = len(collapse) * 100.0 / len(valid)
print("=" * 62)
print(" %s" % label)
print(" 采样窗口 %.0f 分钟(%d 个连接采样)" % (span / 60, len(valid)))
print("=" * 62)
print()
print(" 连接数分布:")
from collections import Counter
cnt = Counter(total_list)
for k in sorted(cnt, reverse=True):
bar = "#" * min(cnt[k], 50)
print(" 总数 %2d : %3d 次 %s" % (k, cnt[k], bar))
print()
print(" 平均连接数: %.2f" % (sum(total_list) / len(total_list)))
print()
# ---- 塌陷窗口 ----
print(" ── 塌陷窗口(连接总数 <= %d)──" % COLLAPSE_TOTAL)
print(" 出现 %d 次采样 = 约 %.0f 秒 = 占窗口的 %.1f%%"
% (len(collapse), collapse_secs, collapse_pct))
if collapse:
print(" 明细:")
for t, q, h in collapse:
print(" %s quic=%s http2=%s 合计=%d"
% (t.strftime("%H:%M:%S"), q, h, q + h))
# ---- 塌陷窗口内的公网可用率 ----
print()
print(" ── 塌陷窗口内的公网可用率 ──")
if not collapse or not health:
print(" (窗口内没有健康样本)")
else:
# 把塌陷时刻扩展成 ±60 秒的窗口,再取落在里面的健康样本
windows = [(t.timestamp() - 60, t.timestamp() + 60) for t, _q, _h in collapse]
inside = []
for t, p in health:
ts = t.timestamp()
if any(a <= ts <= b for a, b in windows):
inside.append((t, p[1]))
if inside:
ok = sum(1 for _t, c in inside if c == "200")
print(" 窗口覆盖到 %d 个健康样本,成功 %d = %.1f%%"
% (len(inside), ok, ok * 100.0 / len(inside)))
bad = [(t, c) for t, c in inside if c != "200"]
if bad:
print(" 失败明细:")
for t, c in bad:
print(" %s 码=%s" % (t.strftime("%H:%M:%S"), c))
else:
print(" (窗口内没有健康样本)")
# ---- 总体可用率 ----
if health:
ok = sum(1 for _t, p in health if p[1] == "200")
print()
print(" ── 总体公网可用率(对照)──")
print(" %d/%d = %.2f%%" % (ok, len(health), ok * 100.0 / len(health)))
# ---- 一句话结论 ----
print()
print(" ── 一句话 ──")
print(" 风险暴露(总数<=%d): %.1f%% 窗口内可用率: %s"
% (COLLAPSE_TOTAL, collapse_pct,
("%.1f%%" % (sum(1 for _t, c in inside if c == "200") * 100.0 / len(inside))
if collapse and health and inside else "无数据")))
return 0
if __name__ == "__main__":
sys.exit(main())