web: show how long a line has been out of contact
The Backend now keeps the time since a link's control layer was last up, because retries move the control state in and out of backoff and its own timestamp keeps resetting. The overview exposes it as control_down_since and the card shows it next to the lost-contact notice.
This commit is contained in:
1 parent
5245f03a78
commit
57acecdb86
7 files changed
+41
-8
No files matched your search
@@ -8774,7 +8774,8 @@
|
||||
"control_state": {
|
||||
"type": "string",
|
||||
"default": "dialing"
|
||||
}
|
||||
},
|
||||
"control_down_since": {}
|
||||
}
|
||||
},
|
||||
"RawChunkMetaDto": {
|
||||
|
||||
@@ -53,7 +53,7 @@ public static class Mappers
|
||||
QuicState.Retrying => "retry_wait",
|
||||
QuicState.Idle => "idle",
|
||||
_ => "connecting",
|
||||
}, quic.RetryAttempt, quic.RetryInMs, ControlStateName(link.Control.State));
|
||||
}, quic.RetryAttempt, quic.RetryInMs, ControlStateName(link.Control.State), Time(snapshot.DownSinceMs));
|
||||
}
|
||||
|
||||
private static string ControlStateName(ControlState state) => state switch
|
||||
|
||||
@@ -52,7 +52,8 @@ public sealed record QuicLineHealthDto(
|
||||
string State = "connecting",
|
||||
int RetryAttempt = 0,
|
||||
long? RetryInMs = null,
|
||||
string ControlState = "dialing");
|
||||
string ControlState = "dialing",
|
||||
DateTimeOffset? ControlDownSince = null);
|
||||
|
||||
public sealed record OverviewDto(string Range, DateTimeOffset From, DateTimeOffset To, string Step, string Source, bool Degraded, IReadOnlyList<OverviewLineDto> Lines);
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ using MoShiro.Protocol;
|
||||
|
||||
namespace MoShiro.Backend.Application;
|
||||
|
||||
public sealed record ReportedLink(LinkHealth Health, long AtMs);
|
||||
/// <summary><paramref name="DownSinceMs"/>:控制层从什么时候起一直不是在线(Unix 毫秒);在线时为空。重试会让控制层状态反复进出退避,所以由 Backend 连着记,不用状态自己的时间。</summary>
|
||||
public sealed record ReportedLink(LinkHealth Health, long AtMs, long? DownSinceMs = null);
|
||||
|
||||
/// <summary>Latest per-line control and task health reported by dialing nodes. Volatile runtime state, not historical metrics.</summary>
|
||||
public sealed class LinkRuntime
|
||||
@@ -20,7 +21,8 @@ public sealed class LinkRuntime
|
||||
{
|
||||
var key = (dialerId, link.ListenerId, link.IpVersion);
|
||||
current.Add(key);
|
||||
_links[key] = new ReportedLink(link, atMs);
|
||||
long? downSince = link.Control.State == ControlState.Up ? null : _links.TryGetValue(key, out ReportedLink? previous) && previous.DownSinceMs is { } since ? since : atMs;
|
||||
_links[key] = new ReportedLink(link, atMs, downSince);
|
||||
}
|
||||
|
||||
foreach ((string Dialer, string Listener, string IpVersion) key in _links.Keys.Where(k => k.Dialer == dialerId && !current.Contains(k)))
|
||||
@@ -41,7 +43,7 @@ public sealed class LinkRuntime
|
||||
old.Control.RetryInMs = null;
|
||||
old.Task.Quic.State = QuicState.Idle;
|
||||
old.Task.Quic.RetryInMs = null;
|
||||
_links[pair.Key] = new ReportedLink(old, atMs);
|
||||
_links[pair.Key] = new ReportedLink(old, atMs, pair.Value.DownSinceMs ?? atMs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace MoShiro.Backend.Tests;
|
||||
|
||||
public sealed class LinkRuntimeTests
|
||||
{
|
||||
private static LinkHealth Health(ControlState state) => new() { ListenerId = "agt_l", Control = { State = state } };
|
||||
|
||||
[Fact]
|
||||
public void DownSince_StaysAtTheFirstLostReport_AndClearsWhenTheControlLinkIsUp()
|
||||
{
|
||||
var runtime = new LinkRuntime();
|
||||
runtime.Replace("agt_d", 1_000, [Health(ControlState.Up)]);
|
||||
Assert.Null(runtime.Get("agt_d", "agt_l", IpVersions.V4)!.DownSinceMs);
|
||||
|
||||
runtime.Replace("agt_d", 2_000, [Health(ControlState.Backoff)]);
|
||||
runtime.Replace("agt_d", 3_000, [Health(ControlState.Dialing)]); // 重试期间状态在退避和拨号之间来回,失联的起点不变
|
||||
runtime.Replace("agt_d", 4_000, [Health(ControlState.Backoff)]);
|
||||
Assert.Equal(2_000, runtime.Get("agt_d", "agt_l", IpVersions.V4)!.DownSinceMs);
|
||||
|
||||
runtime.MarkDialerOffline("agt_d", 5_000);
|
||||
Assert.Equal(2_000, runtime.Get("agt_d", "agt_l", IpVersions.V4)!.DownSinceMs);
|
||||
|
||||
runtime.Replace("agt_d", 6_000, [Health(ControlState.Up)]);
|
||||
Assert.Null(runtime.Get("agt_d", "agt_l", IpVersions.V4)!.DownSinceMs);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user