Files
MoShiro/backend/src/MoShiro.Backend.Analysis/Metrics/MetricRegistry.cs
T

76 lines
3.9 KiB
C#

namespace MoShiro.Backend.Analysis;
public sealed record PresetDef(string Id, string Label, IReadOnlyList<string> MetricIds);
/// <summary>
/// 指标注册表:存储、降采样、API、目录都由它驱动。新增指标时,在对应的 Metrics 文件里加一行声明,
/// 再把它放进这里的 <see cref="All"/>(显式注册,AOT 安全,不使用反射)。
/// </summary>
public static class MetricRegistry
{
public static readonly IReadOnlyList<MetricDef> All =
[
LinkMetrics.Rtt, LinkMetrics.LossUp, LinkMetrics.LossDown, LinkMetrics.LossTotal,
LinkMetrics.JitterUp, LinkMetrics.JitterDown, LinkMetrics.LossRun, LinkMetrics.LossRunUp, LinkMetrics.LossRunDown, LinkMetrics.Reorder, LinkMetrics.Dup, LinkMetrics.SamplesUdp,
TcpMetrics.Rtt, TcpMetrics.Slow, TcpMetrics.Stall, TcpMetrics.StallMax, TcpMetrics.Retrans, TcpMetrics.Srtt, TcpMetrics.BulkRate, TcpMetrics.BulkPeak, TcpMetrics.SamplesTcp,
SessionMetrics.ConnectSuccess, SessionMetrics.ConnectTcp, SessionMetrics.ConnectTls,
SessionMetrics.FailTimeout, SessionMetrics.FailReset, SessionMetrics.FailTls,
SessionMetrics.HoldResets, SessionMetrics.HoldLifetime,
SessionMetrics.TrainLoss, SessionMetrics.TrainJitter, SessionMetrics.TrainRate, SessionMetrics.SamplesConnect,
];
/// <summary>指标分组:前端按分组展示,新增指标只需要把它加进分组。</summary>
public static readonly IReadOnlyList<PresetDef> Presets =
[
new("quic", "QUIC Datagram", ["loss.up", "loss.down", "loss.total", "rtt.p50", "rtt.p95", "rtt.p99", "jitter.up.p95", "jitter.down.p95", "loss.run.up_max", "loss.run.down_max", "reorder", "dup"]),
new("udp", "QUIC Datagram (legacy alias)", ["loss.up", "loss.down", "loss.total", "rtt.p50", "rtt.p95", "rtt.p99", "jitter.up.p95", "jitter.down.p95", "loss.run_max", "reorder", "dup"]),
new("tcp", "TCP", ["tcp.rtt.p50", "tcp.rtt.p95", "tcp.rtt.p99", "tcp.rtt.p999", "tcp.rtt.max", "tcp.rtt.tail", "tcp.slow", "tcp.stall.per_min", "tcp.stall_max", "tcp.retrans", "tcp.srtt.p95"]),
new("connect", "Connect", ["connect.success", "connect.tcp_ms.p95", "connect.tls_ms.p95", "connect.fail.timeout", "connect.fail.rst", "connect.fail.tls"]),
new("session", "Session", ["hold.resets.per_h", "hold.lifetime_max"]),
new("scores", "Scores", ["score.base", "score.mc", "score.hy2", "score.reality"]),
];
private static readonly Dictionary<string, MetricDef> s_byId = All.ToDictionary(m => m.Id, StringComparer.Ordinal);
private static readonly Dictionary<string, (MetricDef Def, MetricExport Export)> s_byExport = BuildExportIndex();
public static MetricDef? Find(string metricId) => s_byId.GetValueOrDefault(metricId);
/// <summary>按导出 id(例如 <c>rtt.p95</c>)查找。</summary>
public static bool TryFindExport(string exportId, out MetricDef def, out MetricExport export)
{
if (s_byExport.TryGetValue(exportId, out (MetricDef Def, MetricExport Export) found))
{
def = found.Def;
export = found.Export;
return true;
}
def = null!;
export = null!;
return false;
}
public static PresetDef? FindPreset(string id) => Presets.FirstOrDefault(p => p.Id == id);
public static IEnumerable<(MetricDef Def, MetricExport Export)> AllExports() =>
All.SelectMany(def => def.Exports.Select(export => (def, export)));
private static Dictionary<string, (MetricDef, MetricExport)> BuildExportIndex()
{
var index = new Dictionary<string, (MetricDef, MetricExport)>(StringComparer.Ordinal);
foreach (MetricDef def in All)
{
foreach (MetricExport export in def.Exports)
{
if (!index.TryAdd(export.Id, (def, export)))
{
throw new InvalidOperationException($"duplicate export id {export.Id}");
}
}
}
return index;
}
}