namespace Monitor.Backend.Analysis;
public enum AccKind : byte
{
Counter = 1,
Ratio = 2,
Sum = 3,
Min = 4,
Max = 5,
Histogram = 6,
RunLength = 7,
Series = 8,
}
///
/// 所有指标的中间状态只能是下面几种累加器之一。合并规则只写一次,
/// 存储、降采样、按时间范围聚合都通用处理。
///
public abstract class Accumulator
{
public abstract AccKind Kind { get; }
/// 加入一个观测值。含义由累加器决定:Counter 加 ,Ratio 的 (a, b) 是 (分子, 分母),Histogram 记录 a。
public abstract void Add(double a, double b = 0);
/// 合并同类型的另一个累加器(精确合并,不是平均)。
public abstract void Merge(Accumulator other);
public abstract Accumulator Clone();
/// 样本数,用于置信说明。
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(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(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;
/// 百分比(0–100);没有样本时为 null。
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(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(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(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(other);
Value = Math.Max(Value, o.Value);
Count += o.Count;
}
public override Accumulator Clone() => new MaxAcc { Value = Value, Count = Count };
}
/// 连续段长度(例如连续丢包):只保留次数、总和和最大值,合并是精确的。
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(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 };
}
/// 固定分辨率的时间序列(仅用于测试结果):Add(a = 序号, b = 值),合并为拼接。
public sealed class SeriesAcc : Accumulator
{
public List 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(other).Points);
public override Accumulator Clone()
{
var copy = new SeriesAcc();
copy.Points.AddRange(Points);
return copy;
}
}