清理误提交的测试数据目录 + 扩展 .gitignore

This commit is contained in:
xmz7891
2026-10-07 19:01:33 +08:00
parent e61bae80ca
commit 0ee91b68de
10 changed files with 636 additions and 41 deletions
+8
View File
@@ -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/
+89
View File
@@ -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% 可用")
+98
View File
@@ -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
+35
View File
@@ -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
+148
View File
@@ -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 (t<min || min=="") min=t
if (t>max) 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+0<minav) minav=$7
if ($8+0>ld) 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"
+147
View File
@@ -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 <标签> <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())
+69
View File
@@ -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
1 time code ms
240 2026-10-07 18:41:34 200 1914
241 2026-10-07 18:41:51 200 1286
242 2026-10-07 18:42:07 200 1029
243 2026-10-07 18:42:23 200 1023
244 2026-10-07 18:42:39 200 1373
245 2026-10-07 18:42:55 200 1320
246 2026-10-07 18:43:12 200 1275
247 2026-10-07 18:43:28 200 1420
248 2026-10-07 18:43:44 200 1468
249 2026-10-07 18:44:01 200 1541
250 2026-10-07 18:44:17 200 1353
251 2026-10-07 18:44:33 200 1142
252 2026-10-07 18:44:50 200 1330
253 2026-10-07 18:45:06 200 1138
254 2026-10-07 18:45:22 200 1141
255 2026-10-07 18:45:38 200 1108
256 2026-10-07 18:45:54 200 1130
257 2026-10-07 18:46:10 200 1114
258 2026-10-07 18:46:27 200 1127
259 2026-10-07 18:46:43 200 1023
260 2026-10-07 18:46:59 200 1316
261 2026-10-07 18:47:15 200 991
262 2026-10-07 18:47:31 200 1013
263 2026-10-07 18:47:48 200 2106
264 2026-10-07 18:48:04 200 1385
265 2026-10-07 18:48:21 200 1168
266 2026-10-07 18:48:37 200 1115
267 2026-10-07 18:48:53 200 1156
268 2026-10-07 18:49:09 200 1121
269 2026-10-07 18:49:25 200 1440
270 2026-10-07 18:49:42 200 1414
271 2026-10-07 18:49:58 200 1015
272 2026-10-07 18:50:14 200 1119
273 2026-10-07 18:50:30 200 1108
274 2026-10-07 18:50:46 200 1093
275 2026-10-07 18:51:02 200 1287
276 2026-10-07 18:51:19 200 1461
277 2026-10-07 18:51:35 200 1104
278 2026-10-07 18:51:51 200 1318
279 2026-10-07 18:52:08 200 1276
280 2026-10-07 18:52:24 200 1408
281 2026-10-07 18:52:41 200 1446
282 2026-10-07 18:52:57 200 1498
283 2026-10-07 18:53:15 200 3390
284 2026-10-07 18:53:33 200 2790
285 2026-10-07 18:53:50 200 1457
286 2026-10-07 18:54:06 200 1416
287 2026-10-07 18:54:22 200 1175
288 2026-10-07 18:54:38 200 1195
289 2026-10-07 18:54:55 200 1429
290 2026-10-07 18:55:11 200 1180
291 2026-10-07 18:55:28 200 1526
292 2026-10-07 18:55:44 200 1253
293 2026-10-07 18:56:00 200 875
294 2026-10-07 18:56:16 200 886
295 2026-10-07 18:56:31 200 871
296 2026-10-07 18:56:47 200 862
297 2026-10-07 18:57:03 200 956
298 2026-10-07 18:57:19 200 863
299 2026-10-07 18:57:35 200 802
300 2026-10-07 18:57:51 200 1144
301 2026-10-07 18:58:07 200 1097
302 2026-10-07 18:58:23 200 853
303 2026-10-07 18:59:01 200 22552
304 2026-10-07 18:59:17 200 1472
305 2026-10-07 18:59:34 200 1476
306 2026-10-07 18:59:50 200 1386
307 2026-10-07 19:00:07 200 1598
308 2026-10-07 19:00:28 200 6359
309 2026-10-07 19:00:44 200 1199
310 2026-10-07 19:01:00 200 1335
311 2026-10-07 19:01:17 200 1437
Binary file not shown.
+38 -10
View File
@@ -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 \
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 [ -z "$out" ]; then
echo "X"
else
if [ -n "$out" ]; then
echo "$out"
return
fi
[ "$attempt" -lt 3 ] && sleep 1
done
echo "X"
}
# 重启指定的一组服务(带最小间隔保护)
@@ -295,14 +308,29 @@ 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 条)—— 再等等,不重启"
# 按连接健康程度分三档,越健康越有耐心 ——
# 别把"抖一下"升级成 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
fi
# 有实例只是"低连接"(不是完全没响应)时,只重启那些实例;
# 全都低就重启全部。
-27
View File
@@ -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
}