Files
justme 83e5120377 refactor!: rename the project from Monitor to MoShiro
Namespaces, assemblies, projects and directories, the solution, binaries,
images, systemd units, config paths, environment variables, protocol method
names, the media type and the raw chunk magic all move to MoShiro/moshiro.
Existing data and deployments are not compatible and must be recreated.
Also fix the architecture tests after the solution layout change and make
the update tests independent of their order.
2026-10-07 01:49:04 +08:00

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