Files
natsdotnet/src/NATS.Server/Auth/ServiceLatencyTracker.cs
T

148 lines
4.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
namespace NATS.Server.Auth;
/// <summary>
/// Tracks service request latency using a sorted list of samples for percentile calculation.
/// Go reference: accounts.go serviceLatency / serviceExportLatencyStats.
/// </summary>
public sealed class ServiceLatencyTracker
{
private readonly Lock _lock = new();
private readonly List<double> _samples = [];
private readonly int _maxSamples;
private long _totalRequests;
/// <summary>
/// Creates a latency tracker with a bounded in-memory sample window.
/// </summary>
/// <param name="maxSamples">Maximum number of latency samples retained for percentile calculations.</param>
public ServiceLatencyTracker(int maxSamples = 10000)
{
_maxSamples = maxSamples;
}
/// <summary>Records a latency sample in milliseconds.</summary>
/// <param name="latencyMs">Observed end-to-end service latency in milliseconds.</param>
public void RecordLatency(double latencyMs)
{
lock (_lock)
{
if (_samples.Count >= _maxSamples)
_samples.RemoveAt(0);
_samples.Add(latencyMs);
_totalRequests++;
}
}
/// <summary>Returns the 50th percentile (median) latency in milliseconds.</summary>
public double GetP50() => GetPercentile(0.50);
/// <summary>Returns the 90th percentile latency in milliseconds.</summary>
public double GetP90() => GetPercentile(0.90);
/// <summary>Returns the 99th percentile latency in milliseconds.</summary>
public double GetP99() => GetPercentile(0.99);
/// <summary>Returns the value at the given percentile (0.01.0) over recorded samples.</summary>
/// <param name="percentile">Percentile fraction between 0.0 and 1.0.</param>
public double GetPercentile(double percentile)
{
lock (_lock)
return ComputePercentile(_samples, percentile);
}
// Must be called under _lock.
private static double ComputePercentile(List<double> samples, double percentile)
{
if (samples.Count == 0)
return 0;
var sorted = new List<double>(samples);
sorted.Sort();
var index = (int)(percentile * (sorted.Count - 1));
return sorted[index];
}
// Must be called under _lock.
private static double ComputeAverage(List<double> samples)
{
if (samples.Count == 0)
return 0;
var sum = 0.0;
foreach (var s in samples)
sum += s;
return sum / samples.Count;
}
/// <summary>Total number of latency observations recorded.</summary>
public long TotalRequests
{
get { lock (_lock) return _totalRequests; }
}
/// <summary>Arithmetic mean latency across currently retained samples.</summary>
public double AverageLatencyMs
{
get { lock (_lock) return ComputeAverage(_samples); }
}
/// <summary>Minimum latency among currently retained samples.</summary>
public double MinLatencyMs
{
get
{
lock (_lock)
return _samples.Count == 0 ? 0 : _samples.Min();
}
}
/// <summary>Maximum latency among currently retained samples.</summary>
public double MaxLatencyMs
{
get
{
lock (_lock)
return _samples.Count == 0 ? 0 : _samples.Max();
}
}
/// <summary>Number of samples currently retained in memory.</summary>
public int SampleCount
{
get { lock (_lock) return _samples.Count; }
}
/// <summary>Clears all samples and resets the total request counter.</summary>
public void Reset()
{
lock (_lock)
{
_samples.Clear();
_totalRequests = 0;
}
}
/// <summary>Returns an immutable snapshot of the current tracker state.</summary>
public ServiceLatencySnapshot GetSnapshot()
{
lock (_lock)
{
return new ServiceLatencySnapshot(
TotalRequests: _totalRequests,
P50Ms: ComputePercentile(_samples, 0.50),
P90Ms: ComputePercentile(_samples, 0.90),
P99Ms: ComputePercentile(_samples, 0.99),
AverageMs: ComputeAverage(_samples),
MinMs: _samples.Count == 0 ? 0 : _samples.Min(),
MaxMs: _samples.Count == 0 ? 0 : _samples.Max(),
SampleCount: _samples.Count);
}
}
}
public sealed record ServiceLatencySnapshot(
long TotalRequests,
double P50Ms,
double P90Ms,
double P99Ms,
double AverageMs,
double MinMs,
double MaxMs,
int SampleCount);