Files
MoShiro/backend/shared/MoShiro.Measurement/Merging/ChunkFinalizer.cs
T
justme 0eda0600cc
ci/woodpecker/pr/verify Pipeline was successful
ci/woodpecker/pr/web-build Pipeline was successful
shared: add independent IPv4 and IPv6 collection [CI]
2026-10-07 11:44:01 +08:00

92 lines
3.2 KiB
C#

using System.Text.Json;
using Fleet.Core;
using MoShiro.Protocol;
namespace MoShiro.Measurement;
/// <summary>Agent 单调时钟(微秒)与 UTC(毫秒)的对应关系:块头里的时钟锚点。</summary>
public readonly record struct ClockAnchor(long AgentMonoUs, long UtcMs);
/// <summary>
/// 给合并好的流盖章:生成完整的块头(任务/测试、参数快照、端口、时钟锚点、起止时间)。
/// 起止时间由数据本身的时间戳换算得出,而不是取收到列块的时刻,所以不受 Probe 交付延迟的影响。
/// </summary>
public static class ChunkFinalizer
{
public const int MaxDurationMs = 600_000;
public static RawChunk Finalize(
KindSpec spec,
RawChunk fragment,
IReadOnlyList<RawStream> streams,
NodeId agent,
ClockAnchor anchor,
IReadOnlyDictionary<string, JsonElement> resolvedParams,
int port,
ChunkId id)
{
RawChunkHeader source = fragment.Header;
long offsetUs = source.Clock.ProbeOffsetUs;
long min = long.MaxValue;
long max = long.MinValue;
foreach (RawStream stream in streams)
{
if (stream.Name == "tcp_info")
{
continue; // 这个流的时钟取决于 side 列,不参与起止时间的计算
}
foreach (RawColumn column in stream.Columns)
{
if (column.Role == ColumnRole.None)
{
continue;
}
for (int i = 0; i < stream.Count; i++)
{
if (!column.IsValid(i))
{
continue;
}
long utc = ToUtcMs(column.Values[i], column.Role, anchor, offsetUs);
min = Math.Min(min, utc);
max = Math.Max(max, utc);
}
}
}
long start = min == long.MaxValue ? anchor.UtcMs - source.DurationMs : min;
int duration = (int)Math.Clamp(Math.Max(source.DurationMs, min == long.MaxValue ? 0 : max - start), 1, MaxDurationMs);
var header = new RawChunkHeader
{
ChunkId = id,
Kind = spec.Id,
TaskId = source.TaskId,
TestId = source.TestId,
Phase = source.Phase,
AgentId = agent,
ProbeId = source.ProbeId,
IpVersion = source.IpVersion,
Target = null,
Params = new Dictionary<string, JsonElement>(resolvedParams),
Port = port,
Direction = source.Direction,
StartUtcMs = start,
DurationMs = duration,
Partial = false,
Clock = new ChunkClock { AgentMonoUs = anchor.AgentMonoUs, UtcMs = anchor.UtcMs, ProbeOffsetUs = offsetUs },
};
return new RawChunk(header, streams);
}
public static long ToUtcMs(long value, ColumnRole role, ClockAnchor anchor, long probeOffsetUs)
{
long agentUs = role == ColumnRole.TsProbe ? value + probeOffsetUs : value;
long delta = agentUs - anchor.AgentMonoUs;
return anchor.UtcMs + (delta >= 0 ? delta / 1000 : -((-delta + 999) / 1000));
}
}