Files
justme 83e5120377 refactor!: rename the project from Monitor to MoShiro
Namespaces, assemblies, projects and directories, the solution, binaries,
images, systemd units, config paths, environment variables, protocol method
names, the media type and the raw chunk magic all move to MoShiro/moshiro.
Existing data and deployments are not compatible and must be recreated.
Also fix the architecture tests after the solution layout change and make
the update tests independent of their order.
2026-10-07 01:49:04 +08:00

10 KiB
Raw Permalink Blame History

分析层(Backend.Analysis)

1. 结构:算子 → 指标 → 模型

 原始块(自描述列)
      │ 解码为列视图(按列名读取)
      ▼
 ┌────────────┐   ┌──────────────────────┐   ┌───────────────────────┐
 │ 算子 Op     │──>│ 指标 MetricDef        │──>│ 模型 Model(评分)     │
 │ 可复用计算  │   │ 一行声明 = 一个指标    │   │ 指标的评分函数          │
 │ rtt/loss/…  │   │ 输入 + 算子 + 累加器   │   │ base/mc/hy2/reality    │
 └────────────┘   └──────────┬───────────┘   └──────────┬────────────┘
                             │ 累加器状态(可合并)         │ 查询时计算
                             ▼                            ▼
                  指标窗口(1m) → 5m → 1h           API:metrics{} + scores{}

三层都采用注册制,由代码中的注册表驱动;API 的 /catalog/metrics、/catalog/models 也直接由注册表生成。

2. 累加器(固定的少数几种)

所有指标的中间状态只能是下面几种累加器之一。合并规则只写一次,存储、降采样、按时间范围聚合都通用处理:

累加器 状态 合并 可导出的值
Counter u64 相加 值;除以时长得到速率(例如每分钟)
Ratio 分子、分母 分别相加 百分比
Sum / Min / Max f64 相加 / 取极值 值;平均值 = Sum / Counter
Histogram 固定对数桶(100 µs~30 s,每 2 倍 8 个桶,相对误差 ≤ 4.5%) 逐桶相加 p50 / p90 / p95 / p99、均值、最值
RunLength 连续段长度的直方图 逐桶相加 平均或最大连续丢包长度
Series 固定分辨率的时间序列(例如每秒一个点) 拼接 曲线(仅用于测试结果)

状态的二进制编码由 AccumulatorCodec 统一实现,存储层只把它当作不透明的字节处理。

3. 算子(可复用的计算,新增时才需要写代码)

算子 输入列 作用
rtt 回显流的 t1..t4 (t4−t1)−(t3−t2)
loss(dir) 回显流或包列 按方向判断丢失
owd_var(dir) t1,t2 或 t3,t4 去除偏移和线性漂移后的单向时延变化(抖动)
loss_runs 丢失标记 连续丢失段的长度
reorder / dup seq 乱序与重复
gap(expected, threshold) 到达时间 卡顿(到达间隔超过期望值 + 阈值)
kernel_delta(field) tcp_info 流 两次采样之间的差值(重传、确认字节数)
rate(dir) 字节数或包数 + 时间 每秒吞吐
phase_ms(a, b) connect 事件 阶段耗时(TCP 建连、TLS 握手)
outcome 事件 按错误类别计数(超时、RST、TLS alert)
change_point 逐秒序列 变点检测(QoS 开始限速的时刻)

算子都是纯函数,每个都有单元测试。

4. 指标定义(一行声明)

// Backend.Analysis/Metrics/LinkMetrics.cs
public static class LinkMetrics
{
    public static readonly MetricDef Rtt =
        Metric.Histogram("rtt", from: "*.echo", op: Ops.Rtt, unit: Unit.Ms,
                         exports: [Stat.P50, Stat.P95, Stat.P99, Stat.Mean]);

    public static readonly MetricDef LossUp =
        Metric.Ratio("loss.up", from: "udp_echo.msg", op: Ops.Loss(Dir.Up), unit: Unit.Pct);

    public static readonly MetricDef JitterDown =
        Metric.Histogram("jitter.down", from: "udp_echo.msg", op: Ops.OwdVar(Dir.Down), unit: Unit.Ms,
                         exports: [Stat.P95]);

    public static readonly MetricDef TcpStall =
        Metric.Counter("tcp.stall", from: "tcp_echo.msg", op: Ops.Gap(expectedFrom: "rate_hz", thresholdMs: 100),
                       unit: Unit.PerMin);

    public static readonly MetricDef TcpRetrans =
        Metric.Ratio("tcp.retrans", from: "tcp_echo.tcp_info", op: Ops.KernelDelta("retrans_total", over: "segs_out"),
                     unit: Unit.Pct);
}
  • from 写成 kind.stream,* 表示所有具有该 stream 的 Kind;某个窗口里没有对应的输入时,该指标在这个窗口就不存在。
  • TCP 长尾:均值和 p95 看不到偶发的重传尖峰(一次丢包后的 RTT 聚在 RTO 附近),所以 tcp.rtt 还导出 p999、max 和长尾比 tail(p99 / p50,从同一个直方图导出,窗口合并后仍然精确);tcp.slow 是慢回显的占比——阈值 max(1.8 × 基线, 基线 + max(15 ms, 0.35 × 基线)),基线是同一个 10 秒块里 RTT 的中位数(所以每个观测只依赖自己所在的块,窗口合并仍然精确;块里少于 20 个样本不产生观测)。把它和 tcp.retrans(内核计数,Agent 一侧反映下行)、tcp.stall 对照,可以区分"重传造成的尖峰"和"队头阻塞"。
  • 导出值的名字为 指标id.统计量,例如 rtt.p95、loss.up、tcp.stall.per_min。

新增一个统计 = 新增一行 MetricDef,如果需要新的算子,再加一个算子类。存储、降采样、API 输出、目录说明都不需要改。新指标上线后,会自动对保留期内(30 天)的原始数据回填(见第 7 节)。

5. 模型(服务体验评分)

模型是一组指标的评分函数,在查询时计算,不存储。改动评分公式后,立即对全部历史生效。

public sealed class McModel : IServiceModel
{
    public string Id => "mc";
    public int Version => 1;
    public IReadOnlyList<string> Needs => ["rtt.p95", "tcp.stall.per_min", "tcp.stall_max", "tcp.retrans"];
    public ModelScore Score(MetricView m) => /* 分段打分 + 加权,返回 0..100 与各维度得分和说明 */;
}
模型 主要指标 说明
base rtt.*、loss.up/down、jitter.up/down、loss.run_max 线路基础质量
mc tcp_echo 的 rtt.p95/p99、tcp.stall.per_min、tcp.stall_max、tcp.retrans TCP 上不会丢包,丢包表现为重传和卡顿(队头阻塞)。没有 tcp_echo 数据时,用 udp_echo 的丢包序列代入 TCP 恢复模型估算,并标记为 estimated
hy2 测试中 udp_train 各档的 loss、jitter,以及变点检测得到的 qos_onset;持续的 udp_echo 作为趋势参考 Brutal 固定码率:计算可持续码率(丢包 ≤ 5% 或 ≤ 10%)、有效吞吐、限速开始的时刻
reality connect 的成功率和阶段耗时、hold 的重置次数和存活时长、tcp_bulk 的吞吐(tcp.bulk.peak.max:最好的持续 1 秒窗口,评分用它;tcp.bulk.mbps.*:每个采样间隔)和重传。hy2_ladder 的阶梯里一步是一个阶段:Probe 会把一个阶段切成几块(一块最多 50000 条记录,100 Mbit/s 的 10 秒要切 3~4 块),评估时同一阶段的块先合并,再算丢包、抖动和实际吞吐;实际吞吐的时长取发送时间戳(Agent 时钟,按每块的时钟锚点换算成 UTC)的跨度,不用块头里的时长(Probe 切块时块头带的时长可能是整个阶段的)、tcp_echo 的 RTT 握手、稳定性、首字节时间估算、吞吐

6. 两种处理路径

chunk 入库 ─┬─> 流式路径:按 1s 桶计算累加器 → 热层环形缓冲(最近 1 小时)
            │               → 每满 10 s 写入 metric_10s → 推送 SSE(series / overview)
            └─> 持久路径:分钟窗口就绪(该分钟的块都已到达,或已过窗口结束时间 + 1 分钟)
                         → 计算完整窗口 → 写入 metric_1m → 标记降采样脏桶(5m、1h)
按需测试:所有阶段的块到达后 → 计算测试结果(包含 Series 曲线)→ 写入 test_results

两条路径调用同一份 MetricDef 和算子代码,所以实时值与历史值使用的是同一套口径。迟到的块(例如 Agent 重连后补传)会同时更新热层(如果仍在 1 小时内)、metric_10s,并重新进入持久路径。

指标分组(preset) 与指标一样采用注册制:Presets.Udp = [LinkMetrics.LossUp, …],由 /api/public/catalog/presets 下发。前端按分组展示,新增指标只需要把它加进分组。查询规则见 query.md。

7. 版本与重算

  • 每个 MetricDef 都有一个指纹(由声明内容和算子版本计算得出)。启动时与 metric_versions 表比对,发现新增或变化的指标时,就把保留期内的相关窗口加入重算队列,由后台以低优先级、限速的方式执行,进度可以通过 API 查询。
  • 超出原始数据保留期的历史保留旧口径,查询结果中会标注指标的版本。
  • 模型只在查询时计算,不参与重算。

8. 置信说明

每个模型的输出都附带:各指标的样本量、estimated 标记,以及局限说明,例如:

  • 模拟的只是流量特征,不是真实协议;
  • 测量端口与真实服务端口不同(port_differs_from_service,默认端口 7443/7444 时总会出现)。

9. 项目结构

backend/src/MoShiro.Backend.Analysis/     依赖:MoShiro.Protocol、Fleet.Core(纯计算,没有 I/O)
├─ Accumulators/  Counter Ratio Sum Min Max Histogram RunLength Series + AccumulatorCodec
├─ Ops/           Rtt Loss OwdVar LossRuns Reorder Gap KernelDelta Rate PhaseMs Outcome ChangePoint
├─ Metrics/       LinkMetrics TcpMetrics UdpTrainMetrics ConnectMetrics …(只包含声明)
├─ Models/        BaseModel McModel Hy2Model RealityModel
├─ Engine/        ChunkView(列视图) WindowEvaluator  TestEvaluator  MetricRegistry  ModelRegistry
└─ Clock/         ClockDriftFit(估计两端的时钟偏移和漂移)

分析库没有 I/O,可以直接用于离线 CLI tools/moshiro-analyze,对下载的原始块做实验。