using System.Text.Json;
using System.Text.Json.Serialization;
using Fleet.Core;
namespace MoShiro.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("ip_version")]
[JsonPropertyOrder(1)]
public string IpVersion { get; set; } = IpVersions.V4;
[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>":存储和分析用的序列标识。
/// 同一个 Agent 上每个 Probe 的每种地址族各是一个序列,IPv6 追加 :ipv6,互不混合。
///
[JsonIgnore]
public string Series => TaskId is { } id && Target is not null ? "target:" + id.Value : IpVersions.LineSeries(ProbeId?.Value ?? "", IpVersion);
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;
}
}