207 lines
9.9 KiB
C#
207 lines
9.9 KiB
C#
namespace MoShiro.Protocol;
|
||
|
||
/// <summary>
|
||
/// Kind 目录。新增一种采集方法 = 在这里加一个静态字段并放进 <see cref="All"/>;
|
||
/// 参数一律用 <see cref="P"/> 声明默认值和上下限。
|
||
/// </summary>
|
||
public static class Kinds
|
||
{
|
||
private static readonly string[] s_directions = ["fwd", "rev", "bidir"];
|
||
|
||
/// <summary>这个任务可能达到的最高探测频率(Hz):自适应时是 <c>rate_max_hz</c>,否则是固定的 <c>rate_hz</c>。预算、限速和准入都按它算。</summary>
|
||
public static long PeakRateHz(string kindId, ParamValues values) =>
|
||
(kindId is "quic_ping" or "tcp_ping") && values.GetBool("adaptive") ? Math.Max(values.GetInt("rate_max_hz"), values.GetInt("rate_min_hz")) : values.GetInt("rate_hz");
|
||
|
||
private static readonly StreamSpec s_echoMsg = new(
|
||
"msg",
|
||
[
|
||
Col.Seq("seq"), Col.U16("size"), Col.U16("w"),
|
||
Col.TsDialer("t1"), Col.TsListener("t2", nullable: true), Col.TsListener("t3", nullable: true), Col.TsDialer("t4", nullable: true),
|
||
],
|
||
Side.Dialer,
|
||
"seq");
|
||
|
||
private static readonly StreamSpec s_tcpInfo = new(
|
||
"tcp_info",
|
||
[
|
||
Col.TsLocal("ts"), Col.U8("side"), Col.U32("srtt_us"), Col.U32("rttvar_us"), Col.U32("cwnd"),
|
||
Col.U32("retrans_total"), Col.U32("segs_out"), Col.U64("bytes_acked"),
|
||
new ColumnSpec("conn", ColumnType.U64, ColumnEnc.Plain, true),
|
||
],
|
||
Union: true);
|
||
|
||
public static readonly KindSpec QuicPing = new(
|
||
"quic_ping",
|
||
"QUIC Datagram echo",
|
||
"Fixed-rate QUIC Datagram echo with four timestamps per packet.",
|
||
Scope.Line,
|
||
new BlockSet("quic_datagram", "fixed", "fixed", "echo", []),
|
||
[
|
||
P.Bool("adaptive", true, "Adapt the probe rate to the loss rate: low when the line is quiet, up to rate_max_hz when it loses packets, exponential back-off when it is unreachable."),
|
||
P.Int("rate_hz", 10, 1, 200, "Hz", "Fixed rate, used when adaptive is false."),
|
||
P.Int("rate_min_hz", 2, 1, 200, "Hz"),
|
||
P.Int("rate_max_hz", 10, 1, 200, "Hz", "Highest rate, and the reference grid for sample weights."),
|
||
P.Number("tau_pct", 0.2, 0.01, 5, "%", "Loss rate a quiet line has to be able to prove below, at 95% confidence."),
|
||
P.Number("rtt_tol_pct", 2, 0.1, 20, "%", "Half-width of the 95% confidence interval of the RTT p99 a quiet line has to keep, as a share of p99 (never below 0.3 ms)."),
|
||
P.Int("size", 64, 44, 1400, "B"),
|
||
P.Int("port", 7443, 1, 65535),
|
||
],
|
||
[s_echoMsg],
|
||
p => Bandwidth.UdpEcho(PeakRateHz("quic_ping", p), p.GetInt("size")));
|
||
|
||
public static readonly KindSpec QuicStream = new(
|
||
"quic_stream",
|
||
"QUIC Datagram train",
|
||
"Fixed-rate or laddered one-way QUIC Datagram stream (Agent to 拨号方) for loss and delay variation under load.",
|
||
Scope.Line,
|
||
new BlockSet("quic_datagram", "ladder", "fixed", "sink", []),
|
||
[
|
||
P.Number("mbps", 10, 0.1, 1000, "Mbit/s"),
|
||
P.Int("size", 1250, 200, 1400, "B"),
|
||
P.Int("duration_s", 10, 1, 120, "s"),
|
||
P.Enum("direction", "rev", s_directions),
|
||
P.Int("port", 7443, 1, 65535),
|
||
],
|
||
[
|
||
new StreamSpec("pkt", [Col.Seq("seq"), Col.U16("size"), Col.TsListener("t_send"), Col.TsDialer("t_recv", nullable: true)], Side.Listener, "seq"),
|
||
new StreamSpec("step", [Col.U16("index"), Col.U32("mbps"), Col.TsDialer("start_ts"), Col.TsDialer("end_ts")]),
|
||
],
|
||
p => (long)(p.GetNumber("mbps") * 1_000_000));
|
||
|
||
public static readonly KindSpec TcpPing = new(
|
||
"tcp_ping",
|
||
"TCP echo",
|
||
"Small TCP messages echoed with four timestamps, plus periodic kernel tcp_info samples.",
|
||
Scope.Line,
|
||
new BlockSet("tcp", "fixed", "uniform", "echo", ["tcp_info"]),
|
||
[
|
||
P.Bool("adaptive", true, "Adapt the echo rate: as low as the loss bound and the RTT percentile precision allow when the line is quiet, the maximum when it is not, back-off when it stalls."),
|
||
P.Int("rate_hz", 20, 1, 200, "Hz", "Fixed rate, used when adaptive is false."),
|
||
P.Int("rate_min_hz", 2, 1, 200, "Hz"),
|
||
P.Int("rate_max_hz", 20, 1, 200, "Hz", "Highest rate, and the reference grid for sample weights."),
|
||
P.Number("tau_pct", 0.2, 0.01, 5, "%", "Share of retransmitted segments a quiet line has to be able to prove below, at 95% confidence."),
|
||
P.Number("rtt_tol_pct", 2, 0.1, 20, "%", "Half-width of the 95% confidence interval of the RTT p99 a quiet line has to keep, as a share of p99 (never below 0.3 ms)."),
|
||
P.Int("min", 32, 16, 1400, "B"),
|
||
P.Int("max", 64, 16, 1400, "B"),
|
||
P.Int("tcp_info_ms", 1000, 10, 1000, "ms"),
|
||
P.Int("port", 7444, 1, 65535),
|
||
P.Bool("tls", false),
|
||
],
|
||
[s_echoMsg, s_tcpInfo],
|
||
p => Bandwidth.TcpEcho(PeakRateHz("tcp_ping", p), p.GetInt("max")));
|
||
|
||
public static readonly KindSpec TcpTransfer = new(
|
||
"tcp_transfer",
|
||
"TCP bulk",
|
||
"Bulk TCP transfer on several streams with periodic byte counters and tcp_info.",
|
||
Scope.Line,
|
||
new BlockSet("tcp", "fixed", "fixed", "source", ["tcp_info"]),
|
||
[
|
||
P.Int("streams", 4, 1, 16),
|
||
P.Int("duration_s", 4, 1, 120, "s"),
|
||
P.Enum("direction", "rev", s_directions),
|
||
P.Int("sample_ms", 100, 20, 1000, "ms"),
|
||
P.Int("port", 7444, 1, 65535),
|
||
P.Bool("tls", true),
|
||
],
|
||
[
|
||
new StreamSpec("bytes", [Col.TsDialer("ts"), Col.U8("conn"), Col.U64("bytes")]),
|
||
s_tcpInfo,
|
||
],
|
||
_ => 0);
|
||
|
||
public static readonly KindSpec TcpConnect = new(
|
||
"tcp_connect",
|
||
"Connect",
|
||
"Repeated TCP and TLS connection establishment with per-phase timings and outcome.",
|
||
Scope.Line | Scope.Target,
|
||
new BlockSet("tcp", "fixed", "fixed", "connect", []),
|
||
[
|
||
P.Int("per_minute", 6, 1, 60),
|
||
P.Bool("tls", true),
|
||
P.Int("timeout_ms", 5000, 500, 30000, "ms"),
|
||
P.Int("port", 7444, 1, 65535),
|
||
],
|
||
[
|
||
new StreamSpec(
|
||
"conn",
|
||
[Col.Seq("attempt"), Col.TsDialer("ts"), Col.U32("tcp_us", nullable: true), Col.U32("tls_us", nullable: true), Col.U8("outcome")]),
|
||
],
|
||
p => p.GetInt("per_minute") * 4000 / 60);
|
||
|
||
public static readonly KindSpec TlsHold = new(
|
||
"tls_hold",
|
||
"Hold",
|
||
"A long-lived TLS connection with periodic heartbeats; records resets and lifetime.",
|
||
Scope.Line,
|
||
new BlockSet("tls", "fixed", "fixed", "hold", []),
|
||
[
|
||
P.Int("heartbeat_s", 30, 5, 300, "s"),
|
||
P.Int("port", 7444, 1, 65535),
|
||
],
|
||
[
|
||
new StreamSpec("hold", [Col.TsDialer("ts"), Col.U8("event"), Col.U32("lifetime_ms")]),
|
||
],
|
||
p => 2000 / Math.Max(1, p.GetInt("heartbeat_s")) * 8);
|
||
|
||
public static readonly KindSpec HttpRequest = new(
|
||
"http_request",
|
||
"HTTP request",
|
||
"One Agent requests a URL over HTTP/1.1, HTTP/2 or HTTP/3 and records DNS, TCP, time to first byte, total time and status. Target is the URL.",
|
||
Scope.Target,
|
||
new BlockSet("http", "fixed", "fixed", "request", []),
|
||
[
|
||
P.Enum("version", "h2", ["h1", "h2", "h3"], "HTTP version; the request fails instead of falling back."),
|
||
P.Enum("method", "GET", ["GET", "HEAD"]),
|
||
P.Bool("adaptive", true, "Adapt the request rate to the failure rate: low while the target answers, up to rate_max_per_min when it fails, exponential back-off while it is unreachable."),
|
||
P.Int("per_minute", 6, 1, 120, "/min", "Fixed rate, used when adaptive is false."),
|
||
P.Int("rate_min_per_min", 1, 1, 120, "/min"),
|
||
P.Int("rate_max_per_min", 12, 1, 120, "/min", "Highest rate, and the reference grid for sample weights."),
|
||
P.Number("tau_pct", 5, 0.1, 50, "%", "Failure rate a quiet target has to be able to prove below, at 95% confidence."),
|
||
P.Int("timeout_ms", 10000, 500, 60000, "ms"),
|
||
P.Int("read_kb", 16, 0, 1024, "KiB", "Body bytes to read before the request counts as finished (0: headers only)."),
|
||
P.Int("ok_status", 0, 0, 599, null, "Status that counts as success; 0 means any status below 400."),
|
||
P.Bool("tls_verify", true),
|
||
],
|
||
[
|
||
new StreamSpec(
|
||
"req",
|
||
[
|
||
Col.Seq("attempt"), Col.TsDialer("ts"), Col.U16("w"),
|
||
Col.U32("dns_us", nullable: true), Col.U32("tcp_us", nullable: true), Col.U32("ttfb_us", nullable: true), Col.U32("total_us", nullable: true),
|
||
Col.U16("status", nullable: true), Col.U8("proto", nullable: true), Col.U8("outcome"),
|
||
]),
|
||
],
|
||
p => p.GetInt("rate_max_per_min") * (2000 + p.GetInt("read_kb") * 1024) * 8 / 60);
|
||
|
||
public static readonly KindSpec IcmpPing = new(
|
||
"icmp_ping",
|
||
"ICMP echo",
|
||
"拨号方 or Agent side ICMP echo to a target address.",
|
||
Scope.Target,
|
||
new BlockSet("icmp", "fixed", "fixed", "echo", []),
|
||
[
|
||
P.Int("rate_hz", 1, 1, 20, "Hz"),
|
||
P.Int("size", 64, 44, 1400, "B"),
|
||
],
|
||
[
|
||
new StreamSpec("msg", [Col.Seq("seq"), Col.U16("size"), Col.TsLocal("t1"), new ColumnSpec("t4", ColumnType.I64, ColumnEnc.Delta, true, ColumnRole.TsLocal)]),
|
||
],
|
||
p => Bandwidth.UdpEcho(p.GetInt("rate_hz"), p.GetInt("size")));
|
||
|
||
public static readonly IReadOnlyList<KindSpec> All = [QuicPing, QuicStream, TcpPing, TcpTransfer, TcpConnect, TlsHold, HttpRequest, IcmpPing];
|
||
|
||
public static KindSpec? Find(string id)
|
||
{
|
||
foreach (KindSpec kind in All)
|
||
{
|
||
if (kind.Id == id)
|
||
{
|
||
return kind;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
}
|