docs: rewrite README and AGENTS.md etc..
This commit is contained in:
1 parent
cc64df3e70
commit
a9bf849697
22 files changed
+514
-665
No files matched your search
+30
-25
@@ -2,9 +2,9 @@
|
||||
|
||||
## 1. 原则
|
||||
|
||||
- **只采集原始数据**:逐包、逐消息、逐事件,TCP 另加周期性的内核采样;不计算任何统计,解释全部放在 [analysis.md](analysis.md)。
|
||||
- **一处声明,处处生效**:采集方法(Kind)由积木组合**声明**而成。Probe 和 Agent 共用同一套积木(`backend/shared/MoShiro.Measurement`)。
|
||||
- **密度即参数**:频率、包大小、采样间隔全部是任务参数,由 API 下发,不需要改代码。
|
||||
- 只采集原始数据:逐包、逐消息、逐事件,TCP 另加周期性的内核采样;不计算任何统计,解释全部放在 [analysis.md](analysis.md)。
|
||||
- 一处声明,处处生效:采集方法(Kind)由积木组合声明而成。Probe 和 Agent 共用同一套积木(`backend/shared/MoShiro.Measurement`)。
|
||||
- 密度即参数:频率、包大小、采样间隔全部是任务参数,由 API 下发,不需要改代码。
|
||||
|
||||
## 2. 任务模型
|
||||
|
||||
@@ -22,9 +22,9 @@ schedule continuous 持续运行,按时间切块
|
||||
once 按需测试,由 API 触发,也就是一次 Test
|
||||
```
|
||||
|
||||
- **线路任务模板**:`agent_id` 为空的 `line` 任务会自动应用到所有 `active` 的 Agent。新 Agent 注册后自动获得这些任务;也可以针对单个 Agent 覆盖参数或停用。
|
||||
- **测试方案(Plan)**:多个阶段的 `once` 任务组合,例如 `hy2_ladder` 是 `udp_train` 在 10、25、50、100 Mbps 下各运行 10 秒。Plan 定义在 `Protocol/Catalog/Plans`。
|
||||
- **期望任务集**:Backend 为每个 Agent 计算“有效任务集”(模板 + 覆盖 + 目标任务),带版本号,作为 Fleet 的期望状态(`fleet.state.sync`,文档类型 `TaskSet`)下发。Agent 的 `TaskReconciler` 与当前运行的任务对比后,启动、停止或修改参数;其中线路任务再经过控制通道同步给 Probe。**新增任务类型时,协议不需要任何改动。**
|
||||
- 线路任务模板:`agent_id` 为空的 `line` 任务会自动应用到所有 `active` 的 Agent。新 Agent 注册后自动获得这些任务;也可以针对单个 Agent 覆盖参数或停用。
|
||||
- 测试方案(Plan):多个阶段的 `once` 任务组合,例如 `hy2_ladder` 是 `udp_train` 在 10、25、50、100 Mbps 下各运行 10 秒。Plan 定义在 `backend/src/MoShiro.Backend.Application/Tests/Plans.cs`。
|
||||
- 期望任务集:Backend 为每个 Agent 计算“有效任务集”(模板 + 覆盖 + 目标任务),带版本号,作为 Fleet 的期望状态(`fleet.state.sync`,文档类型 `TaskSet`)下发。Agent 的 `TaskManager` 与当前运行的任务对比后,启动、停止或修改参数;其中线路任务再经过控制通道同步给 Probe。新增任务类型时协议不变。
|
||||
|
||||
## 3. Kind 目录:积木组合
|
||||
|
||||
@@ -37,9 +37,9 @@ schedule continuous 持续运行,按时间切块
|
||||
| Payload(负载形态) | `fixed`、`uniform`、`choice` | `size`、`min`/`max`、`sizes[]` |
|
||||
| Behavior(对端行为) | `echo`、`sink`、`source`、`connect`、`hold` | `direction`(`fwd`/`rev`/`bidir`)、`streams` |
|
||||
| Sampler(采样器,可挂到任意连接上) | `tcp_info`、`socket_counters` | `every_ms` |
|
||||
| Recorder(记录器) | 根据 Kind 声明的列**自动生成**的列式记录器 | — |
|
||||
| Recorder(记录器) | 根据 Kind 声明的列自动生成的列式记录器 | — |
|
||||
|
||||
**两种角色互相独立**:连接角色(Probe 永远是拨号方,适配 NAT)与数据角色(发送端、接收端、回显端,由 `direction` 决定)无关。每种积木只实现一次,Probe 和 Agent 共用。
|
||||
两种角色互相独立:连接角色(Probe 永远是拨号方,适配 NAT)与数据角色(发送端、接收端、回显端,由 `direction` 决定)无关。每种积木只实现一次,Probe 和 Agent 共用。
|
||||
|
||||
### 3.2 Kind 声明(`MoShiro.Protocol/Catalog/Kinds`)
|
||||
|
||||
@@ -64,10 +64,10 @@ public static readonly KindSpec TcpEcho = new(
|
||||
|
||||
这一条声明会被四个地方使用:
|
||||
|
||||
- **Probe/Agent**:组装积木执行任务;
|
||||
- **Recorder**:根据列定义记录数据;
|
||||
- **Backend**:校验参数和预算,并在 `/catalog/kinds` 中展示;
|
||||
- **Analysis**:按列名读取数据。
|
||||
- Probe/Agent:组装积木执行任务;
|
||||
- Recorder:根据列定义记录数据;
|
||||
- Backend:校验参数和预算,并在 `/catalog/kinds` 中展示;
|
||||
- Analysis:按列名读取数据。
|
||||
|
||||
### 3.3 内置 Kind
|
||||
|
||||
@@ -91,7 +91,7 @@ Probe 单调时钟 Agent 单调时钟
|
||||
t4 收到 <───────────── t3 回复
|
||||
```
|
||||
|
||||
RTT = `(t4 − t1) − (t3 − t2)`。缺少 `t2` 表示上行丢失;有 `t2`/`t3` 但缺少 `t4` 表示下行丢失。`t2 − t1` 和 `t4 − t3` 的**变化量**在去除偏移和漂移后,就是每个方向各自的时延抖动。
|
||||
RTT = `(t4 − t1) − (t3 − t2)`。缺少 `t2` 表示上行丢失;有 `t2`/`t3` 但缺少 `t4` 表示下行丢失。`t2 − t1` 和 `t4 − t3` 的变化量在去除偏移和漂移后,就是每个方向各自的时延抖动。
|
||||
|
||||
## 4. 原始块(自描述)
|
||||
|
||||
@@ -108,20 +108,20 @@ RawChunk
|
||||
└─ crc32c
|
||||
```
|
||||
|
||||
- **列定义写在块头中**:新增列或新增 Kind 时,**不需要升级 `container_version`**。只有容器格式本身变化时才升级。Backend 可以通用地解码任何块,不认识的列会被保留,但会被分析层忽略。
|
||||
- **切块规则**:持续任务每 **10 秒**切一块,或者记录数达到上限(默认 50 000)时提前切块。这样采集密度提高后,单块大小仍然有界,实时显示和持久化也共用同一条链路。
|
||||
- 列定义写在块头中:新增列或新增 Kind 时不需要升级 `container_version`。只有容器格式本身变化时才升级。Backend 可以通用地解码任何块,不认识的列会被保留,但会被分析层忽略。
|
||||
- 切块规则:持续任务每 10 秒切一块,或者记录数达到上限(默认 50 000)时提前切块。这样采集密度提高后,单块大小仍然有界,实时显示和持久化也共用同一条链路。
|
||||
- 按需测试按阶段切块,阶段内同样受记录数上限约束。
|
||||
- 编解码器位于 `MoShiro.Protocol/Raw`,Probe、Agent、Backend、离线工具使用同一份代码。
|
||||
|
||||
## 5. 列的归属与合并
|
||||
|
||||
> **两侧各自记录完整行的流**(`StreamSpec.Union`,例如 `tcp_info`):Probe 和 Agent 各自采样自己的套接字(Linux `TCP_INFO`,通过 `Socket.GetRawSocketOption`,不需要 P/Invoke),每一行带 `side` 标明属于哪一侧;合并时直接拼接,不连接,Agent 一侧的行每行只交出一次。Probe 的重传反映上行方向,Agent 的重传反映下行方向(代理场景里大流量走这个方向)。非 Linux 平台没有这类采样,对应的指标在窗口里就是没有数据。
|
||||
两侧各自记录完整行的流(`StreamSpec.Union`,例如 `tcp_info`):Probe 和 Agent 各自采样自己的套接字(Linux `TCP_INFO`,通过 `Socket.GetRawSocketOption`,不需要 P/Invoke),每一行带 `side` 标明属于哪一侧;合并时直接拼接,不连接,Agent 一侧的行每行只交出一次。Probe 的重传反映上行方向,Agent 的重传反映下行方向(代理场景里大流量走这个方向)。非 Linux 平台没有这类采样,对应的指标在窗口里就是没有数据。
|
||||
|
||||
`tcp_info` 的可空 `conn` 列标识当前记录器内的一条 TCP 连接,不包含地址。Backend 按 `(side, conn)` 分别求累计计数差,避免把重连前后或同时存活的套接字混为一个序列;时间不递增的样本被忽略,任一计数器回退时重新建立基线。旧数据没有 `conn`,仍按 `side` 分组,无法事后恢复丢失的连接归属。升级时先更新 Backend 和 Agent,再更新 Probe;旧 Agent 合并采样时会丢弃尚不认识的列。
|
||||
|
||||
TCP 回显的长度头和正文合为一次写入,避免在启用 `TCP_NODELAY` 时为长度头单独发送一个小段。`tcp.retrans` 仍按内核的重传段数与发送段数求比,不由应用消息数估算。
|
||||
|
||||
采集层只产生**原始列**(`seq`、`size`、`t1…t4`、`t_send`/`t_recv`、`outcome`……),不做任何统计,也不认识上层的方案或协议;丢包率、RTT、抖动由 Backend 的分析层从这些列推算。
|
||||
采集层只产生原始列(`seq`、`size`、`t1…t4`、`t_send`/`t_recv`、`outcome`……),不做任何统计,也不认识上层的方案或协议;丢包率、RTT、抖动由 Backend 的分析层从这些列推算。
|
||||
|
||||
每个记录流(`StreamSpec`)声明:
|
||||
|
||||
@@ -129,17 +129,17 @@ TCP 回显的长度头和正文合为一次写入,避免在启用 `TCP_NODELAY
|
||||
- `Key`:连接键(通常是 `seq`);
|
||||
- 每一列属于哪一侧由它的时钟决定:`TsProbe` 属于 Probe,`TsAgent` 属于 Agent,其余列属于主导方。
|
||||
|
||||
两端各自把**本侧的列**记在一张按键索引的环形行表里(`RowStore`),Probe 每 10 秒把已完成的行切成列块经控制通道交给**对应的 Agent**,Agent 按声明合并成完整的 RawChunk 后上传:
|
||||
两端各自把本侧的列记在一张按键索引的环形行表里(`RowStore`),Probe 每 10 秒把已完成的行切成列块经控制通道交给对应的 Agent,Agent 按声明合并成完整的 RawChunk 后上传:
|
||||
|
||||
- `Driver = Probe`(回显):行取自列块,Agent 的列按键补上,缺失就是缺失(上行丢失);
|
||||
- `Driver = Agent`(单向数据流):行取自 Agent 的记录(按列块头里的序号范围),Probe 的列按键补上,缺失就是缺失(丢失);
|
||||
- 没有 `Key`,或只有主导方一侧有列:原样透传。
|
||||
|
||||
新增一种记录流只需要写声明,不需要写合并代码。Probe 的列在规定时间内没有送达时,目前 Agent 带着已有的数据继续(不上传只有 Agent 一侧的 `partial` 块,见已知缺口)。
|
||||
新增一种记录流只需要写声明,不需要写合并代码。Probe 的列在规定时间内没有送达时,Agent 带着已有的数据继续,不上传只有 Agent 一侧的块。
|
||||
|
||||
## 6. 代码结构(`backend/shared/MoShiro.Measurement`)
|
||||
|
||||
同一种采集方法的两端是**一对角色**写在同一个文件里:拨号方(Probe 永远是拨号方,适配 NAT)和监听方(Agent)。两端共用包的构造和校验、时钟、行表和合并;Probe 和 Agent 只负责组装自己那一侧。
|
||||
同一种采集方法的两端是一对角色写在同一个文件里:拨号方(Probe 永远是拨号方,适配 NAT)和监听方(Agent)。两端共用包的构造和校验、时钟、行表和合并;Probe 和 Agent 只负责组装自己那一侧。
|
||||
|
||||
```text
|
||||
MoShiro.Measurement/ 依赖:MoShiro.Protocol(仅 BCL,零 NuGet)
|
||||
@@ -149,18 +149,23 @@ MoShiro.Measurement/ 依赖:MoShiro.Protocol(仅 BCL,
|
||||
│ ├─ MonoClock.cs 单调时钟(微秒)与 UTC 锚点
|
||||
│ └─ RunningBlock.cs 一个运行中的积木(取消令牌 + 后台任务)
|
||||
├─ Net/
|
||||
│ ├─ QuicDatagramTransport.cs、MeasurementDatagramRouter.cs MsQuic 数据面与逐流认证分发
|
||||
│ ├─ MsQuic*.cs、QuicDatagramTransport.cs、MeasurementDatagramRouter.cs MsQuic 数据面与逐流认证分发
|
||||
│ ├─ LinuxIcmpEcho.cs、TcpInfo.cs ICMP 回显;Linux `TCP_INFO` 采样
|
||||
│ └─ Tcp.cs TcpLink(拨号:TLS 按指纹固定 + 认证首帧)、TcpAcceptor(监听:TLS 自动识别 + 认证首帧)
|
||||
├─ Blocks/ 一个文件 = 一种采集方法的两个角色
|
||||
│ ├─ BlockBase.cs 积木的公共基类
|
||||
│ ├─ Echo.cs DatagramEchoDialer/Listener、TcpEchoDialer/Listener
|
||||
│ ├─ Session.cs ConnectDialer、HoldDialer、IcmpEchoSolo(没有对端)、IdleListener
|
||||
│ ├─ Train.cs TrainDialer(接收,记 t_recv)、TrainListener(按码率发包,记发送行)
|
||||
│ ├─ Bulk.cs `tcp_bulk` 的两个角色
|
||||
│ └─ BlockCatalog.cs Kind → (拨号方, 监听方);capabilities、本地预算的支持列表都从这里推出来
|
||||
├─ Merging/ FragmentMerger(声明驱动的按键合并)、ChunkFinalizer(盖章:块头、时钟锚点、起止时间)
|
||||
└─ Execution/ BudgetGuard(参数校验 + 本地硬上限,超出即拒绝)
|
||||
├─ Execution/ BudgetGuard(参数校验 + 本地硬上限,超出即拒绝)
|
||||
├─ Platform/ 主机汇总采样、IPv6 诊断、本机地址、平台识别
|
||||
└─ Update/ RestartUpdater(收到更新请求后有序退出)
|
||||
```
|
||||
|
||||
**边界**(由架构测试强制):采集层的任何代码里不能出现 REALITY、Hysteria2、Minecraft 这些业务概念,也没有测试方案——方案(例如 `hy2_ladder`、`reality_session`)只是若干采集原语阶段的编排,只存在于 Backend。监听方的准入策略(重放窗口、令牌桶、源地址学习)是安全边界,通过 `IAdmission` 由 Agent 实现,积木本身不含策略。
|
||||
边界(由架构测试强制):采集层的任何代码里不能出现 REALITY、Hysteria2、Minecraft 这些业务概念,也没有测试方案——方案(例如 `hy2_ladder`、`reality_session`)只是若干采集原语阶段的编排,只存在于 Backend。监听方的准入策略(重放窗口、令牌桶、源地址学习)是安全边界,通过 `IAdmission` 由 Agent 实现,积木本身不含策略。
|
||||
|
||||
## 7. 资源与安全上限
|
||||
|
||||
@@ -181,10 +186,10 @@ MoShiro.Measurement/ 依赖:MoShiro.Protocol(仅 BCL,
|
||||
| QUIC Datagram 数据(`udp_echo`、`udp_train`) | 7443/udp | Agent 固定监听;Probe 每次新连接轮换本地源端口 |
|
||||
| TCP/TLS 数据(`tcp_echo`、`tcp_bulk`、`connect`、`hold`) | 7444/tcp | 按首帧中的 `stream_id` 分发 |
|
||||
|
||||
- **默认不使用 443**,避免与 VPS 上已有的 REALITY 或 Hysteria2 服务冲突,也不需要特权端口权限。
|
||||
- 默认不使用 443,避免与 VPS 上已有的 REALITY 或 Hysteria2 服务冲突,也不需要特权端口权限。
|
||||
- 任务参数中的 `port` 只能取 Agent 本地配置 `listen` 列表中的端口。要用 443 测量,需要节点所有者在本机把 443 加入 `listen`(并授予 `CAP_NET_BIND_SERVICE`),然后在任务中指定 `port: 443`。
|
||||
- 每个原始块的头部都记录实际使用的端口。当测量端口与被推算服务的常用端口(443)不同时,分析结果附带 `port_differs_from_service` 置信说明,因为运营商的 QoS 可能按端口区别对待。
|
||||
|
||||
## 9. 边界
|
||||
|
||||
采集端只模拟流量特征(端口、包大小、速率、握手、方向),**不实现** REALITY 或 Hysteria2 协议本身,分析结果会附带置信说明。用户态看不到 TCP 的逐段收发,所以 TCP 能拿到的最原始数据是“逐消息时间戳 + 内核采样”;pcap/eBPF 不在首版范围内。
|
||||
采集端只模拟流量特征(端口、包大小、速率、握手、方向),不实现 REALITY 或 Hysteria2 协议本身,分析结果附带置信说明。用户态看不到 TCP 的逐段收发,TCP 的原始数据是逐消息时间戳加内核采样;pcap / eBPF 见[路线图](../roadmap.md)。
|
||||
Reference in new issue
Block a user