using System.Text.Json; using Fleet.Core; using Fleet.Link; using MoShiro.Measurement; using MoShiro.Protocol; namespace MoShiro.Probe; /// /// 估计 Agent 单调时钟 − Probe 单调时钟:每次 moshiro.clock 往返取中点,在最近 8 次里取往返最短的一次 /// (往返越短,对称性假设的误差越小)。Probe 的列块头里带着这个偏移,Backend 据此对齐两端的时间戳。 /// public sealed class ClockEstimator { private const int Window = 8; private readonly object _gate = new(); private readonly Queue<(long RoundTripUs, long OffsetUs)> _samples = new(); public long OffsetUs { get; private set; } public bool HasEstimate { get; private set; } public void Add(long sendUs, long agentUs, long receiveUs) { lock (_gate) { _samples.Enqueue((receiveUs - sendUs, agentUs - ((sendUs + receiveUs) / 2))); while (_samples.Count > Window) { _samples.Dequeue(); } OffsetUs = _samples.MinBy(s => s.RoundTripUs).OffsetUs; HasEstimate = true; } } } /// /// 把 Probe 一侧的行交给这个 Agent:补全块头 → 编码 → 放进会话的发件箱(通道 17,至少一次,有界)。 /// 发件箱的内容在连接断开重连之后重发;发件箱满了丢弃最旧的。 /// public sealed class FragmentShipper : IFragmentSink { private readonly Outbox _outbox; private readonly NodeId _agent; private readonly ProbeId _probe; private readonly ClockEstimator _clock; private readonly TimeProvider _time; public FragmentShipper(Outbox outbox, NodeId agent, ProbeId probe, ClockEstimator clock, TimeProvider time) { _outbox = outbox; _agent = agent; _probe = probe; _clock = clock; _time = time; } public void Ship(string kind, FragmentScope scope, IReadOnlyList streams, IReadOnlyDictionary? parameters, long startUtcMs, int durationMs, int port) { var header = new RawChunkHeader { ChunkId = ChunkId.New(_time), Kind = kind, TaskId = scope.Task, TestId = scope.Test, Phase = scope.Phase, AgentId = _agent, ProbeId = _probe, Port = port, StartUtcMs = startUtcMs, DurationMs = Math.Max(1, durationMs), Clock = new ChunkClock { ProbeOffsetUs = _clock.OffsetUs }, }; if (parameters is not null) { foreach ((string key, JsonElement value) in parameters) { header.Params[key] = value; } } _outbox.Enqueue(MoShiroChannels.ColumnBlock, header.ChunkId.Value, RawChunkCodec.Encode(new RawChunk(header, streams))); } }