namespace MoShiro.Protocol; [Flags] public enum Scope { Line = 1, Target = 2, } public enum ColumnType { U8, U16, U32, U64, I32, I64, } public enum ColumnEnc { Plain, Delta, } /// 时间列的时钟归属:拨号方、监听方各自的单调时钟(微秒),或所在一侧的本地时钟。 public enum ColumnRole { None, TsDialer, TsListener, TsLocal, } public sealed record ColumnSpec(string Name, ColumnType Type, ColumnEnc Enc = ColumnEnc.Plain, bool Nullable = false, ColumnRole Role = ColumnRole.None); /// 数据面上的一侧:拨号方发起连接,监听方接受连接。 public enum Side { Dialer, Listener, } /// /// 一个记录流(一张表)。 是行的主导方:行由它发出的包或事件决定,另一侧的列按 (例如 seq)补充, /// 缺失就是缺失(回显缺 t2/t3 表示上行丢失,单向数据流缺 t_recv 表示丢失)。没有 的流只有主导方一侧的列,原样透传。 /// 每一列属于哪一侧由它的时钟决定(TsDialer 属于拨号方,TsListener 属于监听方),其他列属于主导方。 /// 流是例外:两侧各自记录完整的行(行里有一列标明是哪一侧),合并时直接拼接,不连接。 /// 这一条声明同时决定了两端各记录什么、监听方怎么合并,新增一种记录流不需要写合并代码。 /// public sealed record StreamSpec(string Name, IReadOnlyList Columns, Side Driver = Side.Dialer, string? Key = null, bool Union = false) { public Side OwnerOf(ColumnSpec column) => column.Role switch { ColumnRole.TsDialer => Side.Dialer, ColumnRole.TsListener => Side.Listener, _ => Driver, }; /// /// 该侧记录的列(不含连接键:键两侧都有,由行的编号表示)。 流两侧各自记录完整的行(例如各自套接字的内核采样), /// 所以每一侧拥有所有列。 /// public IReadOnlyList ColumnsOf(Side side) => [.. Columns.Where(c => c.Name != Key && (Union || OwnerOf(c) == side))]; } /// 积木组合(只描述,不含代码;节点按这个描述组装执行图)。 public sealed record BlockSet(string Transport, string Pacer, string Payload, string Behavior, IReadOnlyList Samplers); /// /// 采集方法声明:积木组合 + 参数 + 记录的列 + 带宽预算。 /// 这一条声明同时被拨号方和监听方(组装执行)、Backend(校验与目录)、Analysis(按列名读数据)使用。 /// public sealed record KindSpec( string Id, string Title, string Description, Scope Scopes, BlockSet Blocks, IReadOnlyList Params, IReadOnlyList Streams, Func BudgetBitsPerSecond) { public StreamSpec? FindStream(string name) { foreach (StreamSpec stream in Streams) { if (stream.Name == name) { return stream; } } return null; } } public static class Col { public static ColumnSpec U8(string name, bool nullable = false) => new(name, ColumnType.U8, ColumnEnc.Plain, nullable); public static ColumnSpec U16(string name, bool nullable = false) => new(name, ColumnType.U16, ColumnEnc.Plain, nullable); public static ColumnSpec U32(string name, bool nullable = false) => new(name, ColumnType.U32, ColumnEnc.Plain, nullable); public static ColumnSpec U64(string name, ColumnEnc enc = ColumnEnc.Plain) => new(name, ColumnType.U64, enc); /// 单调递增的序号:差分编码。 public static ColumnSpec Seq(string name) => new(name, ColumnType.U32, ColumnEnc.Delta); public static ColumnSpec TsDialer(string name, bool nullable = false) => new(name, ColumnType.I64, ColumnEnc.Delta, nullable, ColumnRole.TsDialer); public static ColumnSpec TsListener(string name, bool nullable = false) => new(name, ColumnType.I64, ColumnEnc.Delta, nullable, ColumnRole.TsListener); public static ColumnSpec TsLocal(string name) => new(name, ColumnType.I64, ColumnEnc.Delta, false, ColumnRole.TsLocal); } /// 带宽预算的估算函数(bit/s)。 public static class Bandwidth { private const int UdpOverheadBytes = 28; private const int TcpOverheadBytes = 52; public static long UdpEcho(long rateHz, long sizeBytes) => rateHz * ((sizeBytes + UdpOverheadBytes) * 8) * 2; public static long TcpEcho(long rateHz, long sizeBytes) => rateHz * ((sizeBytes + TcpOverheadBytes) * 8) * 2; }