using MoShiro.Protocol; namespace MoShiro.Backend.Analysis; /// /// 把原始块按时间分桶算成累加器。流式路径(1s 桶)和持久路径(分钟窗口)调用同一份代码, /// 所以实时值与历史值使用的是同一套口径。 /// public sealed class WindowEvaluator { private readonly IReadOnlyList _metrics; public WindowEvaluator(IReadOnlyList metrics) { _metrics = metrics; } public static WindowEvaluator Default { get; } = new(MetricRegistry.All); /// /// 评估一个块,结果合并进 (键是桶起点的 UTC 毫秒)。 /// 不为空时只计算这些指标(用于回填新增或变更的指标)。 /// public void Evaluate(RawChunk chunk, long bucketMs, IDictionary buckets, ISet? only = null) { var view = new ChunkView(chunk); foreach (RawStream stream in chunk.Streams) { foreach (MetricDef metric in _metrics) { if ((only is not null && !only.Contains(metric.Id)) || !metric.Matches(chunk.Header.Kind, stream.Name)) { continue; } metric.Op.Evaluate(view, stream, new BucketSink(metric, bucketMs, buckets)); } } } private sealed class BucketSink : IObservationSink { private readonly MetricDef _metric; private readonly long _bucketMs; private readonly IDictionary _buckets; public BucketSink(MetricDef metric, long bucketMs, IDictionary buckets) { _metric = metric; _bucketMs = bucketMs; _buckets = buckets; } public void Emit(long utcMs, double a, double b = 0) { long start = Math.DivRem(utcMs, _bucketMs, out long rem) * _bucketMs; if (rem < 0) { start -= _bucketMs; } if (!_buckets.TryGetValue(start, out MetricSet? set)) { set = new MetricSet(); _buckets[start] = set; } set.GetOrAdd(_metric.Id, _metric.Kind).Add(a, b); } } }