feat: add closed connection tracking, state filtering, ByStop/ByReason sorting
This commit is contained in:
25
src/NATS.Server/Monitoring/ClosedClient.cs
Normal file
25
src/NATS.Server/Monitoring/ClosedClient.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace NATS.Server.Monitoring;
|
||||
|
||||
/// <summary>
|
||||
/// Snapshot of a closed client connection for /connz reporting.
|
||||
/// </summary>
|
||||
public sealed record ClosedClient
|
||||
{
|
||||
public required ulong Cid { get; init; }
|
||||
public string Ip { get; init; } = "";
|
||||
public int Port { get; init; }
|
||||
public DateTime Start { get; init; }
|
||||
public DateTime Stop { get; init; }
|
||||
public string Reason { get; init; } = "";
|
||||
public string Name { get; init; } = "";
|
||||
public string Lang { get; init; } = "";
|
||||
public string Version { get; init; } = "";
|
||||
public long InMsgs { get; init; }
|
||||
public long OutMsgs { get; init; }
|
||||
public long InBytes { get; init; }
|
||||
public long OutBytes { get; init; }
|
||||
public uint NumSubs { get; init; }
|
||||
public TimeSpan Rtt { get; init; }
|
||||
public string TlsVersion { get; init; } = "";
|
||||
public string TlsCipherSuite { get; init; } = "";
|
||||
}
|
||||
@@ -169,6 +169,8 @@ public enum SortOpt
|
||||
ByIdle,
|
||||
ByUptime,
|
||||
ByRtt,
|
||||
ByStop,
|
||||
ByReason,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -12,9 +12,25 @@ public sealed class ConnzHandler(NatsServer server)
|
||||
{
|
||||
var opts = ParseQueryParams(ctx);
|
||||
var now = DateTime.UtcNow;
|
||||
var clients = server.GetClients().ToArray();
|
||||
|
||||
var connInfos = clients.Select(c => BuildConnInfo(c, now, opts)).ToList();
|
||||
var connInfos = new List<ConnInfo>();
|
||||
|
||||
// Collect open connections
|
||||
if (opts.State is ConnState.Open or ConnState.All)
|
||||
{
|
||||
var clients = server.GetClients().ToArray();
|
||||
connInfos.AddRange(clients.Select(c => BuildConnInfo(c, now, opts)));
|
||||
}
|
||||
|
||||
// Collect closed connections
|
||||
if (opts.State is ConnState.Closed or ConnState.All)
|
||||
{
|
||||
connInfos.AddRange(server.GetClosedClients().Select(c => BuildClosedConnInfo(c, now, opts)));
|
||||
}
|
||||
|
||||
// Validate sort options that require closed state
|
||||
if (opts.Sort is SortOpt.ByStop or SortOpt.ByReason && opts.State == ConnState.Open)
|
||||
opts.Sort = SortOpt.ByCid; // Fallback
|
||||
|
||||
// Sort
|
||||
connInfos = opts.Sort switch
|
||||
@@ -30,6 +46,8 @@ public sealed class ConnzHandler(NatsServer server)
|
||||
SortOpt.ByLast => connInfos.OrderByDescending(c => c.LastActivity).ToList(),
|
||||
SortOpt.ByIdle => connInfos.OrderByDescending(c => now - c.LastActivity).ToList(),
|
||||
SortOpt.ByUptime => connInfos.OrderByDescending(c => now - c.Start).ToList(),
|
||||
SortOpt.ByStop => connInfos.OrderByDescending(c => c.Stop ?? DateTime.MinValue).ToList(),
|
||||
SortOpt.ByReason => connInfos.OrderBy(c => c.Reason).ToList(),
|
||||
SortOpt.ByRtt => connInfos.OrderBy(c => c.Rtt).ToList(),
|
||||
_ => connInfos.OrderBy(c => c.Cid).ToList(),
|
||||
};
|
||||
@@ -74,7 +92,7 @@ public sealed class ConnzHandler(NatsServer server)
|
||||
Reason = client.CloseReason.ToReasonString(),
|
||||
TlsVersion = client.TlsState?.TlsVersion ?? "",
|
||||
TlsCipherSuite = client.TlsState?.CipherSuite ?? "",
|
||||
Rtt = client.Rtt == TimeSpan.Zero ? "" : $"{client.Rtt.TotalMilliseconds:F3}ms",
|
||||
Rtt = FormatRtt(client.Rtt),
|
||||
};
|
||||
|
||||
if (opts.Subscriptions)
|
||||
@@ -98,6 +116,35 @@ public sealed class ConnzHandler(NatsServer server)
|
||||
return info;
|
||||
}
|
||||
|
||||
private static ConnInfo BuildClosedConnInfo(ClosedClient closed, DateTime now, ConnzOptions opts)
|
||||
{
|
||||
return new ConnInfo
|
||||
{
|
||||
Cid = closed.Cid,
|
||||
Kind = "Client",
|
||||
Type = "Client",
|
||||
Ip = closed.Ip,
|
||||
Port = closed.Port,
|
||||
Start = closed.Start,
|
||||
Stop = closed.Stop,
|
||||
LastActivity = closed.Stop,
|
||||
Uptime = FormatDuration(closed.Stop - closed.Start),
|
||||
Idle = FormatDuration(now - closed.Stop),
|
||||
InMsgs = closed.InMsgs,
|
||||
OutMsgs = closed.OutMsgs,
|
||||
InBytes = closed.InBytes,
|
||||
OutBytes = closed.OutBytes,
|
||||
NumSubs = closed.NumSubs,
|
||||
Name = closed.Name,
|
||||
Lang = closed.Lang,
|
||||
Version = closed.Version,
|
||||
Reason = closed.Reason,
|
||||
Rtt = FormatRtt(closed.Rtt),
|
||||
TlsVersion = closed.TlsVersion,
|
||||
TlsCipherSuite = closed.TlsCipherSuite,
|
||||
};
|
||||
}
|
||||
|
||||
private static ConnzOptions ParseQueryParams(HttpContext ctx)
|
||||
{
|
||||
var q = ctx.Request.Query;
|
||||
@@ -119,6 +166,8 @@ public sealed class ConnzHandler(NatsServer server)
|
||||
"idle" => SortOpt.ByIdle,
|
||||
"uptime" => SortOpt.ByUptime,
|
||||
"rtt" => SortOpt.ByRtt,
|
||||
"stop" => SortOpt.ByStop,
|
||||
"reason" => SortOpt.ByReason,
|
||||
_ => SortOpt.ByCid,
|
||||
};
|
||||
}
|
||||
@@ -131,6 +180,17 @@ public sealed class ConnzHandler(NatsServer server)
|
||||
opts.Subscriptions = true;
|
||||
}
|
||||
|
||||
if (q.TryGetValue("state", out var state))
|
||||
{
|
||||
opts.State = state.ToString().ToLowerInvariant() switch
|
||||
{
|
||||
"open" => ConnState.Open,
|
||||
"closed" => ConnState.Closed,
|
||||
"all" => ConnState.All,
|
||||
_ => ConnState.Open,
|
||||
};
|
||||
}
|
||||
|
||||
if (q.TryGetValue("offset", out var offset) && int.TryParse(offset, out var o))
|
||||
opts.Offset = o;
|
||||
|
||||
@@ -140,6 +200,16 @@ public sealed class ConnzHandler(NatsServer server)
|
||||
return opts;
|
||||
}
|
||||
|
||||
private static string FormatRtt(TimeSpan rtt)
|
||||
{
|
||||
if (rtt == TimeSpan.Zero) return "";
|
||||
if (rtt.TotalMilliseconds < 1)
|
||||
return $"{rtt.TotalMicroseconds:F3}\u00b5s";
|
||||
if (rtt.TotalSeconds < 1)
|
||||
return $"{rtt.TotalMilliseconds:F3}ms";
|
||||
return $"{rtt.TotalSeconds:F3}s";
|
||||
}
|
||||
|
||||
private static string FormatDuration(TimeSpan ts)
|
||||
{
|
||||
if (ts.TotalDays >= 1)
|
||||
|
||||
Reference in New Issue
Block a user