92 lines
3.2 KiB
C#
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));
|
|
}
|
|
}
|