From 93abf59afbf17537dbff4f7d8c627fa0b81f95bc Mon Sep 17 00:00:00 2001 From: justme Date: Thu, 22 Oct 2026 10:57:53 +0800 Subject: [PATCH] backend: leave the time before the first rate record unknown in the adaptive audit --- .../Query/AdaptiveAudit.cs | 11 ++++++----- .../MoShiro.Backend.Tests/AdaptiveAuditTests.cs | 13 +++++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/backend/src/MoShiro.Backend.Application/Query/AdaptiveAudit.cs b/backend/src/MoShiro.Backend.Application/Query/AdaptiveAudit.cs index f680623..39ffe10 100644 --- a/backend/src/MoShiro.Backend.Application/Query/AdaptiveAudit.cs +++ b/backend/src/MoShiro.Backend.Application/Query/AdaptiveAudit.cs @@ -74,7 +74,8 @@ public static class AdaptiveAudit /// /// 把按时间排好的状态变化还原成每个时间桶里各状态的秒数。一次变化之后到下一次变化之前,是变化后的状态和频率; - /// 区间里第一次变化之前,状态是这次变化的「从」(频率不知道,不计入平均)。一种采集方法一条时间线(同一条线路上每种方法各自变化)。 + /// 第一次记录之前什么都不知道,不计入任何状态(节点启动时会记一条「start → watch」,所以每个自适应的任务从启动起就有记录; + /// 没有这条记录的时段是节点还没升级到会上报的版本)。一种采集方法一条时间线(同一条线路上每种方法各自变化)。 /// public static AdaptiveResult Summarize(IReadOnlyList changes, long fromMs, long toMs, long stepMs) { @@ -92,10 +93,10 @@ public static class AdaptiveAudit var maxLoss = new double[count]; RateChange[] ordered = [.. group.OrderBy(c => c.AtMs)]; - // 每一段:[开始, 结束) 内的状态;第一段在第一次变化之前,状态取它的「从」 - string state = ordered[0].From; - double? hz = null; - long cursor = Math.Min(ordered[0].AtMs, fromMs); + // 每一段:[这次变化, 下一次变化) 内是变化后的状态和频率;第一次记录之前不知道,不计 + string state = ordered[0].To; + double? hz = ordered[0].Hz; + long cursor = ordered[0].AtMs; foreach (RateChange change in ordered) { AddSegment(cursor, change.AtMs, state, hz); diff --git a/backend/tests/MoShiro.Backend.Tests/AdaptiveAuditTests.cs b/backend/tests/MoShiro.Backend.Tests/AdaptiveAuditTests.cs index a69f289..1c93818 100644 --- a/backend/tests/MoShiro.Backend.Tests/AdaptiveAuditTests.cs +++ b/backend/tests/MoShiro.Backend.Tests/AdaptiveAuditTests.cs @@ -28,9 +28,10 @@ public sealed class AdaptiveAuditTests : IClassFixture { RateChange[] changes = [ - Change(s_t0 + (30 * Minute), "quic_ping", "watch", "quiet", 2), // 10:00–10:30 watch(频率未知),之后 quiet 2 Hz + Change(s_t0, "quic_ping", "start", "watch", 10, "start"), // 10:00 起步:watch 10 Hz + Change(s_t0 + (30 * Minute), "quic_ping", "watch", "quiet", 2), // 10:30 起 quiet 2 Hz Change(s_t0 + (90 * Minute), "quic_ping", "quiet", "active", 10, "sustained loss", 0.4), // 11:30 起 active 10 Hz - Change(s_t0 + (30 * Minute), "tcp_ping", "quiet", "watch", 20, "rtt above baseline"), + Change(s_t0 + (30 * Minute), "tcp_ping", "quiet", "watch", 20, "rtt above baseline"), // 这条方法 10:30 才有第一条记录 ]; AdaptiveResult result = AdaptiveAudit.Summarize(changes, s_t0, s_t0 + (2 * Hour), Hour); @@ -39,18 +40,18 @@ public sealed class AdaptiveAuditTests : IClassFixture Assert.Equal(2, quic.Buckets.Count); AdaptiveBucket first = quic.Buckets[0]; Assert.Equal((1800, 1800, 0), ((int)first.WatchSeconds, (int)first.QuietSeconds, (int)first.ActiveSeconds)); // 10:00–10:30 watch,10:30–11:00 quiet - Assert.Equal(2, first.AverageHz); // 只有 quiet 那半小时知道频率 - Assert.Equal(1, first.Changes); + Assert.Equal(6, first.AverageHz); // (10 Hz × 30 min + 2 Hz × 30 min) / 60 min + Assert.Equal(2, first.Changes); AdaptiveBucket second = quic.Buckets[1]; Assert.Equal((1800, 1800), ((int)second.QuietSeconds, (int)second.ActiveSeconds)); // 11:00–11:30 quiet,11:30–12:00 active Assert.Equal(6, second.AverageHz); // (2 Hz × 30 min + 10 Hz × 30 min) / 60 min Assert.Equal((1, 0.4), (second.Changes, second.MaxLossPct)); AdaptiveKind tcp = Assert.Single(result.Kinds, k => k.Kind == "tcp_ping"); - Assert.Equal((1800, 1800), ((int)tcp.Buckets[0].QuietSeconds, (int)tcp.Buckets[0].WatchSeconds)); + Assert.Equal((0, 1800), ((int)tcp.Buckets[0].QuietSeconds, (int)tcp.Buckets[0].WatchSeconds)); // 10:00–10:30 没有记录,不算任何状态 Assert.Equal(3600, (int)tcp.Buckets[1].WatchSeconds); - Assert.Equal(3, result.Changes.Count); + Assert.Equal(4, result.Changes.Count); Assert.Equal("quic_ping", result.Changes[0].Kind); // 最新的在前 }