refactor!: rename the project from Monitor to MoShiro [CI]
ci/woodpecker/push/main-amd64-build Pipeline was successful
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.
This commit is contained in:
1 parent
27ac7fb787
commit
2e146d4da7
309 files changed
+1565
-1572
No files matched your search
@@ -0,0 +1,241 @@
|
||||
namespace MoShiro.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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
using Fleet.Core;
|
||||
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>一个时间桶里所有指标的累加器:<c>指标 id → 累加器</c>。</summary>
|
||||
public sealed class MetricSet
|
||||
{
|
||||
private readonly Dictionary<string, Accumulator> _items = new(StringComparer.Ordinal);
|
||||
|
||||
public IReadOnlyDictionary<string, Accumulator> Items => _items;
|
||||
|
||||
public int Count => _items.Count;
|
||||
|
||||
public Accumulator GetOrAdd(string metricId, AccKind kind)
|
||||
{
|
||||
if (!_items.TryGetValue(metricId, out Accumulator? acc))
|
||||
{
|
||||
acc = Accumulator.Create(kind);
|
||||
_items[metricId] = acc;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
public bool TryGet(string metricId, out Accumulator acc) => _items.TryGetValue(metricId, out acc!);
|
||||
|
||||
/// <summary>逐个指标精确合并。</summary>
|
||||
public void Merge(MetricSet other)
|
||||
{
|
||||
foreach ((string id, Accumulator acc) in other._items)
|
||||
{
|
||||
if (_items.TryGetValue(id, out Accumulator? mine))
|
||||
{
|
||||
if (mine.Kind == acc.Kind)
|
||||
{
|
||||
mine.Merge(acc);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_items[id] = acc.Clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Set(string metricId, Accumulator acc) => _items[metricId] = acc;
|
||||
|
||||
public MetricSet Clone()
|
||||
{
|
||||
var copy = new MetricSet();
|
||||
foreach ((string id, Accumulator acc) in _items)
|
||||
{
|
||||
copy._items[id] = acc.Clone();
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 累加器的二进制编码:存储层只把它当作不透明的字节。
|
||||
/// <c>varint 数量 | { varint id 长度 | id | u8 kind | varint 长度 | 状态 }…</c>
|
||||
/// </summary>
|
||||
public static class AccumulatorCodec
|
||||
{
|
||||
public static byte[] Encode(MetricSet set)
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
VarInt.Write(stream, (ulong)set.Count);
|
||||
foreach ((string id, Accumulator acc) in set.Items.OrderBy(i => i.Key, StringComparer.Ordinal))
|
||||
{
|
||||
byte[] idBytes = Encoding.UTF8.GetBytes(id);
|
||||
VarInt.Write(stream, (ulong)idBytes.Length);
|
||||
stream.Write(idBytes);
|
||||
stream.WriteByte((byte)acc.Kind);
|
||||
byte[] state = EncodeState(acc);
|
||||
VarInt.Write(stream, (ulong)state.Length);
|
||||
stream.Write(state);
|
||||
}
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
public static Result<MetricSet> Decode(ReadOnlySpan<byte> data)
|
||||
{
|
||||
var set = new MetricSet();
|
||||
if (!VarInt.TryRead(data, out ulong count, out int used))
|
||||
{
|
||||
return Bad();
|
||||
}
|
||||
|
||||
data = data[used..];
|
||||
for (ulong i = 0; i < count; i++)
|
||||
{
|
||||
if (!VarInt.TryRead(data, out ulong idLength, out used) || (ulong)(data.Length - used) < idLength)
|
||||
{
|
||||
return Bad();
|
||||
}
|
||||
|
||||
data = data[used..];
|
||||
string id = Encoding.UTF8.GetString(data[..(int)idLength]);
|
||||
data = data[(int)idLength..];
|
||||
if (data.Length < 1)
|
||||
{
|
||||
return Bad();
|
||||
}
|
||||
|
||||
var kind = (AccKind)data[0];
|
||||
data = data[1..];
|
||||
if (!VarInt.TryRead(data, out ulong stateLength, out used) || (ulong)(data.Length - used) < stateLength)
|
||||
{
|
||||
return Bad();
|
||||
}
|
||||
|
||||
data = data[used..];
|
||||
Accumulator? acc = DecodeState(kind, data[..(int)stateLength]);
|
||||
if (acc is null)
|
||||
{
|
||||
return Bad();
|
||||
}
|
||||
|
||||
set.Set(id, acc);
|
||||
data = data[(int)stateLength..];
|
||||
}
|
||||
|
||||
return Result<MetricSet>.Ok(set);
|
||||
}
|
||||
|
||||
private static Result<MetricSet> Bad() =>
|
||||
Result<MetricSet>.Fail(FleetErrors.Internal, "accumulator data is corrupt");
|
||||
|
||||
public static byte[] EncodeState(Accumulator acc)
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
switch (acc)
|
||||
{
|
||||
case CounterAcc c:
|
||||
VarInt.Write(stream, c.Value);
|
||||
break;
|
||||
case RatioAcc r:
|
||||
VarInt.Write(stream, r.Numerator);
|
||||
VarInt.Write(stream, r.Denominator);
|
||||
break;
|
||||
case SumAcc s:
|
||||
WriteDouble(stream, s.Sum);
|
||||
VarInt.Write(stream, s.Count);
|
||||
break;
|
||||
case MinAcc m:
|
||||
WriteDouble(stream, m.Value);
|
||||
VarInt.Write(stream, m.Count);
|
||||
break;
|
||||
case MaxAcc x:
|
||||
WriteDouble(stream, x.Value);
|
||||
VarInt.Write(stream, x.Count);
|
||||
break;
|
||||
case RunLengthAcc rl:
|
||||
VarInt.Write(stream, rl.Runs);
|
||||
WriteDouble(stream, rl.Total);
|
||||
WriteDouble(stream, rl.Max);
|
||||
break;
|
||||
case HistogramAcc h:
|
||||
VarInt.Write(stream, h.Count);
|
||||
WriteDouble(stream, h.Sum);
|
||||
WriteDouble(stream, h.Min);
|
||||
WriteDouble(stream, h.Max);
|
||||
VarInt.Write(stream, (ulong)h.NonEmptyBuckets);
|
||||
int previous = 0;
|
||||
foreach ((int bucket, ulong n) in h.Buckets())
|
||||
{
|
||||
VarInt.Write(stream, (ulong)(bucket - previous));
|
||||
VarInt.Write(stream, n);
|
||||
previous = bucket;
|
||||
}
|
||||
|
||||
break;
|
||||
case SeriesAcc se:
|
||||
VarInt.Write(stream, (ulong)se.Points.Count);
|
||||
foreach (double p in se.Points)
|
||||
{
|
||||
WriteDouble(stream, p);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("unknown accumulator");
|
||||
}
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
public static Accumulator? DecodeState(AccKind kind, ReadOnlySpan<byte> data)
|
||||
{
|
||||
var reader = new Reader(data);
|
||||
switch (kind)
|
||||
{
|
||||
case AccKind.Counter:
|
||||
return reader.ReadVarInt(out ulong v) ? new CounterAcc { Value = v } : null;
|
||||
case AccKind.Ratio:
|
||||
return reader.ReadVarInt(out ulong num) && reader.ReadVarInt(out ulong den)
|
||||
? new RatioAcc { Numerator = num, Denominator = den }
|
||||
: null;
|
||||
case AccKind.Sum:
|
||||
return reader.ReadDouble(out double sum) && reader.ReadVarInt(out ulong sn) ? new SumAcc { Sum = sum, Count = sn } : null;
|
||||
case AccKind.Min:
|
||||
return reader.ReadDouble(out double min) && reader.ReadVarInt(out ulong mn) ? new MinAcc { Value = min, Count = mn } : null;
|
||||
case AccKind.Max:
|
||||
return reader.ReadDouble(out double max) && reader.ReadVarInt(out ulong xn) ? new MaxAcc { Value = max, Count = xn } : null;
|
||||
case AccKind.RunLength:
|
||||
return reader.ReadVarInt(out ulong runs) && reader.ReadDouble(out double total) && reader.ReadDouble(out double rmax)
|
||||
? new RunLengthAcc { Runs = runs, Total = total, Max = rmax }
|
||||
: null;
|
||||
case AccKind.Histogram:
|
||||
{
|
||||
if (!reader.ReadVarInt(out ulong count) || !reader.ReadDouble(out double hsum) || !reader.ReadDouble(out double hmin)
|
||||
|| !reader.ReadDouble(out double hmax) || !reader.ReadVarInt(out ulong buckets) || buckets > (ulong)HistogramAcc.BucketCount)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var list = new List<(int, ulong)>((int)buckets);
|
||||
int bucket = 0;
|
||||
for (ulong i = 0; i < buckets; i++)
|
||||
{
|
||||
if (!reader.ReadVarInt(out ulong delta) || !reader.ReadVarInt(out ulong n))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
bucket += (int)delta;
|
||||
if (bucket >= HistogramAcc.BucketCount)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
list.Add((bucket, n));
|
||||
}
|
||||
|
||||
var hist = new HistogramAcc();
|
||||
hist.Restore(list, count, hsum, hmin, hmax);
|
||||
return hist;
|
||||
}
|
||||
|
||||
case AccKind.Series:
|
||||
{
|
||||
if (!reader.ReadVarInt(out ulong n) || n > 10_000_000)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var series = new SeriesAcc();
|
||||
for (ulong i = 0; i < n; i++)
|
||||
{
|
||||
if (!reader.ReadDouble(out double p))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
series.Points.Add(p);
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteDouble(MemoryStream stream, double value)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[8];
|
||||
BinaryPrimitives.WriteDoubleLittleEndian(bytes, value);
|
||||
stream.Write(bytes);
|
||||
}
|
||||
|
||||
private ref struct Reader
|
||||
{
|
||||
private ReadOnlySpan<byte> _data;
|
||||
|
||||
public Reader(ReadOnlySpan<byte> data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public bool ReadVarInt(out ulong value)
|
||||
{
|
||||
if (!VarInt.TryRead(_data, out value, out int used))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_data = _data[used..];
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ReadDouble(out double value)
|
||||
{
|
||||
if (_data.Length < 8)
|
||||
{
|
||||
value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = BinaryPrimitives.ReadDoubleLittleEndian(_data);
|
||||
_data = _data[8..];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>
|
||||
/// 固定对数桶直方图:100 µs ~ 30 s,每 2 倍 8 个桶(相对误差 ≤ 4.5%)。
|
||||
/// 稀疏存储(只保留非空桶),因为 1 秒热层里每个桶只有几个样本。数值单位是毫秒。
|
||||
/// </summary>
|
||||
public sealed class HistogramAcc : Accumulator
|
||||
{
|
||||
public const double MinValueMs = 0.1;
|
||||
public const double MaxValueMs = 30_000;
|
||||
public const int BucketsPerOctave = 8;
|
||||
|
||||
/// <summary>桶 0 是下溢桶 [0, 0.1),桶 i≥1 是 [0.1·2^((i−1)/8), 0.1·2^(i/8))。</summary>
|
||||
public static readonly int BucketCount = 1 + (int)Math.Ceiling(BucketsPerOctave * Math.Log2(MaxValueMs / MinValueMs));
|
||||
|
||||
private int[] _index = new int[4];
|
||||
private ulong[] _counts = new ulong[4];
|
||||
private int _used;
|
||||
|
||||
public override AccKind Kind => AccKind.Histogram;
|
||||
|
||||
public ulong Count { get; private set; }
|
||||
|
||||
public double Sum { get; private set; }
|
||||
|
||||
public double Min { get; private set; } = double.PositiveInfinity;
|
||||
|
||||
public double Max { get; private set; } = double.NegativeInfinity;
|
||||
|
||||
public override ulong SampleCount => Count;
|
||||
|
||||
public double? Mean => Count == 0 ? null : Sum / Count;
|
||||
|
||||
public int NonEmptyBuckets => _used;
|
||||
|
||||
public static int BucketOf(double valueMs)
|
||||
{
|
||||
if (!(valueMs >= MinValueMs))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bucket = 1 + (int)Math.Floor(BucketsPerOctave * Math.Log2(valueMs / MinValueMs));
|
||||
return Math.Min(bucket, BucketCount - 1);
|
||||
}
|
||||
|
||||
public static double LowerBound(int bucket) => bucket == 0 ? 0 : MinValueMs * Math.Pow(2, (bucket - 1) / (double)BucketsPerOctave);
|
||||
|
||||
public static double UpperBound(int bucket) => MinValueMs * Math.Pow(2, bucket / (double)BucketsPerOctave);
|
||||
|
||||
public override void Add(double a, double b = 0)
|
||||
{
|
||||
if (double.IsNaN(a) || a < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddToBucket(BucketOf(a), 1);
|
||||
Count++;
|
||||
Sum += a;
|
||||
Min = Math.Min(Min, a);
|
||||
Max = Math.Max(Max, a);
|
||||
}
|
||||
|
||||
public override void Merge(Accumulator other)
|
||||
{
|
||||
var o = Cast<HistogramAcc>(other);
|
||||
for (int i = 0; i < o._used; i++)
|
||||
{
|
||||
AddToBucket(o._index[i], o._counts[i]);
|
||||
}
|
||||
|
||||
Count += o.Count;
|
||||
Sum += o.Sum;
|
||||
Min = Math.Min(Min, o.Min);
|
||||
Max = Math.Max(Max, o.Max);
|
||||
}
|
||||
|
||||
public override Accumulator Clone()
|
||||
{
|
||||
var copy = new HistogramAcc
|
||||
{
|
||||
_index = (int[])_index.Clone(),
|
||||
_counts = (ulong[])_counts.Clone(),
|
||||
_used = _used,
|
||||
Count = Count,
|
||||
Sum = Sum,
|
||||
Min = Min,
|
||||
Max = Max,
|
||||
};
|
||||
return copy;
|
||||
}
|
||||
|
||||
/// <summary>分位数:取累计计数首次达到 q·N 的桶的几何中点,并限制在实际的最小/最大值之内。</summary>
|
||||
public double? Quantile(double q)
|
||||
{
|
||||
if (Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
double target = Math.Clamp(q, 0, 1) * Count;
|
||||
ulong cumulative = 0;
|
||||
for (int i = 0; i < _used; i++)
|
||||
{
|
||||
cumulative += _counts[i];
|
||||
if (cumulative >= target && cumulative > 0)
|
||||
{
|
||||
int bucket = _index[i];
|
||||
double estimate = bucket == 0 ? Min : Math.Sqrt(LowerBound(bucket) * UpperBound(bucket));
|
||||
return Math.Clamp(estimate, Min, Max);
|
||||
}
|
||||
}
|
||||
|
||||
return Max;
|
||||
}
|
||||
|
||||
/// <summary>按桶遍历(桶号升序)。</summary>
|
||||
public IEnumerable<(int Bucket, ulong Count)> Buckets()
|
||||
{
|
||||
for (int i = 0; i < _used; i++)
|
||||
{
|
||||
yield return (_index[i], _counts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>用于反序列化:直接设置状态。</summary>
|
||||
internal void Restore(IReadOnlyList<(int Bucket, ulong Count)> buckets, ulong count, double sum, double min, double max)
|
||||
{
|
||||
_used = 0;
|
||||
foreach ((int bucket, ulong n) in buckets)
|
||||
{
|
||||
AddToBucket(bucket, n);
|
||||
}
|
||||
|
||||
Count = count;
|
||||
Sum = sum;
|
||||
Min = min;
|
||||
Max = max;
|
||||
}
|
||||
|
||||
private void AddToBucket(int bucket, ulong n)
|
||||
{
|
||||
int pos = Array.BinarySearch(_index, 0, _used, bucket);
|
||||
if (pos >= 0)
|
||||
{
|
||||
_counts[pos] += n;
|
||||
return;
|
||||
}
|
||||
|
||||
pos = ~pos;
|
||||
if (_used == _index.Length)
|
||||
{
|
||||
Array.Resize(ref _index, _used * 2);
|
||||
Array.Resize(ref _counts, _used * 2);
|
||||
}
|
||||
|
||||
Array.Copy(_index, pos, _index, pos + 1, _used - pos);
|
||||
Array.Copy(_counts, pos, _counts, pos + 1, _used - pos);
|
||||
_index[pos] = bucket;
|
||||
_counts[pos] = n;
|
||||
_used++;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user