Files
MoShiro/backend/src/Monitor.Backend.Analysis/Accumulators/Accumulator.cs
T
2026-10-04 21:46:39 +08:00

242 lines
6.5 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.
namespace Monitor.Backend.Analysis;
public enum AccKind : byte
{
Counter = 1,
Ratio = 2,
Sum = 3,
Min = 4,
Max = 5,
Histogram = 6,
RunLength = 7,
Series = 8,
}
/// <summary>
/// 所有指标的中间状态只能是下面几种累加器之一。合并规则只写一次,
/// 存储、降采样、按时间范围聚合都通用处理。
/// </summary>
public abstract class Accumulator
{
public abstract AccKind Kind { get; }
/// <summary>加入一个观测值。含义由累加器决定:Counter 加 <paramref name="a"/>,Ratio 的 (a, b) 是 (分子, 分母),Histogram 记录 a。</summary>
public abstract void Add(double a, double b = 0);
/// <summary>合并同类型的另一个累加器(精确合并,不是平均)。</summary>
public abstract void Merge(Accumulator other);
public abstract Accumulator Clone();
/// <summary>样本数,用于置信说明。</summary>
public abstract ulong SampleCount { get; }
public static Accumulator Create(AccKind kind) => kind switch
{
AccKind.Counter => new CounterAcc(),
AccKind.Ratio => new RatioAcc(),
AccKind.Sum => new SumAcc(),
AccKind.Min => new MinAcc(),
AccKind.Max => new MaxAcc(),
AccKind.Histogram => new HistogramAcc(),
AccKind.RunLength => new RunLengthAcc(),
AccKind.Series => new SeriesAcc(),
_ => throw new ArgumentOutOfRangeException(nameof(kind)),
};
protected T Cast<T>(Accumulator other)
where T : Accumulator =>
other as T ?? throw new InvalidOperationException($"cannot merge {other.Kind} into {Kind}");
}
public sealed class CounterAcc : Accumulator
{
public ulong Value { get; set; }
public override AccKind Kind => AccKind.Counter;
public override ulong SampleCount => Value;
public override void Add(double a, double b = 0) => Value += (ulong)Math.Max(0, a);
public override void Merge(Accumulator other) => Value += Cast<CounterAcc>(other).Value;
public override Accumulator Clone() => new CounterAcc { Value = Value };
}
public sealed class RatioAcc : Accumulator
{
public ulong Numerator { get; set; }
public ulong Denominator { get; set; }
public override AccKind Kind => AccKind.Ratio;
public override ulong SampleCount => Denominator;
/// <summary>百分比(0–100);没有样本时为 null。</summary>
public double? Percent => Denominator == 0 ? null : 100.0 * Numerator / Denominator;
public override void Add(double a, double b = 0)
{
Numerator += (ulong)Math.Max(0, a);
Denominator += (ulong)Math.Max(0, b);
}
public override void Merge(Accumulator other)
{
var o = Cast<RatioAcc>(other);
Numerator += o.Numerator;
Denominator += o.Denominator;
}
public override Accumulator Clone() => new RatioAcc { Numerator = Numerator, Denominator = Denominator };
}
public sealed class SumAcc : Accumulator
{
public double Sum { get; set; }
public ulong Count { get; set; }
public override AccKind Kind => AccKind.Sum;
public override ulong SampleCount => Count;
public double? Mean => Count == 0 ? null : Sum / Count;
public override void Add(double a, double b = 0)
{
Sum += a;
Count++;
}
public override void Merge(Accumulator other)
{
var o = Cast<SumAcc>(other);
Sum += o.Sum;
Count += o.Count;
}
public override Accumulator Clone() => new SumAcc { Sum = Sum, Count = Count };
}
public sealed class MinAcc : Accumulator
{
public double Value { get; set; } = double.PositiveInfinity;
public ulong Count { get; set; }
public override AccKind Kind => AccKind.Min;
public override ulong SampleCount => Count;
public double? Result => Count == 0 ? null : Value;
public override void Add(double a, double b = 0)
{
Value = Math.Min(Value, a);
Count++;
}
public override void Merge(Accumulator other)
{
var o = Cast<MinAcc>(other);
Value = Math.Min(Value, o.Value);
Count += o.Count;
}
public override Accumulator Clone() => new MinAcc { Value = Value, Count = Count };
}
public sealed class MaxAcc : Accumulator
{
public double Value { get; set; } = double.NegativeInfinity;
public ulong Count { get; set; }
public override AccKind Kind => AccKind.Max;
public override ulong SampleCount => Count;
public double? Result => Count == 0 ? null : Value;
public override void Add(double a, double b = 0)
{
Value = Math.Max(Value, a);
Count++;
}
public override void Merge(Accumulator other)
{
var o = Cast<MaxAcc>(other);
Value = Math.Max(Value, o.Value);
Count += o.Count;
}
public override Accumulator Clone() => new MaxAcc { Value = Value, Count = Count };
}
/// <summary>连续段长度(例如连续丢包):只保留次数、总和和最大值,合并是精确的。</summary>
public sealed class RunLengthAcc : Accumulator
{
public ulong Runs { get; set; }
public double Total { get; set; }
public double Max { get; set; }
public override AccKind Kind => AccKind.RunLength;
public override ulong SampleCount => Runs;
public double? Mean => Runs == 0 ? null : Total / Runs;
public override void Add(double a, double b = 0)
{
Runs++;
Total += a;
Max = Math.Max(Max, a);
}
public override void Merge(Accumulator other)
{
var o = Cast<RunLengthAcc>(other);
Runs += o.Runs;
Total += o.Total;
Max = Math.Max(Max, o.Max);
}
public override Accumulator Clone() => new RunLengthAcc { Runs = Runs, Total = Total, Max = Max };
}
/// <summary>固定分辨率的时间序列(仅用于测试结果):Add(a = 序号, b = 值),合并为拼接。</summary>
public sealed class SeriesAcc : Accumulator
{
public List<double> Points { get; } = [];
public override AccKind Kind => AccKind.Series;
public override ulong SampleCount => (ulong)Points.Count;
public override void Add(double a, double b = 0)
{
int index = (int)a;
while (Points.Count <= index)
{
Points.Add(double.NaN);
}
Points[index] = b;
}
public override void Merge(Accumulator other) => Points.AddRange(Cast<SeriesAcc>(other).Points);
public override Accumulator Clone()
{
var copy = new SeriesAcc();
copy.Points.AddRange(Points);
return copy;
}
}