ci/woodpecker/push/main-amd64-build Pipeline was successful
Namespaces, assemblies, solution, binaries, images, systemd units, config paths, environment variables, protocol methods and the Linux user are all renamed. Breaking: existing databases, node identities and installed units must be recreated.
163 lines
5.1 KiB
C#
163 lines
5.1 KiB
C#
using MoShiro.Backend.Analysis;
|
|
|
|
namespace MoShiro.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);
|
|
}
|
|
}
|