Files
MoShiro/backend/probe/src/MoShiro.Probe/Link/Support.cs
T
justme 83e5120377 refactor!: rename the project from Monitor to MoShiro
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.
2026-10-07 01:49:04 +08:00

88 lines
2.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Text.Json;
using Fleet.Core;
using Fleet.Link;
using MoShiro.Measurement;
using MoShiro.Protocol;
namespace MoShiro.Probe;
/// <summary>
/// 估计 Agent 单调时钟 − Probe 单调时钟:每次 <c>moshiro.clock</c> 往返取中点,在最近 8 次里取往返最短的一次
/// (往返越短,对称性假设的误差越小)。Probe 的列块头里带着这个偏移,Backend 据此对齐两端的时间戳。
/// </summary>
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;
}
}
}
/// <summary>
/// 把 Probe 一侧的行交给这个 Agent:补全块头 → 编码 → 放进会话的发件箱(通道 17,至少一次,有界)。
/// 发件箱的内容在连接断开重连之后重发;发件箱满了丢弃最旧的。
/// </summary>
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<RawStream> streams, IReadOnlyDictionary<string, JsonElement>? 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)));
}
}