#!/usr/bin/env node /** * 覆盖网络 序⑥ · S3 链路抖动与 RTT 口径校验(⛔ 一次性脚本、不进产品路径)。 * * ## 为什么需要它 * 参数表把 `RELAY_RTT_W106 = 336 ms` 标成"实测",但它其实来自 `server.ts:810` 的 * **心跳往返**(一次 WS 往返 + 应用层处理 + 验签)⇒ **不一定等于网络 RTT**。 * 而 336 ms 目前是"跨云链路很差"的**唯一证据** ⇒ 若它是口径问题,后面所有 * "跨云不可玩"的结论都要重判。本脚本做**三方对比**:ICMP / TCP 握手 / relay 心跳。 * * 用法: * node overlay-jitter.cjs --icmp 106.54.21.172 --count 300 --interval 0.2 * node overlay-jitter.cjs --tcp 106.54.21.172:22 --tcp-n 30 * node overlay-jitter.cjs --icmp H --count 300 --tcp H:22 --tcp-n 30 # 两者一起跑 * * @module scripts/overlay-jitter */ 'use strict' const net = require('node:net') const { spawnSync } = require('node:child_process') function parseArgs(argv) { const out = {} for (let i = 0; i < argv.length; i++) { const a = argv[i] if (!a.startsWith('--')) continue const k = a.slice(2) const v = argv[i + 1] if (v === undefined || v.startsWith('--')) out[k] = true else { out[k] = isNaN(Number(v)) || v === '' ? v : Number(v) i++ } } return out } const args = parseArgs(process.argv.slice(2)) const log = (s) => process.stderr.write(`[jit] ${s}\n`) const out = (o) => process.stdout.write(`### RESULT ### ${JSON.stringify(o)}\n`) function pct(arr, p) { if (arr.length === 0) return 0 const s = [...arr].sort((a, b) => a - b) return Math.round(s[Math.min(s.length - 1, Math.floor(s.length * p))] * 100) / 100 } function icmp(host, count, interval) { const win = process.platform === 'win32' const argv = win ? ['-n', String(count), '-w', '1000', host] : ['-c', String(count), '-i', String(interval || 0.2), '-W', '1', host] const r = spawnSync(win ? 'ping' : 'ping', argv, { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 }) const text = `${r.stdout || ''}` const rtts = [] const re = win ? /time[=<]([\d.]+)\s*ms/gi : /time=([\d.]+)\s*ms/gi let m while ((m = re.exec(text)) !== null) rtts.push(Number(m[1])) if (rtts.length < 2) return { host, packets: rtts.length, error: 'no-rtt-samples', raw: text.slice(0, 300) } const deltas = [] for (let i = 1; i < rtts.length; i++) deltas.push(Math.abs(rtts[i] - rtts[i - 1])) const avg = rtts.reduce((a, b) => a + b, 0) / rtts.length const mdev = Math.sqrt(rtts.reduce((a, b) => a + (b - avg) ** 2, 0) / rtts.length) return { host, packets: rtts.length, sentExpected: count, lossPct: Math.round(((count - rtts.length) / count) * 10000) / 100, min: pct(rtts, 0), avg: Math.round(avg * 100) / 100, p50: pct(rtts, 0.5), max: pct(rtts, 1), mdev: Math.round(mdev * 100) / 100, p95AbsDelta: pct(deltas, 0.95), p50AbsDelta: pct(deltas, 0.5), note_win: win ? 'windows ping:无 -i 间隔参数,实际约 1 包/秒' : undefined, } } function tcpHandshake(host, port, n) { const rtts = [] let left = n return new Promise((resolve) => { const one = () => { const t = process.hrtime.bigint() const s = net.connect(port, host) const done = (ok) => { if (ok) rtts.push(Number(process.hrtime.bigint() - t) / 1e6) s.destroy() if (--left <= 0) { resolve({ target: `${host}:${port}`, samples: rtts.length, min: pct(rtts, 0), median: pct(rtts, 0.5), p95: pct(rtts, 0.95), max: pct(rtts, 1), }) return } setTimeout(one, 100) } s.setTimeout(5000) s.on('connect', () => done(true)) s.on('error', () => done(false)) s.on('timeout', () => done(false)) } one() }) } async function main() { const res = { host: require('node:os').hostname(), platform: process.platform } if (args.icmp) res.icmp = icmp(String(args.icmp), args.count || 300, args.interval) if (args.tcp) { const [h, p] = String(args.tcp).split(':') res.tcpHandshake = await tcpHandshake(h, Number(p || 22), args['tcp-n'] || 30) } out(res) process.exit(0) } main()