23bf3077c8
记录两处上下行数字口径差异:连接页来自内核 Clash API 全局吞吐含直连, 统计页来自节点 v2ray_api per-user 计数仅代理;差额=直连,设计使然。 含对账口径警示。登记进 docs/index.html 知识库分类。决策:保持现状不改连接页口径。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
117 lines
6.8 KiB
HTML
117 lines
6.8 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Pangolin 流量记账口径 · 连接页实时 vs 统计页累计</title>
|
||
<style>
|
||
:root{
|
||
--bg:#0f1117; --panel:#171a22; --panel2:#1d2129; --fg:#e6e8ee; --fg2:#a8afbd;
|
||
--accent:#e0884f; --accent2:#5fb0c9; --ok:#5ec27a; --bad:#e06a6a; --warn:#e0b84f;
|
||
--border:#272c36; --mono:"SF Mono",ui-monospace,Menlo,Consolas,monospace;
|
||
--sans:-apple-system,"PingFang SC","Helvetica Neue",Arial,sans-serif;
|
||
}
|
||
*{box-sizing:border-box}
|
||
body{margin:0;background:var(--bg);color:var(--fg);font-family:var(--sans);line-height:1.7;font-size:15px}
|
||
.wrap{max-width:920px;margin:0 auto;padding:48px 24px 96px}
|
||
h1{font-size:30px;line-height:1.3;margin:0 0 8px;letter-spacing:-.01em}
|
||
.sub{color:var(--fg2);font-size:15px;margin:0 0 28px}
|
||
h2{font-size:21px;margin:44px 0 14px;padding-bottom:8px;border-bottom:1px solid var(--border)}
|
||
h3{font-size:16.5px;margin:24px 0 8px;color:var(--accent)}
|
||
p{margin:10px 0}
|
||
code{font-family:var(--mono);font-size:.88em;background:var(--panel2);padding:1px 6px;border-radius:5px;color:#f0d9c4}
|
||
a{color:var(--accent2);text-decoration:none}
|
||
a:hover{text-decoration:underline}
|
||
ul{margin:8px 0;padding-left:22px}
|
||
li{margin:5px 0}
|
||
.lead{background:linear-gradient(180deg,rgba(224,136,79,.10),transparent);border:1px solid var(--border);border-radius:12px;padding:18px 20px;margin:0 0 8px}
|
||
.small{color:var(--fg2);font-size:13px}
|
||
.note{color:var(--fg2);font-size:13px;border-left:3px solid var(--border);padding-left:12px;margin:10px 0}
|
||
table{width:100%;border-collapse:collapse;margin:14px 0;font-size:13.5px}
|
||
th,td{border:1px solid var(--border);padding:8px 10px;text-align:left;vertical-align:top}
|
||
th{background:var(--panel2);color:var(--fg);font-weight:600}
|
||
td{color:var(--fg2)}
|
||
td b,td strong{color:var(--fg)}
|
||
.tag{display:inline-block;font-size:11px;font-weight:700;padding:1px 8px;border-radius:999px;white-space:nowrap}
|
||
.t-ok{background:rgba(94,194,122,.16);color:var(--ok)}
|
||
.t-warn{background:rgba(224,184,79,.16);color:var(--warn)}
|
||
.t-bad{background:rgba(224,106,106,.16);color:var(--bad)}
|
||
.yes{color:var(--ok);font-weight:700}
|
||
.no{color:var(--bad);font-weight:700}
|
||
.xref{display:inline-block;margin-top:6px;font-size:13px}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
|
||
<h1>流量记账口径 · 连接页实时 vs 统计页累计</h1>
|
||
<p class="sub">两个上下行数字来自不同源、统计的是不同范围——连接页含直连,统计页只算代理</p>
|
||
|
||
<div class="lead">
|
||
<strong>一句话:</strong>客户端有两处"上传/下载"数字,<b>口径不同、天然对不上</b>。
|
||
<b>连接页实时速率</b>来自本地内核的全局吞吐,<b>含直连</b>;<b>统计页累计用量</b>来自服务端按用户的节点计数,<b>只含真正经过节点的代理流量</b>。两者差额 = 直连流量(国内站 / 分流 direct / LAN)。这是<b>设计使然</b>,非 bug:计费/配额按代理流量算。
|
||
</div>
|
||
|
||
<h2>两个数据源</h2>
|
||
<table>
|
||
<tr><th>维度</th><th>连接页 · 实时上下行</th><th>统计页 · 累计用量</th></tr>
|
||
<tr>
|
||
<td><b>取数源</b></td>
|
||
<td>客户端<b>内核 Clash API</b><br><code>GET /traffic</code>(SSE 推 <code>{up,down}</code> bytes/s)+ <code>GET /connections</code>(<code>downloadTotal/uploadTotal</code> 差分算瞬时速率)</td>
|
||
<td><b>服务端</b> <code>GET /v1/usage</code> ← 节点 sing-box <b>v2ray_api StatsService</b> 的 per-user 计数器</td>
|
||
</tr>
|
||
<tr>
|
||
<td><b>计数粒度</b></td>
|
||
<td>sing-box <b>引擎全局总量</b>(不分出站,所有连接合计)</td>
|
||
<td>每用户独立计数器 <code>user>>>{dp_uuid}>>>traffic>>>uplink|downlink</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td><b>测量位置</b></td>
|
||
<td>客户端本机(TUN 入口处的总吞吐)</td>
|
||
<td>节点上(只有流量真正到达节点才被计)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><b>含直连流量?</b></td>
|
||
<td class="yes">含 ✅</td>
|
||
<td class="no">不含 ❌</td>
|
||
</tr>
|
||
<tr>
|
||
<td><b>语义</b></td>
|
||
<td>"这台设备此刻的总网速"(代理 + 直连)</td>
|
||
<td>"通过节点代理出去、计费口径的用量"</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<h2>为什么统计页不含直连</h2>
|
||
<p>全局 TUN 模式下,客户端 sing-box 按路由规则把流量分成两类:</p>
|
||
<ul>
|
||
<li><b>代理(proxy 出站)</b>:经 REALITY 数据口送到节点 → 节点 sing-box 处理 → 节点的 v2ray_api per-user 计数器<b>能看到、会计数</b>。</li>
|
||
<li><b>直连(direct 出站)</b>:走本地物理网卡直接出网(国内站 split-CN、LAN、被判 direct 的域名)→ <b>根本不碰节点</b> → 节点计数器无从得知。</li>
|
||
</ul>
|
||
<p>所以服务端记账(统计页)<b>物理上只能看到代理流量</b>。直连那部分对节点是不可见的。</p>
|
||
|
||
<h2>结论与含义</h2>
|
||
<ul>
|
||
<li>恒有 <b>连接页实时(含直连)≥ 统计页累计(仅代理)</b>。</li>
|
||
<li>对国内用户尤其明显:刷国内站 / 看视频走直连时,连接页速率飙升,统计页却不动(没走节点、不计费)。</li>
|
||
<li><b>合理且符合预期</b>:配额 / 计费就该按代理流量算,直连不该计费。统计页口径正确。</li>
|
||
<li><b>已知 UX 副作用</b>:连接页实时速率含直连,可能让用户误解"下了这么多怎么没扣量"。</li>
|
||
</ul>
|
||
|
||
<h2>现状决策(2026-06-29)</h2>
|
||
<p><b>保持现状,不改连接页口径。</b>连接页继续显示"总吞吐"语义(含直连)——与多数客户端一致;统计页继续"仅代理"。两者口径差异由本文档说明,不视为缺陷。</p>
|
||
<div class="note">
|
||
若未来要让两者口径一致:连接页可改为<b>仅累加走 <code>proxy</code> 出站的连接字节</b>——<code>/connections</code> 每条连接带 <code>chains</code>/outbound 信息可分类求和;全局 SSE <code>/traffic</code> 给不了出站维度,需改用 <code>/connections</code> 轮询差分。当前未做。
|
||
</div>
|
||
|
||
<h2>相关代码 / 文档</h2>
|
||
<ul>
|
||
<li>连接页速率:<code>client/lib/screens/connect_page.dart</code>(<code>stats.downloadSpeed/uploadSpeed</code>)← <code>client/lib/bridge/kernel_process.dart</code>(<code>/traffic</code>、<code>/connections</code>)</li>
|
||
<li>统计页用量:<code>client/lib/screens/stats_page.dart</code> ← <code>GET /v1/usage</code>(<code>server/internal/usage/*</code>)← agent 读 v2ray_api(<code>server/internal/agentd/render.go</code> · <code>parseUserStat</code>)</li>
|
||
<li>对账验收:<a href="feature-test-coverage-checklist.html">功能 × 测试覆盖清单</a> §A.2「记账对账」——对账时须按本文口径,<b>客户端统计页 vs 节点计数对的是"代理流量",不能拿连接页实时(含直连)去对</b>。</li>
|
||
</ul>
|
||
|
||
</div>
|
||
</body>
|
||
</html>
|