agent: unify listening and dialing with directed mutual measurements
Replace Probe with Agent across node runtime, Fleet contracts, backend state, API and storage. Report incoming sessions and outgoing link health through each Agent uplink, and retain directed link controls and retry. Update protocol fixtures and end-to-end coverage for the unified model; accept requested cancellation during the Fleet timeout test cleanup.
This commit is contained in:
1 parent
d5ebf29e57
commit
ad5efc62ff
206 files changed
+4202
-4964
No files matched your search
@@ -58,9 +58,9 @@ public sealed class QueryService
|
||||
|
||||
// ───────────────────────── 单条线路的时序 ─────────────────────────
|
||||
|
||||
/// <summary>这条线路现在是不是连着(Agent 报告了这个 Probe 的会话):连着的线路窗口以当前时间为终点,不去查“最后一个样本”——Backend 刚重启、热层和持久层还没追上时,后者会落后好几个小时,页面就显示成旧数据。</summary>
|
||||
/// <summary>这条线路现在是不是连着(监听方报告了这个拨号方的会话):连着的线路窗口以当前时间为终点,不去查“最后一个样本”——Backend 刚重启、热层和持久层还没追上时,后者会落后好几个小时,页面就显示成旧数据。</summary>
|
||||
private bool IsConnected(LineKey line) =>
|
||||
_runtime.TryGet(line.Agent, out AgentRuntimeState? state) && state.Probes.Any(p => p.Id == line.Probe.Value && p.IpVersion == line.IpVersion);
|
||||
_runtime.TryGet(line.Listener, out AgentRuntimeState? state) && state.Dialers.Any(p => p.Id == line.Dialer.Value && p.IpVersion == line.IpVersion);
|
||||
|
||||
public async ValueTask<Result<SeriesResult>> SeriesAsync(LineKey line, RangeRequest request, MetricSelection selection, CancellationToken ct)
|
||||
{
|
||||
@@ -70,7 +70,7 @@ public sealed class QueryService
|
||||
&& !IsConnected(line))
|
||||
{
|
||||
long now = _time.GetUtcNow().ToUnixTimeMilliseconds();
|
||||
if (await _loader.LatestSampleMsAsync(line.Agent, line.Series, now, ct) is { } latest && latest < now - 5000)
|
||||
if (await _loader.LatestSampleMsAsync(line.Listener, line.Series, now, ct) is { } latest && latest < now - 5000)
|
||||
{
|
||||
request = request with { Range = null, From = DateTimeOffset.FromUnixTimeMilliseconds(latest - span), To = DateTimeOffset.FromUnixTimeMilliseconds(latest) };
|
||||
}
|
||||
@@ -82,7 +82,7 @@ public sealed class QueryService
|
||||
return Result<SeriesResult>.Fail(error);
|
||||
}
|
||||
|
||||
Result<IReadOnlyList<SourceRow>> loaded = await _loader.LoadAsync(p.Plan, p.Range, line.Agent, line.Series, ct);
|
||||
Result<IReadOnlyList<SourceRow>> loaded = await _loader.LoadAsync(p.Plan, p.Range, line.Listener, line.Series, ct);
|
||||
if (!loaded.TryGet(out IReadOnlyList<SourceRow>? rows, out error))
|
||||
{
|
||||
return Result<SeriesResult>.Fail(error);
|
||||
@@ -124,7 +124,7 @@ public sealed class QueryService
|
||||
|
||||
SummaryResult summary = Summarize(bucketed.Merge(), bucketed.TotalRows, p.Plan, p.Range, selection, overlay);
|
||||
long nowMs = _time.GetUtcNow().ToUnixTimeMilliseconds();
|
||||
IReadOnlyList<EventRecord> events = await _events.ListAsync(line.Agent, null, p.Range.FromMs, p.Range.ToMs, null, 100, ct);
|
||||
IReadOnlyList<EventRecord> events = await _events.ListAsync(line.Listener, null, p.Range.FromMs, p.Range.ToMs, null, 100, ct);
|
||||
IReadOnlyList<GapInfo> gaps = FindGaps(bucketed, p.Range, nowMs, events);
|
||||
var versions = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
foreach (string id in selection.ExportIds)
|
||||
@@ -140,7 +140,7 @@ public sealed class QueryService
|
||||
|
||||
/// <summary>这条线路最早的数据(Unix 毫秒);没有数据返回 null。</summary>
|
||||
public ValueTask<long?> EarliestAsync(LineKey line, CancellationToken ct) =>
|
||||
_loader.EarliestSampleMsAsync(line.Agent, line.Series, _time.GetUtcNow().ToUnixTimeMilliseconds(), ct);
|
||||
_loader.EarliestSampleMsAsync(line.Listener, line.Series, _time.GetUtcNow().ToUnixTimeMilliseconds(), ct);
|
||||
|
||||
// ───────────────────────── 首页排行 ─────────────────────────
|
||||
|
||||
@@ -174,7 +174,7 @@ public sealed class QueryService
|
||||
}
|
||||
|
||||
IReadOnlyDictionary<string, double> overlay = selection.WantsScores ? await TestOverlayAsync(line.Key, ct) : new Dictionary<string, double>();
|
||||
IReadOnlyList<TestRecord> recent = await _tests.ListAsync(line.Key.Agent, line.Key.Probe, 1, line.Key.IpVersion, ct);
|
||||
IReadOnlyList<TestRecord> recent = await _tests.ListAsync(line.Key.Listener, line.Key.Dialer, 1, line.Key.IpVersion, ct);
|
||||
result.Add(new OverviewLine(line, Summarize(merged, count, p.Plan, p.Range, selection, overlay), recent.Count > 0 ? recent[0] : null));
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ public sealed class QueryService
|
||||
return Result<DistributionResult>.Fail(error);
|
||||
}
|
||||
|
||||
Result<IReadOnlyList<SourceRow>> loaded = await _loader.LoadAsync(p.Plan, p.Range, line.Agent, line.Series, ct);
|
||||
Result<IReadOnlyList<SourceRow>> loaded = await _loader.LoadAsync(p.Plan, p.Range, line.Listener, line.Series, ct);
|
||||
if (!loaded.TryGet(out IReadOnlyList<SourceRow>? rows, out error))
|
||||
{
|
||||
return Result<DistributionResult>.Fail(error);
|
||||
@@ -289,7 +289,7 @@ public sealed class QueryService
|
||||
return Result<HeatmapResult>.Fail(error);
|
||||
}
|
||||
|
||||
Result<IReadOnlyList<SourceRow>> loaded = await _loader.LoadAsync(p.Plan, p.Range, line.Agent, line.Series, ct);
|
||||
Result<IReadOnlyList<SourceRow>> loaded = await _loader.LoadAsync(p.Plan, p.Range, line.Listener, line.Series, ct);
|
||||
if (!loaded.TryGet(out IReadOnlyList<SourceRow>? rows, out error))
|
||||
{
|
||||
return Result<HeatmapResult>.Fail(error);
|
||||
@@ -379,7 +379,7 @@ public sealed class QueryService
|
||||
long cutoff = _time.GetUtcNow().ToUnixTimeMilliseconds() - (7 * Steps.Day);
|
||||
var overlay = new Dictionary<string, double>(StringComparer.Ordinal);
|
||||
var seenPlans = new HashSet<string>();
|
||||
foreach (TestRecord test in await _tests.ListAsync(line.Agent, line.Probe, 20, line.IpVersion, ct))
|
||||
foreach (TestRecord test in await _tests.ListAsync(line.Listener, line.Dialer, 20, line.IpVersion, ct))
|
||||
{
|
||||
if (test.Status != TestStatuses.Succeeded || test.FinishedAtMs is not { } finished || finished < cutoff || !seenPlans.Add(test.Plan))
|
||||
{
|
||||
@@ -422,7 +422,7 @@ public sealed class QueryService
|
||||
{
|
||||
long from = bucketed.T[start];
|
||||
long to = bucketed.T[i - 1] + range.StepMs;
|
||||
EventRecord? near = events.FirstOrDefault(e => e.AtMs >= from - range.StepMs && e.AtMs <= to && e.Kind is "probe_disconnected" or "agent_offline" or "clock_step" or "gap" or "cpu_saturated");
|
||||
EventRecord? near = events.FirstOrDefault(e => e.AtMs >= from - range.StepMs && e.AtMs <= to && e.Kind is "dialer_disconnected" or "agent_offline" or "clock_step" or "gap" or "cpu_saturated");
|
||||
gaps.Add(new GapInfo(from, to, near?.Kind ?? "no_data"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user