Namespaces, assemblies, projects and directories, the solution, binaries, images, systemd units, config paths, environment variables, protocol method names, the media type and the raw chunk magic all move to MoShiro/moshiro. Existing data and deployments are not compatible and must be recreated. Also fix the architecture tests after the solution layout change and make the update tests independent of their order.
91 lines
3.2 KiB
C#
91 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,
|
|
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));
|
|
}
|
|
}
|