diff --git a/examples~/quickstart/client/client.csproj b/examples~/quickstart/client/client.csproj index 93444533..48917cce 100644 --- a/examples~/quickstart/client/client.csproj +++ b/examples~/quickstart/client/client.csproj @@ -3,6 +3,7 @@ Exe net7.0 + false disable enable diff --git a/src/Stats.cs b/src/Stats.cs index bac011d0..7a2224b0 100644 --- a/src/Stats.cs +++ b/src/Stats.cs @@ -7,11 +7,14 @@ namespace SpacetimeDB { public class NetworkRequestTracker { - private readonly ConcurrentQueue<(DateTime End, TimeSpan Duration, string Metadata)> _requestDurations = new(); + private readonly ConcurrentQueue<(DateTime End, (TimeSpan Duration, string Metadata) Request)> _requestDurations = new(); private uint _nextRequestId; private readonly Dictionary _requests = new(); + // Limit the number of request durations we store to prevent memory leaks. + public int KeepLastSeconds = 5 * 60; + internal uint StartTrackingRequest(string metadata = "") { // Record the start time of the request @@ -40,9 +43,25 @@ internal bool FinishTrackingRequest(uint requestId) return true; } + private IEnumerable<(TimeSpan Duration, string Metadata)> GetRequestDurations(int lastSeconds) + { + var cutoff = DateTime.UtcNow.AddSeconds(-lastSeconds); + return _requestDurations.SkipWhile(x => x.End < cutoff).Select(x => x.Request); + } + internal void InsertRequest(TimeSpan duration, string metadata) { - _requestDurations.Enqueue((DateTime.UtcNow, duration, metadata)); + lock (_requestDurations) + { + // Remove expired entries, we need to do this atomically. + var cutoff = DateTime.UtcNow.AddSeconds(-KeepLastSeconds); + var removeCount = _requestDurations.TakeWhile(x => x.End < cutoff).Count(); + for (var i = 0; i < removeCount; i++) + { + _requestDurations.TryDequeue(out _); + } + _requestDurations.Enqueue((DateTime.UtcNow, (duration, metadata))); + } } internal void InsertRequest(DateTime start, string metadata) @@ -52,8 +71,13 @@ internal void InsertRequest(DateTime start, string metadata) public ((TimeSpan Duration, string Metadata) Min, (TimeSpan Duration, string Metadata) Max)? GetMinMaxTimes(int lastSeconds) { + if (lastSeconds > KeepLastSeconds) + { + throw new ArgumentException($"lastSeconds must be less than or equal to KeepLastSeconds = {KeepLastSeconds}", nameof(lastSeconds)); + } + var cutoff = DateTime.UtcNow.AddSeconds(-lastSeconds); - var requestDurations = _requestDurations.Where(x => x.End >= cutoff).Select(x => (x.Duration, x.Metadata)); + var requestDurations = _requestDurations.SkipWhile(x => x.End < cutoff).Select(x => x.Request); if (!requestDurations.Any()) { @@ -74,5 +98,14 @@ public class Stats public readonly NetworkRequestTracker SubscriptionRequestTracker = new(); public readonly NetworkRequestTracker AllReducersTracker = new(); public readonly NetworkRequestTracker ParseMessageTracker = new(); + + public void KeepLastSeconds(int seconds) + { + ReducerRequestTracker.KeepLastSeconds = seconds; + OneOffRequestTracker.KeepLastSeconds = seconds; + SubscriptionRequestTracker.KeepLastSeconds = seconds; + AllReducersTracker.KeepLastSeconds = seconds; + ParseMessageTracker.KeepLastSeconds = seconds; + } } } diff --git a/tests~/VerifyInit.cs b/tests~/VerifyInit.cs index 58d37d65..481e2e24 100644 --- a/tests~/VerifyInit.cs +++ b/tests~/VerifyInit.cs @@ -55,7 +55,7 @@ public override void Write(VerifyJsonWriter writer, NetworkRequestTracker value) } if ( - value.GetMinMaxTimes(int.MaxValue) is + value.GetMinMaxTimes(60) is { Min.Metadata: var Min, Max.Metadata: var Max } ) {