diff --git a/src/NATS.Server/Monitoring/Varz.cs b/src/NATS.Server/Monitoring/Varz.cs index 847bdc2..3e85374 100644 --- a/src/NATS.Server/Monitoring/Varz.cs +++ b/src/NATS.Server/Monitoring/Varz.cs @@ -157,6 +157,12 @@ public sealed class Varz [JsonPropertyName("slow_consumer_stats")] public SlowConsumersStats SlowConsumerStats { get; set; } = new(); + [JsonPropertyName("stale_connections")] + public long StaleConnections { get; set; } + + [JsonPropertyName("stale_connection_stats")] + public StaleConnectionStats StaleConnectionStatsDetail { get; set; } = new(); + [JsonPropertyName("subscriptions")] public uint Subscriptions { get; set; } @@ -219,6 +225,25 @@ public sealed class SlowConsumersStats public ulong Leafs { get; set; } } +/// +/// Statistics about stale connections by connection type. +/// Corresponds to Go server/monitor.go StaleConnectionStats struct. +/// +public sealed class StaleConnectionStats +{ + [JsonPropertyName("clients")] + public ulong Clients { get; set; } + + [JsonPropertyName("routes")] + public ulong Routes { get; set; } + + [JsonPropertyName("gateways")] + public ulong Gateways { get; set; } + + [JsonPropertyName("leafs")] + public ulong Leafs { get; set; } +} + /// /// Cluster configuration monitoring information. /// Corresponds to Go server/monitor.go ClusterOptsVarz struct. diff --git a/src/NATS.Server/Monitoring/VarzHandler.cs b/src/NATS.Server/Monitoring/VarzHandler.cs index c872b28..433aa84 100644 --- a/src/NATS.Server/Monitoring/VarzHandler.cs +++ b/src/NATS.Server/Monitoring/VarzHandler.cs @@ -91,6 +91,14 @@ public sealed class VarzHandler : IDisposable Gateways = (ulong)Interlocked.Read(ref stats.SlowConsumerGateways), Leafs = (ulong)Interlocked.Read(ref stats.SlowConsumerLeafs), }, + StaleConnections = Interlocked.Read(ref stats.StaleConnections), + StaleConnectionStatsDetail = new StaleConnectionStats + { + Clients = (ulong)Interlocked.Read(ref stats.StaleConnectionClients), + Routes = (ulong)Interlocked.Read(ref stats.StaleConnectionRoutes), + Gateways = (ulong)Interlocked.Read(ref stats.StaleConnectionGateways), + Leafs = (ulong)Interlocked.Read(ref stats.StaleConnectionLeafs), + }, Subscriptions = _server.SubList.Count, ConfigLoadTime = _server.StartTime, HttpReqStats = stats.HttpReqStats.ToDictionary(kv => kv.Key, kv => (ulong)kv.Value), diff --git a/src/NATS.Server/NatsClient.cs b/src/NATS.Server/NatsClient.cs index a2bb19e..b568536 100644 --- a/src/NATS.Server/NatsClient.cs +++ b/src/NATS.Server/NatsClient.cs @@ -682,6 +682,12 @@ public sealed class NatsClient : IDisposable break; } + if (reason == ClientClosedReason.StaleConnection) + { + Interlocked.Increment(ref _serverStats.StaleConnections); + Interlocked.Increment(ref _serverStats.StaleConnectionClients); + } + _logger.LogDebug("Client {ClientId} connection closed: {CloseReason}", Id, reason); } diff --git a/src/NATS.Server/ServerStats.cs b/src/NATS.Server/ServerStats.cs index b737dee..21ebd7d 100644 --- a/src/NATS.Server/ServerStats.cs +++ b/src/NATS.Server/ServerStats.cs @@ -16,5 +16,9 @@ public sealed class ServerStats public long SlowConsumerRoutes; public long SlowConsumerLeafs; public long SlowConsumerGateways; + public long StaleConnectionClients; + public long StaleConnectionRoutes; + public long StaleConnectionLeafs; + public long StaleConnectionGateways; public readonly ConcurrentDictionary HttpReqStats = new(); } diff --git a/tests/NATS.Server.Tests/ServerStatsTests.cs b/tests/NATS.Server.Tests/ServerStatsTests.cs index 6b45bd3..7baf061 100644 --- a/tests/NATS.Server.Tests/ServerStatsTests.cs +++ b/tests/NATS.Server.Tests/ServerStatsTests.cs @@ -75,6 +75,27 @@ public class ServerStatsTests : IAsyncLifetime client.StartTime.ShouldNotBe(default); } + [Fact] + public void StaleConnection_stats_incremented_on_mark_closed() + { + var stats = new ServerStats(); + stats.StaleConnectionClients.ShouldBe(0); + + Interlocked.Increment(ref stats.StaleConnectionClients); + stats.StaleConnectionClients.ShouldBe(1); + } + + [Fact] + public void StaleConnection_stats_all_fields_default_to_zero() + { + var stats = new ServerStats(); + stats.StaleConnections.ShouldBe(0); + stats.StaleConnectionClients.ShouldBe(0); + stats.StaleConnectionRoutes.ShouldBe(0); + stats.StaleConnectionLeafs.ShouldBe(0); + stats.StaleConnectionGateways.ShouldBe(0); + } + private static int GetFreePort() { using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);