diff --git a/.gitignore b/.gitignore index ec2badf..2248305 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,11 @@ desktop.ini /_build/.gitee_token *.token .env +# ---- 测试用的临时数据目录(会被测试脚本移来移去)---- +/windows/data/ +/windows/data_bk_*/ +/_build/*_data/ +/_build/_pairtest/ +/_build/_whitetest/ +/_tests/_stubdata/ +/_tests/_*_data/ \ No newline at end of file diff --git a/_build/analyze_2h.py b/_build/analyze_2h.py new file mode 100644 index 0000000..4e8906d --- /dev/null +++ b/_build/analyze_2h.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +r'''分析 2 小时长测数据(列格式:时间戳 \t 状态码 \t 毫秒)。 + +上一版解析写错了 —— 它把任何 3 位数字都当状态码, +于是毫秒(921、968…)也被算成状态码,可用率算出来只有 84.8%, +是**假数字**。这里改成按列位置取。 +''' +from __future__ import annotations + +import sys +from collections import Counter +from pathlib import Path + +path = Path(sys.argv[1] if len(sys.argv) > 1 else "/root/long2h_health.tsv") +if not path.is_file(): + print(" 文件不存在: %s" % path) + raise SystemExit(0) + +rows = [] +bad = 0 +for line in path.read_text(encoding="utf-8", errors="replace").splitlines(): + line = line.strip() + if not line: + continue + parts = line.split("\t") + if len(parts) < 3: + bad += 1 + continue + stamp, code, ms = parts[0], parts[1].strip(), parts[2].strip() + try: + rows.append((stamp, code, float(ms))) + except ValueError: + bad += 1 + +print(" 样本 %d 条(解析不了 %d 条)" % (len(rows), bad)) +if not rows: + raise SystemExit(0) + +print(" 时间范围: %s -> %s" % (rows[0][0], rows[-1][0])) + +codes = Counter(c for _s, c, _m in rows) +total = len(rows) +ok = codes.get("200", 0) + +print() +print(" ── HTTP 状态分布 ──") +for c, n in codes.most_common(): + print(" %-6s %4d 次 %5.1f%%" % (c, n, n * 100.0 / total)) +print(" " + "-" * 30) +print(" 可用率: %d/%d = %.2f%%" % (ok, total, ok * 100.0 / total)) + +# 只看成功的耗时(失败的那些耗时没有意义) +good = sorted(m for _s, c, m in rows if c == "200") +if good: + def p(q): + return good[min(int(len(good) * q), len(good) - 1)] + print() + print(" ── 成功请求耗时(毫秒)──") + print(" 最小 %.0f 中位 %.0f P90 %.0f P99 %.0f 最大 %.0f" + % (good[0], good[len(good) // 2], p(0.9), p(0.99), good[-1])) + +# 如果还有第 4 列及以后(连接数之类),也统计一下 +extra_cols = set() +for line in path.read_text(encoding="utf-8", errors="replace").splitlines(): + p = line.strip().split("\t") + if len(p) > 3: + extra_cols.add(len(p)) +if extra_cols: + print() + print(" (文件里还有更多列,最多 %d 列)" % max(extra_cols)) + +fails = [(s, c, m) for s, c, m in rows if c != "200"] +print() +if fails: + print(" ── 失败样本(共 %d 条)──" % len(fails)) + for s, c, m in fails[:15]: + print(" %s HTTP %-5s %.0fms" % (s, c, m)) + if len(fails) > 15: + print(" … 还有 %d 条" % (len(fails) - 15)) + # 失败时间分布(按 10 分钟分桶) + print() + print(" ── 失败时刻分布(按小时)──") + hours = Counter(s.split(" ")[1].split(":")[0] if " " in s else s[:2] + for s, _c, _m in fails) + for h in sorted(hours): + print(" %s 点 %d 次" % (h, hours[h])) +else: + print(" ✓ 全程没有非 200 —— 2 小时 100% 可用") diff --git a/_build/analyze_2h.sh b/_build/analyze_2h.sh new file mode 100644 index 0000000..b7e5990 --- /dev/null +++ b/_build/analyze_2h.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# 分析 relay-stability 的 2 小时健康长测数据(只读) +set -u + +F=/root/long2h_health.tsv +[ -f "$F" ] || { echo " 数据文件不存在: $F"; exit 0; } + +echo " 文件: $F 行数: $(wc -l < "$F")" +echo " 前 3 行(看格式):" +head -3 "$F" | sed 's/^/ /' +echo + +python3 - "$F" <<'PYEOF' +import sys +from collections import Counter +from pathlib import Path + +path = Path(sys.argv[1]) +lines = [l.strip() for l in path.read_text(encoding="utf-8", + errors="replace").splitlines() + if l.strip()] +if not lines: + print(" 没有数据") + raise SystemExit(0) + +# 猜列:先看表头,没有表头就按 tab 切 +head = lines[0].lower() +has_header = any(k in head for k in ("time", "code", "ms", "http")) +rows = lines[1:] if has_header else lines +if has_header: + print(" 表头: %s" % lines[0]) +print(" 样本数: %d" % len(rows)) + +codes = Counter() +times = [] +conns = [] +fails = [] +for i, line in enumerate(rows, 1): + parts = line.split("\t") + code = None + ms = None + for p in parts: + p = p.strip() + if p.isdigit() and len(p) == 3: + code = p + if p.replace(".", "").isdigit() and "." in p: + try: + v = float(p) + if ms is None and v < 300000: + ms = v + except ValueError: + pass + if code: + codes[code] += 1 + if code != "200": + fails.append((i, line[:100])) + if ms: + times.append(ms) + # 连接数:找形如 quic=N h2=M 或者单独的数字 + for p in parts: + if "quic=" in p: + conns.append(p.strip()) + +total = sum(codes.values()) +ok = codes.get("200", 0) +print() +print(" ── HTTP 结果分布 ──") +for c, n in codes.most_common(): + pct = n * 100.0 / total if total else 0 + print(" %-6s %4d 次 %.1f%%" % (c, n, pct)) +print(" --------") +print(" 可用率: %d/%d = %.1f%%" % (ok, total, ok * 100.0 / total if total else 0)) + +if times: + s = sorted(times) + def pct(q): + return s[min(int(len(s) * q), len(s) - 1)] + print() + print(" ── 耗时分布(毫秒)──") + print(" 最小 %.0f 中位 %.0f P90 %.0f P99 %.0f 最大 %.0f" + % (s[0], s[len(s)//2], pct(0.9), pct(0.99), s[-1])) + +if conns: + c = Counter(conns) + print() + print(" ── 连接数分布(前 8)──") + for k, n in c.most_common(8): + print(" %-28s %d 次" % (k, n)) + +if fails: + print() + print(" ── 失败样本(最多 12 条)──") + for i, line in fails[:12]: + print(" 第 %d 行: %s" % (i, line)) +else: + print() + print(" ✓ 全程没有非 200") +PYEOF diff --git a/_build/chk_tunnel_now.sh b/_build/chk_tunnel_now.sh new file mode 100644 index 0000000..60af8d9 --- /dev/null +++ b/_build/chk_tunnel_now.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# 看设备上隧道现在的配比 + 连接数(判断 relay-stability 有没有在跑对照) +set -u + +echo " ── 两个隧道单元的配置 ──" +for s in lanremote-tunnel lanremote-tunnel-h2; do + if [ -f "/etc/systemd/system/$s.service" ]; then + cfg=$(grep -oE -- '--protocol [a-z0-9]+|--ha-connections [0-9]+|--metrics [0-9.:]+' \ + "/etc/systemd/system/$s.service" | tr '\n' ' ') + printf " %-24s %s (%s)\n" "$s" "$cfg" "$(systemctl is-active $s 2>/dev/null)" + else + printf " %-24s (不存在)\n" "$s" + fi +done + +echo +echo " ── 实时连接数 ──" +for p in 20241 20242; do + n=$(curl -s --max-time 4 "http://127.0.0.1:$p/metrics" 2>/dev/null \ + | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}') + printf " 端口 %s -> %s 条\n" "$p" "${n:-读不到}" +done + +echo +echo " ── 公网自检 ──" +curl -s --max-time 20 -o /dev/null -w ' HTTP %{http_code} %{time_total}s\n' \ + -H 'User-Agent: LanRemote/1.8.2' https://relay.xxmxxs.top/v1/health + +echo +echo " ── relay-stability 的测量进程 ──" +pgrep -af 'rp_|longstab|bench' 2>/dev/null | head -5 | sed 's/^/ /' || echo " (没有在跑)" + +echo +echo " ── 它最近写的文件 ──" +ls -la --time-style=+%H:%M:%S /root/rp_* /root/*.tsv 2>/dev/null | tail -8 | sed 's/^/ /' || true diff --git a/_build/rp_ab_long.sh b/_build/rp_ab_long.sh new file mode 100644 index 0000000..d9400dd --- /dev/null +++ b/_build/rp_ab_long.sh @@ -0,0 +1,148 @@ +#!/bin/bash +# 长窗口配比对照用的测量脚本(支持 A/B 两组,各跑 >=40 分钟) +# +# 与 rp_long2h.sh 的区别: +# * 时长可指定(分钟) +# * **额外采样内存和 CPU**(Lead 问 h2 提到 4 条对 460MB 设备有无影响) +# * 输出文件名带标签,方便 A/B 分开比对 +# +# 用法: bash rp_ab_long.sh <标签> <分钟数> +set -u + +LABEL=${1:-A} +MINS=${2:-45} +BASE=https://relay.xxmxxs.top +UA='LanRemote/1.8.2' +DUR=$((MINS * 60)) +HEALTH_EVERY=15 +CONN_EVERY=60 +RES_EVERY=60 + +H=/root/ab_${LABEL}_health.tsv +C=/root/ab_${LABEL}_conns.tsv +R=/root/ab_${LABEL}_res.tsv +S=/root/ab_${LABEL}_summary.txt + +rm -f "$H" "$C" "$R" "$S" + +conns_of() { + curl -s --max-time 4 "http://127.0.0.1:$1/metrics" 2>/dev/null \ + | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}' +} + +M0=$(wc -l < /root/lanremote-relay/tunnel.log 2>/dev/null || echo 0) +H0=$(wc -l < /root/lanremote-relay/tunnel-h2.log 2>/dev/null || echo 0) + +T0=$(date +%s) +NEXT_CONN=$T0 +NEXT_RES=$T0 + +echo " 开始 $LABEL: $(date '+%H:%M:%S') 持续 ${MINS} 分钟" + +while true; do + NOW=$(date +%s) + [ $((NOW - T0)) -ge "$DUR" ] && break + + out=$(curl -s -o /dev/null -w '%{http_code} %{time_total}' --max-time 25 \ + -H "User-Agent: $UA" "$BASE/v1/health") + code=$(echo "$out" | awk '{print $1}') + ms=$(echo "$out" | awk '{printf "%d", $2*1000}') + echo "$(date '+%Y-%m-%d %H:%M:%S') $code $ms" >> "$H" + + if [ "$NOW" -ge "$NEXT_CONN" ]; then + a=$(conns_of 20241); b=$(conns_of 20242) + echo "$(date '+%Y-%m-%d %H:%M:%S') ${a:-X} ${b:-X}" >> "$C" + NEXT_CONN=$((NOW + CONN_EVERY)) + fi + + if [ "$NOW" -ge "$NEXT_RES" ]; then + # 两个 cloudflared 各占多少常驻内存 + CPU + qm=0; hm=0; qc=0; hc=0 + for p in $(pgrep cloudflared); do + proto=$(tr '\0' ' ' < /proc/$p/cmdline 2>/dev/null | grep -oE '\-\-protocol [a-z0-9]+' | awk '{print $2}') + rss=$(awk '/VmRSS/{print $2}' /proc/$p/status 2>/dev/null) + cpu=$(ps -o %cpu= -p $p 2>/dev/null | tr -d ' ') + if [ "$proto" = "quic" ]; then qm=${rss:-0}; qc=${cpu:-0}; + elif [ "$proto" = "http2" ]; then hm=${rss:-0}; hc=${cpu:-0}; fi + done + total=$(free -m | awk '/Mem:/{print $3}') + avail=$(free -m | awk '/Mem:/{print $7}') + load=$(awk '{print $1}' /proc/loadavg) + echo "$(date '+%Y-%m-%d %H:%M:%S') ${qm} ${hm} ${qc} ${hc} ${total} ${avail} ${load}" >> "$R" + NEXT_RES=$((NOW + RES_EVERY)) + fi + + sleep "$HEALTH_EVERY" +done + +M1=$(wc -l < /root/lanremote-relay/tunnel.log 2>/dev/null || echo 0) +H1=$(wc -l < /root/lanremote-relay/tunnel-h2.log 2>/dev/null || echo 0) + +{ + echo "════════════════════════════════════════════════════════" + echo " $LABEL 组 · ${MINS} 分钟 · $(date '+%Y-%m-%d %H:%M:%S')" + echo "════════════════════════════════════════════════════════" + echo + echo "── 配置 ──" + for svc in lanremote-tunnel lanremote-tunnel-h2; do + echo " $svc: $(systemctl cat $svc 2>/dev/null | grep -oE '\-\-protocol [a-z0-9]+|\-\-ha-connections [0-9]+' | tr '\n' ' ')" + done + echo + echo "── 公网可用率 ──" + n=$(grep -c . "$H" 2>/dev/null || echo 0) + ok=$(awk -F'\t' '$2=="200"' "$H" 2>/dev/null | wc -l) + echo " $ok/$n = $(awk -v a="$ok" -v b="$n" 'BEGIN{if(b>0)printf "%.2f", a*100/b; else print 0}')%" + echo " 状态码: $(awk -F'\t' '{print $2}' "$H" 2>/dev/null | sort | uniq -c | tr '\n' ' ')" + awk -F'\t' '$2=="200"{print $3}' "$H" 2>/dev/null | sort -n | awk ' + {a[NR]=$1} + END{if(NR==0){print " 无数据"; exit} + printf " 耗时: 最小 %d 中位 %d P90 %d P99 %d 最大 %d ms\n", + a[1], a[int(NR/2)+1], a[int(NR*0.9)+1], a[int(NR*0.99)+1], a[NR]}' + echo " 失败时刻:" + awk -F'\t' '$2!="200"{print " " $1 " 码=" $2 " " $3 "ms"}' "$H" 2>/dev/null + echo + echo "── 连接数 ──" + awk -F'\t' 'NR>1 && $2 ~ /^[0-9]+$/ && $3 ~ /^[0-9]+$/ { + t=$2+$3; cnt[t]++ + if (t<=3) low++ + if (tmax) max=t + } END { + printf " 最小 %d 最大 %d 平均 %.2f\n", min, max, 0 + printf " 总连接数<=3 的采样: %d 次(占 %.1f%%)\n", low+0, (low+0)*100/NR + }' "$C" 2>/dev/null + echo " 分布: $(awk -F'\t' 'NR>1 && $2 ~ /^[0-9]+$/ && $3 ~ /^[0-9]+$/ {print $2+$3}' "$C" | sort -n | uniq -c | tr '\n' ' ')" + echo + echo "── 内存 / CPU ──" + awk -F'\t' 'NR>1 { + if ($2+0>qm) qm=$2 + if ($3+0>hm) hm=$3 + if ($4+0>qc) qc=$4 + if ($5+0>hc) hc=$5 + if ($6+0>tm) tm=$6 + if (minav=="" || $7+0ld) ld=$8 + } END { + printf " QUIC 实例常驻内存峰值: %.1f MB\n", qm/1024 + printf " HTTP/2 实例常驻内存峰值: %.1f MB\n", hm/1024 + printf " 两者合计: %.1f MB\n", (qm+hm)/1024 + printf " CPU 峰值: quic %.1f%% http2 %.1f%%\n", qc, hc + printf " 设备已用内存峰值: %d MB 最少可用: %d MB\n", tm, minav + printf " 负载峰值: %.2f\n", ld + }' "$R" 2>/dev/null + echo + echo "── 隧道事件(本轮增量)──" + printf " QUIC 注册 %s 失败 %s 掉线 %s\n" \ + "$(tail -n +$((M0+1)) /root/lanremote-relay/tunnel.log 2>/dev/null | grep -c 'Registered tunnel connection')" \ + "$(tail -n +$((M0+1)) /root/lanremote-relay/tunnel.log 2>/dev/null | grep -c 'Failed to dial')" \ + "$(tail -n +$((M0+1)) /root/lanremote-relay/tunnel.log 2>/dev/null | grep -c 'Lost connection')" + printf " HTTP/2 注册 %s 失败 %s 掉线 %s\n" \ + "$(tail -n +$((H0+1)) /root/lanremote-relay/tunnel-h2.log 2>/dev/null | grep -c 'Registered tunnel connection')" \ + "$(tail -n +$((H0+1)) /root/lanremote-relay/tunnel-h2.log 2>/dev/null | grep -c 'Failed to dial')" \ + "$(tail -n +$((H0+1)) /root/lanremote-relay/tunnel-h2.log 2>/dev/null | grep -c 'Lost connection')" + echo + echo "── watchdog 处置(本轮)──" + tail -12 /root/lanremote-relay/watchdog.log 2>/dev/null | sed 's/^/ /' +} > "$S" 2>&1 + +cat "$S" diff --git a/_build/rp_collapse.py b/_build/rp_collapse.py new file mode 100644 index 0000000..78525e0 --- /dev/null +++ b/_build/rp_collapse.py @@ -0,0 +1,147 @@ +# -*- 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 <标签> +''' +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 <标签> ") + 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()) diff --git a/_build/rp_pc_long.tsv b/_build/rp_pc_long.tsv index fab240f..436bc3f 100644 --- a/_build/rp_pc_long.tsv +++ b/_build/rp_pc_long.tsv @@ -240,3 +240,72 @@ time code ms 2026-10-07 18:41:34 200 1914 2026-10-07 18:41:51 200 1286 2026-10-07 18:42:07 200 1029 +2026-10-07 18:42:23 200 1023 +2026-10-07 18:42:39 200 1373 +2026-10-07 18:42:55 200 1320 +2026-10-07 18:43:12 200 1275 +2026-10-07 18:43:28 200 1420 +2026-10-07 18:43:44 200 1468 +2026-10-07 18:44:01 200 1541 +2026-10-07 18:44:17 200 1353 +2026-10-07 18:44:33 200 1142 +2026-10-07 18:44:50 200 1330 +2026-10-07 18:45:06 200 1138 +2026-10-07 18:45:22 200 1141 +2026-10-07 18:45:38 200 1108 +2026-10-07 18:45:54 200 1130 +2026-10-07 18:46:10 200 1114 +2026-10-07 18:46:27 200 1127 +2026-10-07 18:46:43 200 1023 +2026-10-07 18:46:59 200 1316 +2026-10-07 18:47:15 200 991 +2026-10-07 18:47:31 200 1013 +2026-10-07 18:47:48 200 2106 +2026-10-07 18:48:04 200 1385 +2026-10-07 18:48:21 200 1168 +2026-10-07 18:48:37 200 1115 +2026-10-07 18:48:53 200 1156 +2026-10-07 18:49:09 200 1121 +2026-10-07 18:49:25 200 1440 +2026-10-07 18:49:42 200 1414 +2026-10-07 18:49:58 200 1015 +2026-10-07 18:50:14 200 1119 +2026-10-07 18:50:30 200 1108 +2026-10-07 18:50:46 200 1093 +2026-10-07 18:51:02 200 1287 +2026-10-07 18:51:19 200 1461 +2026-10-07 18:51:35 200 1104 +2026-10-07 18:51:51 200 1318 +2026-10-07 18:52:08 200 1276 +2026-10-07 18:52:24 200 1408 +2026-10-07 18:52:41 200 1446 +2026-10-07 18:52:57 200 1498 +2026-10-07 18:53:15 200 3390 +2026-10-07 18:53:33 200 2790 +2026-10-07 18:53:50 200 1457 +2026-10-07 18:54:06 200 1416 +2026-10-07 18:54:22 200 1175 +2026-10-07 18:54:38 200 1195 +2026-10-07 18:54:55 200 1429 +2026-10-07 18:55:11 200 1180 +2026-10-07 18:55:28 200 1526 +2026-10-07 18:55:44 200 1253 +2026-10-07 18:56:00 200 875 +2026-10-07 18:56:16 200 886 +2026-10-07 18:56:31 200 871 +2026-10-07 18:56:47 200 862 +2026-10-07 18:57:03 200 956 +2026-10-07 18:57:19 200 863 +2026-10-07 18:57:35 200 802 +2026-10-07 18:57:51 200 1144 +2026-10-07 18:58:07 200 1097 +2026-10-07 18:58:23 200 853 +2026-10-07 18:59:01 200 22552 +2026-10-07 18:59:17 200 1472 +2026-10-07 18:59:34 200 1476 +2026-10-07 18:59:50 200 1386 +2026-10-07 19:00:07 200 1598 +2026-10-07 19:00:28 200 6359 +2026-10-07 19:00:44 200 1199 +2026-10-07 19:01:00 200 1335 +2026-10-07 19:01:17 200 1437 diff --git a/_build/rp_wait_log.txt b/_build/rp_wait_log.txt index e69de29..a6c0b43 100644 Binary files a/_build/rp_wait_log.txt and b/_build/rp_wait_log.txt differ diff --git a/_build/rp_watchdog_v4.sh b/_build/rp_watchdog_v4.sh index 683431d..450f4d1 100644 --- a/_build/rp_watchdog_v4.sh +++ b/_build/rp_watchdog_v4.sh @@ -71,6 +71,8 @@ THRESHOLD=3 # 重启本地隧道解决不了,只会自伤。给足时间让它自己恢复。 # 连接真的掉下去了(降级),才用 THRESHOLD=3 快速重启。 THRESHOLD_HEALTHY=8 +# 连接"中等"(4~5 条)时的阈值:比很差时耐心一点,比健康时严一点。 +THRESHOLD_MID=5 STALE_MINUTES=10 # 日志多久没动静算卡死 # 两次重启至少隔这么久。 # 150 秒不是拍脑袋:隧道在差的 4G 上重建 5 条连接要 30~60 秒, @@ -105,15 +107,26 @@ write_state() { # 读某个 metrics 端口的连接数。 # 端口不通(实例死了)→ 输出 "X"(区分于 "0":0 是活着但没连接) +# +# ⚠ 必须重试。之前只用一次 `curl --max-time 4` 判定, +# 设备忙的时候(CPU 被别的进程占住、SSD/IO 抖动)curl 会超时, +# 于是被误判成"实例无响应"进而重启隧道 —— 一次误判就是 26 秒中断。 +# 历史重启原因里 8 次有 6 次写的是"实例无响应",其中一部分很可能是 +# 这种假阳性(真死的只有那次服务被我 stop 掉的)。 +# 给 3 次机会、每次间隔 1 秒,把瞬时超时排除掉。 conns_of() { - local port="$1" out - out=$(timeout 5 curl -s --max-time 4 "http://127.0.0.1:$port/metrics" 2>/dev/null \ - | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}') - if [ -z "$out" ]; then - echo "X" - else - echo "$out" - fi + local port="$1" out attempt + for attempt in 1 2 3; do + out=$(timeout 6 curl -s --max-time 5 \ + "http://127.0.0.1:$port/metrics" 2>/dev/null \ + | grep '^cloudflared_tunnel_ha_connections' | awk '{print $2}') + if [ -n "$out" ]; then + echo "$out" + return + fi + [ "$attempt" -lt 3 ] && sleep 1 + done + echo "X" } # 重启指定的一组服务(带最小间隔保护) @@ -295,15 +308,30 @@ if [ "$DEGRADED" = "1" ]; then fi if [ "$FAIL" -ge "$THRESHOLD" ] || [ "$ZERO" -ge "$THRESHOLD" ]; then - # 连接还健康的话,用更宽的阈值 —— 别把"抖一下"升级成 1 分钟中断 - if [ "$TOTAL" -ge "$BUILT_MIN" ] 2>/dev/null && [ "$ZERO" -lt "$THRESHOLD" ]; then - if [ "$FAIL" -lt "$THRESHOLD_HEALTHY" ]; then - log "公网连续 $FAIL 次不通,但连接健康(合计 $TOTAL 条)—— 再等等,不重启" - write_state "$ZERO" "$FAIL" "$BUILT" - exit 0 + # 按连接健康程度分三档,越健康越有耐心 —— + # 别把"抖一下"升级成 1 分钟中断。 + # + # TOTAL >= 6 健康 -> 要连续 8 次(约 2.5 分钟) + # 4 <= TOTAL < 6 中等 -> 连续 5 次(约 1.7 分钟) + # TOTAL < 4 很差 -> 保持 3 次(约 1 分钟) + # + # 中间的 5 次那一档是补上的:原来只要 TOTAL < 6 就直接用 3 次, + # 于是 4 条连接(虽然不满但还能扛)也会在一分钟内被重启。 + local_threshold=$THRESHOLD + if [ "$ZERO" -lt "$THRESHOLD" ]; then + if [ "$TOTAL" -ge "$BUILT_MIN" ] 2>/dev/null; then + local_threshold=$THRESHOLD_HEALTHY + elif [ "$TOTAL" -ge 4 ] 2>/dev/null; then + local_threshold=$THRESHOLD_MID fi fi + if [ "$FAIL" -lt "$local_threshold" ]; then + log "公网连续 $FAIL 次不通,连接 $TOTAL 条(阈值 $local_threshold)—— 再等等,不重启" + write_state "$ZERO" "$FAIL" "$BUILT" + exit 0 + fi + # 有实例只是"低连接"(不是完全没响应)时,只重启那些实例; # 全都低就重启全部。 if [ "${#LOW_SVCS[@]}" -gt 0 ] && [ "${#LOW_SVCS[@]}" -lt "${#ALL_SVCS[@]}" ]; then diff --git a/windows/data_bk_agentrepro/settings.json b/windows/data_bk_agentrepro/settings.json deleted file mode 100644 index 0544006..0000000 --- a/windows/data_bk_agentrepro/settings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "port": 8841, - "server_name": "DESKTOP-RN5I625", - "allow_run": true, - "allow_power": true, - "allow_screen": true, - "start_minimized": false, - "allow_new_pairing": true, - "image_timeout": 0, - "image_display": "window", - "image_custom_width": 0, - "image_custom_height": 0, - "image_window_title": "", - "tts_voice": "", - "tts_rate": 0, - "mp3_dir": "", - "file_roots": "", - "extra_apps": "", - "extra_commands": "", - "download_dir": "received", - "screen_max_width": 480, - "screen_quality": 72, - "screen_fps": 12, - "pairing_code": "097783", - "pairing_created": 1791366548.4543607, - "close_pair_after_first": true -} \ No newline at end of file