using System.Net; using Fleet.Core; using MoShiro.Protocol; namespace MoShiro.Measurement; /// 积木的公共部分:环境、参数、把行切成列块交出去。 public abstract class BlockBase : IBlock { protected BlockBase(BlockEnv env, BlockSpec spec) { Env = env; Spec = spec; } public virtual long Produced => 0; protected BlockEnv Env { get; } protected BlockSpec Spec { get; } public abstract Task RunAsync(CancellationToken ct); public virtual int Flush(FlushMode mode, long peerProduced = 0) => 0; public virtual RowStore? Records(string streamName) => null; protected IPEndPoint PeerEndpoint(int port) => new(Env.PeerAddress!, port); /// 交出一个流的行;起点和时长取自时间列 的首尾(本侧的单调时钟,微秒)。 protected void Ship(RawStream stream, string timeColumn, IReadOnlyList? extra = null, IReadOnlyDictionary? parameters = null) { RawColumn? time = stream.Find(timeColumn); long first = stream.Count > 0 && time is not null ? time.Values[0] : Env.Clock.NowUs(); long last = stream.Count > 0 && time is not null ? time.Values[stream.Count - 1] : first; List streams = [stream]; if (extra is not null) { streams.AddRange(extra); } Env.Fragments!.Ship(Spec.KindId, Spec.Scope, streams, parameters ?? Spec.Params.Values, Env.Clock.UtcMsAt(first), (int)((last - first) / 1000), Spec.Port); } /// /// 按自适应的节奏发包:下一个包的时刻按绝对时间排(不累积误差),每秒评估一次状态,状态变化记进日志(供审计)。 /// 落后了就从现在重新排,不补发。 /// protected async Task PaceAsync(AdaptiveRate rate, Func sendOne, CancellationToken ct) { long due = Env.Clock.NowUs(); long lastEvaluation = due; while (!ct.IsCancellationRequested) { due += (long)(rate.NextDelay().TotalMilliseconds * 1000); long wait = due - Env.Clock.NowUs(); if (wait > 0) { await Task.Delay(TimeSpan.FromMicroseconds(wait), Env.Time, ct); } else { due = Env.Clock.NowUs(); } await sendOne(); long now = Env.Clock.NowUs(); if (now - lastEvaluation >= 1_000_000 || rate.State == ProbeState.Dead) { lastEvaluation = now; if (rate.Evaluate(now) is { } change) { LogTransition(change); } } } } /// 读这个任务的自适应频率参数(quic_ping 和 tcp_ping 共用)。 protected AdaptiveRate? CreateAdaptiveRate() { if (!Spec.Bool("adaptive")) { return null; } double max = Math.Max(Spec.Int("rate_max_hz"), Spec.Int("rate_min_hz")); ReportStart(max); return new AdaptiveRate(new AdaptiveRateOptions { MinHz = Spec.Int("rate_min_hz"), MaxHz = max, TauPct = Spec.Number("tau_pct"), RttTolPct = Spec.Number("rtt_tol_pct"), Prior = Env.LossPrior, }); } /// /// 自适应开始:记一条「start → watch」(从最高频率起步)。审计据此知道这个任务从什么时候起在自适应, /// 没有这条之前的时段是「没有记录」,不是某个状态。 /// protected void ReportStart(double hz) => Env.OnRateChange?.Invoke(new RateChange { AtMs = Env.Time.GetUtcNow().ToUnixTimeMilliseconds(), Task = Spec.Scope.Task?.Value, Kind = Spec.KindId, From = "start", To = "watch", Hz = Math.Round(hz, 3), Reason = "start", }); private void LogTransition(ProbeTransition change) { Env.OnRateChange?.Invoke(new RateChange { AtMs = Env.Time.GetUtcNow().ToUnixTimeMilliseconds(), Task = Spec.Scope.Task?.Value, Kind = Spec.KindId, From = change.From.ToString().ToLowerInvariant(), To = change.To.ToString().ToLowerInvariant(), Hz = Math.Round(change.Hz, 3), Reason = change.Reason, LossPct = Math.Round(change.LossPct, 4), Sent = change.Sent, Lost = change.Lost, }); var fields = new (string, object?)[] { ("stream", Spec.StreamId), ("kind", Spec.KindId), ("from", change.From.ToString().ToLowerInvariant()), ("to", change.To.ToString().ToLowerInvariant()), ("hz", Math.Round(change.Hz, 1)), ("reason", change.Reason), ("loss_pct", Math.Round(change.LossPct, 3)), ("sent", change.Sent), ("lost", change.Lost), }; if (change.To == ProbeState.Dead) { Env.Log.Warn("probe rate changed", fields); } else { Env.Log.Info("probe rate changed", fields); } } /// 什么也不做、一直等到取消(监听方里不需要自己运行逻辑的积木)。 protected async Task WaitUntilCancelledAsync(CancellationToken ct) { try { await Task.Delay(Timeout.InfiniteTimeSpan, Env.Time, ct); } catch (OperationCanceledException) { // 被停止 } } } /// /// 拨号方作为行的主导方时记录的行:行按键(序号)追加,回复到达时按键回填列,到点把已经稳定的行从游标处切下来交出去。 /// 环形缓冲被覆盖的行(交出太慢)直接跳过。 /// internal sealed class DriverLog { private readonly object _gate = new(); private long _cursor; public DriverLog(StreamSpec spec, Side driver, int capacity) { Store = new RowStore(spec, driver, capacity); Spec = spec; } public RowStore Store { get; } public StreamSpec Spec { get; } public long Count => Store.NextKey; /// 取出游标之后已经稳定的行: 为 null 或 时取全部。 public RawStream? Take(Func? settled, bool all, int maxRows = int.MaxValue) { long from; long to; lock (_gate) { from = _cursor; to = from; long end = Store.NextKey; while (to < end && to - from < maxRows && (all || settled is null || !Store.Contains(to) || settled(to))) { to++; } if (to == from) { return null; } _cursor = to; } RawStream stream = Store.Cut(from, to, includeKey: Spec.Key is not null); return stream.Count == 0 ? null : stream; } }