Skip to content

Commit 702de62

Browse files
committed
Warn when agent clock skew is silently suppressing a site's alerts
The AlertFreshness gate (samples >10 min old skip the alert state machines, so a replayed outage backlog doesn't fire alerts hours late) has a silent failure mode: an agent host whose clock runs >10 min behind server time stamps every sample stale, permanently disabling the site's target and health alerts with no trace - monitoring data still records, so nothing looks wrong. Distinguish the two by tunnel uptime: a backlog replay lands right after reconnect, so skipped samples inside a 15 min post-connect grace stay quiet. Skipped samples on a tunnel connected longer than that mean clock skew (or badly delayed delivery), and now log a rate-limited warning (hourly per site) naming the offset and pointing at the host's clock/NTP.
1 parent d66d58a commit 702de62

1 file changed

Lines changed: 36 additions & 0 deletions

File tree

src/NetworkOptimizer.Web/Services/AgentProbeResultSink.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ public class AgentProbeResultSink
5353
// caches (replay is chronological, so the caches end on the newest sample).
5454
private static readonly TimeSpan AlertFreshness = TimeSpan.FromMinutes(10);
5555

56+
// Stale samples right after a reconnect are the buffered backlog replaying -
57+
// expected, and what AlertFreshness exists for. Stale samples on a tunnel
58+
// that has been up far longer than any backlog takes to drain mean the agent
59+
// host's clock is behind server time, and every sample is skipping alert
60+
// evaluation - silently and indefinitely. Warn (rate-limited per site) so
61+
// the operator knows alerts are not firing and can fix the host's clock/NTP.
62+
private static readonly TimeSpan ReplayGraceAfterConnect = TimeSpan.FromMinutes(15);
63+
private static readonly TimeSpan SkewWarnInterval = TimeSpan.FromHours(1);
64+
private readonly ConcurrentDictionary<string, DateTime> _skewWarnedAt = new();
65+
5666
// Per-site topology-boundary aggregator (fabric sums, AP backhaul, gateway WAN),
5767
// the same LanFabricAggregator the directly-monitored fast tier uses. Keyed by slug
5868
// because this sink is a singleton serving every agent site.
@@ -786,7 +796,10 @@ await influx.WriteDeviceHealthAsync(
786796
// config push gives the alert a device label instead of a bare MAC.
787797
// Replayed backlog samples skip evaluation (see AlertFreshness).
788798
if (DateTime.UtcNow - timestamp > AlertFreshness)
799+
{
800+
NoteAlertEvaluationSkipped(connection, timestamp);
789801
continue;
802+
}
790803
try
791804
{
792805
string? deviceName = null;
@@ -999,6 +1012,10 @@ await influx.WriteLatencyAsync(
9991012
target.TargetId, connection.SiteSlug);
10001013
}
10011014
}
1015+
else
1016+
{
1017+
NoteAlertEvaluationSkipped(connection, timestamp);
1018+
}
10021019

10031020
if (result.Success)
10041021
target.LastVerified = timestamp;
@@ -1007,6 +1024,25 @@ await influx.WriteLatencyAsync(
10071024
await db.SaveChangesAsync(ct);
10081025
}
10091026

1027+
/// <summary>
1028+
/// Called when a relayed sample is too old for alert evaluation (see
1029+
/// <see cref="AlertFreshness"/>). Right after a reconnect that's the buffered
1030+
/// backlog replaying and stays quiet. On a long-connected tunnel it means the
1031+
/// agent host's clock is behind server time (or samples are arriving badly
1032+
/// delayed), so the site's alerts are silently not firing - surface that with
1033+
/// a rate-limited warning instead of letting it go unnoticed.
1034+
/// </summary>
1035+
private void NoteAlertEvaluationSkipped(AgentTunnelConnection connection, DateTime sampleTimestamp)
1036+
{
1037+
var now = DateTime.UtcNow;
1038+
if (now - connection.ConnectedAt < ReplayGraceAfterConnect) return;
1039+
if (_skewWarnedAt.TryGetValue(connection.SiteSlug, out var warned) && now - warned < SkewWarnInterval) return;
1040+
_skewWarnedAt[connection.SiteSlug] = now;
1041+
_logger.LogWarning(
1042+
"Site {Slug}: samples from agent {AgentId} arrive stamped {BehindMinutes:0} min behind server time and skip alert evaluation - monitoring data still records, but this site's alerts will not fire. Check the agent host's clock/NTP.",
1043+
connection.SiteSlug, connection.AgentId, (now - sampleTimestamp).TotalMinutes);
1044+
}
1045+
10101046
private static string NormalizeMac(string mac) =>
10111047
string.IsNullOrEmpty(mac) ? string.Empty : mac.ToLowerInvariant().Replace('-', ':');
10121048
}

0 commit comments

Comments
 (0)