Files
pangolin/docs/traffic-accounting-scopes.html
T
wangjia 23bf3077c8 docs(kb): 流量记账口径知识库 — 连接页实时(含直连) vs 统计页累计(仅代理)
记录两处上下行数字口径差异:连接页来自内核 Clash API 全局吞吐含直连,
统计页来自节点 v2ray_api per-user 计数仅代理;差额=直连,设计使然。
含对账口径警示。登记进 docs/index.html 知识库分类。决策:保持现状不改连接页口径。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 21:04:50 +08:00

117 lines
6.8 KiB
HTML
Raw 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.
<!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&gt;&gt;&gt;{dp_uuid}&gt;&gt;&gt;traffic&gt;&gt;&gt;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>