215 lines
5.7 KiB
C#
215 lines
5.7 KiB
C#
using System.Text.Json;
|
||
using System.Text.Json.Serialization;
|
||
using Fleet.Core;
|
||
|
||
namespace MoShiro.Protocol;
|
||
|
||
/// <summary>Agent 单调时钟与 UTC 的锚点,以及 Probe 时钟相对 Agent 时钟的偏移估计。</summary>
|
||
public sealed class ChunkClock
|
||
{
|
||
[JsonPropertyName("agent_mono_us")]
|
||
public long AgentMonoUs { get; set; }
|
||
|
||
[JsonPropertyName("utc_ms")]
|
||
public long UtcMs { get; set; }
|
||
|
||
/// <summary>Agent 时钟 − Probe 时钟(微秒)。</summary>
|
||
[JsonPropertyName("probe_offset_us")]
|
||
public long ProbeOffsetUs { get; set; }
|
||
|
||
/// <summary>Probe 相对 Agent 的时钟漂移(ppb,十亿分之一)。</summary>
|
||
[JsonPropertyName("probe_drift_ppb")]
|
||
public long ProbeDriftPpb { get; set; }
|
||
}
|
||
|
||
public sealed class ColumnHeader
|
||
{
|
||
[JsonPropertyName("name")]
|
||
public string Name { get; set; } = "";
|
||
|
||
/// <summary>u8 | u16 | u32 | u64 | i32 | i64</summary>
|
||
[JsonPropertyName("type")]
|
||
public string Type { get; set; } = "";
|
||
|
||
/// <summary>plain | delta</summary>
|
||
[JsonPropertyName("enc")]
|
||
public string Enc { get; set; } = "plain";
|
||
|
||
[JsonPropertyName("nullable")]
|
||
public bool Nullable { get; set; }
|
||
|
||
/// <summary>ts_probe | ts_agent | ts_local,其他为空。</summary>
|
||
[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<ColumnHeader> Columns { get; set; } = [];
|
||
}
|
||
|
||
/// <summary>块头(JSON,自描述):列定义写在块里,新增列或新增 Kind 不需要升级 container_version。</summary>
|
||
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; }
|
||
|
||
/// <summary>参数的完整快照(包含默认值)。</summary>
|
||
[JsonPropertyName("params")]
|
||
public Dictionary<string, JsonElement> 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; }
|
||
|
||
/// <summary>Probe 一侧的列没有在宽限期内送达:只有 Agent 一侧的数据。</summary>
|
||
[JsonPropertyName("partial")]
|
||
public bool Partial { get; set; }
|
||
|
||
[JsonPropertyName("clock")]
|
||
public ChunkClock Clock { get; set; } = new();
|
||
|
||
[JsonPropertyName("streams")]
|
||
public List<StreamHeader> Streams { get; set; } = [];
|
||
|
||
/// <summary>
|
||
/// "line:<probe_id>" 或 "target:<task_id>":存储和分析用的序列标识。
|
||
/// 同一个 Agent 上每个 Probe 的每种地址族各是一个序列,IPv6 追加 :ipv6,互不混合。
|
||
/// </summary>
|
||
[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; }
|
||
|
||
/// <summary>所有列统一用 long 存放(u64 按位保存)。缺失值对应的位置内容无意义,请先看 <see cref="IsValid"/>。</summary>
|
||
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<RawColumn> columns)
|
||
{
|
||
Name = name;
|
||
Count = count;
|
||
Columns = columns;
|
||
}
|
||
|
||
public string Name { get; }
|
||
|
||
public int Count { get; }
|
||
|
||
public IReadOnlyList<RawColumn> 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<RawStream> streams)
|
||
{
|
||
Header = header;
|
||
Streams = streams;
|
||
}
|
||
|
||
public RawChunkHeader Header { get; }
|
||
|
||
public IReadOnlyList<RawStream> Streams { get; }
|
||
|
||
public RawStream? Find(string name)
|
||
{
|
||
foreach (RawStream stream in Streams)
|
||
{
|
||
if (stream.Name == name)
|
||
{
|
||
return stream;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
}
|