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);
}
}
}