using System.Text.Json; using System.Text.Json.Serialization; using Fleet.Core; namespace Monitor.Protocol; /// Agent 单调时钟与 UTC 的锚点,以及 Probe 时钟相对 Agent 时钟的偏移估计。 public sealed class ChunkClock { [JsonPropertyName("agent_mono_us")] public long AgentMonoUs { get; set; } [JsonPropertyName("utc_ms")] public long UtcMs { get; set; } /// Agent 时钟 − Probe 时钟(微秒)。 [JsonPropertyName("probe_offset_us")] public long ProbeOffsetUs { get; set; } /// Probe 相对 Agent 的时钟漂移(ppb,十亿分之一)。 [JsonPropertyName("probe_drift_ppb")] public long ProbeDriftPpb { get; set; } } public sealed class ColumnHeader { [JsonPropertyName("name")] public string Name { get; set; } = ""; /// u8 | u16 | u32 | u64 | i32 | i64 [JsonPropertyName("type")] public string Type { get; set; } = ""; /// plain | delta [JsonPropertyName("enc")] public string Enc { get; set; } = "plain"; [JsonPropertyName("nullable")] public bool Nullable { get; set; } /// ts_probe | ts_agent | ts_local,其他为空。 [JsonPropertyName("role")] public string? Role { get; set; } } public sealed class StreamHeader { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("count")] public int Count { get; set; } [JsonPropertyName("columns")] public List Columns { get; set; } = []; } /// 块头(JSON,自描述):列定义写在块里,新增列或新增 Kind 不需要升级 container_version。 public sealed class RawChunkHeader { [JsonPropertyName("chunk_id")] public ChunkId ChunkId { get; set; } [JsonPropertyName("container_version")] public int ContainerVersion { get; set; } = ProtocolVersion.Container; [JsonPropertyName("kind")] public string Kind { get; set; } = ""; [JsonPropertyName("task_id")] public TaskId? TaskId { get; set; } [JsonPropertyName("test_id")] public TestId? TestId { get; set; } [JsonPropertyName("phase")] public int? Phase { get; set; } [JsonPropertyName("agent_id")] public NodeId AgentId { get; set; } [JsonPropertyName("probe_id")] public ProbeId? ProbeId { get; set; } [JsonPropertyName("target")] public string? Target { get; set; } /// 参数的完整快照(包含默认值)。 [JsonPropertyName("params")] public Dictionary Params { get; set; } = []; [JsonPropertyName("port")] public int Port { get; set; } [JsonPropertyName("direction")] public string? Direction { get; set; } [JsonPropertyName("start_utc_ms")] public long StartUtcMs { get; set; } [JsonPropertyName("duration_ms")] public int DurationMs { get; set; } /// Probe 一侧的列没有在宽限期内送达:只有 Agent 一侧的数据。 [JsonPropertyName("partial")] public bool Partial { get; set; } [JsonPropertyName("clock")] public ChunkClock Clock { get; set; } = new(); [JsonPropertyName("streams")] public List Streams { get; set; } = []; /// /// "line:<probe_id>" 或 "target:<task_id>":存储和分析用的序列标识。 /// 一条线路是一对(Probe,Agent):同一个 Agent 上每个 Probe 的数据各是一个序列,互不混合。 /// [JsonIgnore] public string Series => TaskId is { } id && Target is not null ? "target:" + id.Value : LinePrefix + (ProbeId?.Value ?? ""); public const string LinePrefix = "line:"; } public sealed class RawColumn { public RawColumn(string name, ColumnType type, ColumnEnc enc, bool nullable, ColumnRole role, long[] values, bool[]? valid) { Name = name; Type = type; Enc = enc; Nullable = nullable; Role = role; Values = values; Valid = valid; } public string Name { get; } public ColumnType Type { get; } public ColumnEnc Enc { get; } public bool Nullable { get; } public ColumnRole Role { get; } /// 所有列统一用 long 存放(u64 按位保存)。缺失值对应的位置内容无意义,请先看 。 public long[] Values { get; } public bool[]? Valid { get; } public int Count => Values.Length; public bool IsValid(int row) => Valid is null || Valid[row]; } public sealed class RawStream { public RawStream(string name, int count, IReadOnlyList columns) { Name = name; Count = count; Columns = columns; } public string Name { get; } public int Count { get; } public IReadOnlyList Columns { get; } public RawColumn? Find(string name) { foreach (RawColumn column in Columns) { if (column.Name == name) { return column; } } return null; } } public sealed class RawChunk { public RawChunk(RawChunkHeader header, IReadOnlyList streams) { Header = header; Streams = streams; } public RawChunkHeader Header { get; } public IReadOnlyList Streams { get; } public RawStream? Find(string name) { foreach (RawStream stream in Streams) { if (stream.Name == name) { return stream; } } return null; } }