Files
MoShiro/backend/tests/Monitor.Backend.Analysis.Tests/AccumulatorTests.cs
T
2026-10-04 21:46:39 +08:00

163 lines
5.1 KiB
C#

using Monitor.Backend.Analysis;
namespace Monitor.Backend.Analysis.Tests;
public sealed class AccumulatorTests
{
[Theory]
[InlineData(0.5)]
[InlineData(0.9)]
[InlineData(0.95)]
[InlineData(0.99)]
public void Histogram_Quantile_WithinRelativeError(double q)
{
var rng = new Random(42);
var values = new List<double>();
var hist = new HistogramAcc();
for (int i = 0; i < 20_000; i++)
{
// 对数正态分布:中位数约 50 ms,长尾
double v = 50 * Math.Exp(rng.NextDouble() * 2.5 - 1.2);
values.Add(v);
hist.Add(v);
}
values.Sort();
double exact = values[(int)(q * (values.Count - 1))];
double estimate = hist.Quantile(q)!.Value;
Assert.InRange(Math.Abs(estimate - exact) / exact, 0, 0.045);
}
[Fact]
public void Histogram_Merge_EqualsSingleAccumulator()
{
var rng = new Random(7);
var whole = new HistogramAcc();
var left = new HistogramAcc();
var right = new HistogramAcc();
for (int i = 0; i < 5000; i++)
{
double v = rng.NextDouble() * 200;
whole.Add(v);
(i % 2 == 0 ? left : right).Add(v);
}
left.Merge(right);
Assert.Equal(whole.Count, left.Count);
Assert.Equal(whole.Quantile(0.95), left.Quantile(0.95));
Assert.Equal(whole.Quantile(0.5), left.Quantile(0.5));
Assert.Equal(whole.Max, left.Max);
Assert.Equal(whole.NonEmptyBuckets, left.NonEmptyBuckets);
}
[Fact]
public void Histogram_Empty_HasNoQuantiles()
{
var hist = new HistogramAcc();
Assert.Null(hist.Quantile(0.5));
Assert.Null(hist.Mean);
}
[Fact]
public void Histogram_SingleSample_QuantilesClampToSample()
{
var hist = new HistogramAcc();
hist.Add(42);
Assert.Equal(42, hist.Quantile(0.01));
Assert.Equal(42, hist.Quantile(0.99));
}
[Fact]
public void Histogram_IgnoresNegativeAndNaN()
{
var hist = new HistogramAcc();
hist.Add(-1);
hist.Add(double.NaN);
Assert.Equal(0UL, hist.Count);
}
[Fact]
public void Histogram_ValuesBeyondRange_LandInEdgeBuckets()
{
var hist = new HistogramAcc();
hist.Add(0.01);
hist.Add(1_000_000);
Assert.Equal(2UL, hist.Count);
Assert.Equal(2, hist.NonEmptyBuckets);
}
[Fact]
public void Ratio_Merge_SumsNumeratorAndDenominator()
{
var a = new RatioAcc();
a.Add(1, 10);
var b = new RatioAcc();
b.Add(3, 10);
a.Merge(b);
Assert.Equal(20.0, a.Percent);
Assert.Null(new RatioAcc().Percent);
}
[Fact]
public void Merge_DifferentKinds_Throws()
{
Assert.Throws<InvalidOperationException>(() => new CounterAcc().Merge(new RatioAcc()));
}
[Fact]
public void Codec_RoundTrip_PreservesEveryKind()
{
var set = new MetricSet();
set.GetOrAdd("c", AccKind.Counter).Add(5);
set.GetOrAdd("r", AccKind.Ratio).Add(2, 9);
set.GetOrAdd("s", AccKind.Sum).Add(2.5);
set.GetOrAdd("n", AccKind.Min).Add(-3);
set.GetOrAdd("x", AccKind.Max).Add(11);
set.GetOrAdd("l", AccKind.RunLength).Add(4);
var hist = (HistogramAcc)set.GetOrAdd("h", AccKind.Histogram);
foreach (double v in new[] { 1.0, 20, 20, 300 })
{
hist.Add(v);
}
set.GetOrAdd("t", AccKind.Series).Add(2, 7.5);
MetricSet decoded = AccumulatorCodec.Decode(AccumulatorCodec.Encode(set)).Value;
Assert.Equal(5UL, ((CounterAcc)decoded.Items["c"]).Value);
Assert.Equal(9UL, ((RatioAcc)decoded.Items["r"]).Denominator);
Assert.Equal(2.5, ((SumAcc)decoded.Items["s"]).Sum);
Assert.Equal(-3, ((MinAcc)decoded.Items["n"]).Value);
Assert.Equal(11, ((MaxAcc)decoded.Items["x"]).Value);
Assert.Equal(4, ((RunLengthAcc)decoded.Items["l"]).Max);
var h2 = (HistogramAcc)decoded.Items["h"];
Assert.Equal(hist.Count, h2.Count);
Assert.Equal(hist.Quantile(0.5), h2.Quantile(0.5));
Assert.Equal(7.5, ((SeriesAcc)decoded.Items["t"]).Points[2]);
}
[Fact]
public void Codec_Decode_RejectsTruncatedData()
{
var set = new MetricSet();
set.GetOrAdd("h", AccKind.Histogram).Add(10);
byte[] bytes = AccumulatorCodec.Encode(set);
Assert.False(AccumulatorCodec.Decode(bytes.AsSpan(0, bytes.Length - 3)).IsSuccess);
Assert.False(AccumulatorCodec.Decode([200, 200]).IsSuccess);
}
[Fact]
public void MetricSet_Merge_CombinesDisjointAndSharedMetrics()
{
var a = new MetricSet();
a.GetOrAdd("x", AccKind.Counter).Add(1);
var b = new MetricSet();
b.GetOrAdd("x", AccKind.Counter).Add(2);
b.GetOrAdd("y", AccKind.Counter).Add(4);
a.Merge(b);
Assert.Equal(3UL, ((CounterAcc)a.Items["x"]).Value);
Assert.Equal(4UL, ((CounterAcc)a.Items["y"]).Value);
Assert.Equal(2UL, ((CounterAcc)b.Items["x"]).Value);
}
}