From 52e48789ae598c66a45434e9ee050185986a168e Mon Sep 17 00:00:00 2001 From: 6076437 Date: Thu, 28 May 2026 15:44:40 +0530 Subject: [PATCH 1/6] updating to flush the each line immediately instead of wait for filling of internal buffer --- src/Shared-parser/StreamProcessor/JsonStreamProcessor.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Shared-parser/StreamProcessor/JsonStreamProcessor.cs b/src/Shared-parser/StreamProcessor/JsonStreamProcessor.cs index 9636e710..f802e9f4 100644 --- a/src/Shared-parser/StreamProcessor/JsonStreamProcessor.cs +++ b/src/Shared-parser/StreamProcessor/JsonStreamProcessor.cs @@ -77,8 +77,9 @@ public override async Task CopyToAsync(System.Net.Http.HttpContent sourceContent { //cancellationToken?.ThrowIfCancellationRequested(); - // Write each line immediately - no delays - Task t = writer.WriteLineAsync(currentLine); + // Write and flush each line immediately so the client receives chunks as they arrive + await writer.WriteLineAsync(currentLine).ConfigureAwait(false); + await writer.FlushAsync().ConfigureAwait(false); // Only process through lines that could have usage in them if (CaptureAllLines) @@ -92,8 +93,6 @@ public override async Task CopyToAsync(System.Net.Http.HttpContent sourceContent currentIndex = (currentIndex + 1) % MaxLines; // Wrap around lineCount++; } - - await t.ConfigureAwait(false); } _logger?.LogDebug("Finished streaming {LineCount} lines from source", lineCount); From d15f27be14f1f86ac7452fa2936743a810a15e7e Mon Sep 17 00:00:00 2001 From: 6076437 Date: Thu, 4 Jun 2026 16:32:00 +0530 Subject: [PATCH 2/6] Updated the streamprocessor to search for usage block from latest to oldest and added logic to capture total latency for streaming req --- .../StreamProcessor/JsonStreamProcessor.cs | 2 +- src/SimpleL7Proxy/Events/EventDataBuilder.cs | 24 ++++++++++++++++++- src/SimpleL7Proxy/Proxy/ProxyWorker.cs | 16 +++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Shared-parser/StreamProcessor/JsonStreamProcessor.cs b/src/Shared-parser/StreamProcessor/JsonStreamProcessor.cs index f802e9f4..7b25c6f1 100644 --- a/src/Shared-parser/StreamProcessor/JsonStreamProcessor.cs +++ b/src/Shared-parser/StreamProcessor/JsonStreamProcessor.cs @@ -158,7 +158,7 @@ public override async Task CopyToAsync(System.Net.Http.HttpContent sourceContent _logger?.LogDebug("Searching for usage and background request patterns in last lines"); // Loop through lines starting from most recent, going backwards - for (int i = 0; i < validLines.Length; i++) + for (int i = validLines.Length - 1; i >= 0; i--) { var line = validLines[i]; if (line.IndexOf("usage", StringComparison.OrdinalIgnoreCase) >= 0) diff --git a/src/SimpleL7Proxy/Events/EventDataBuilder.cs b/src/SimpleL7Proxy/Events/EventDataBuilder.cs index 851ce956..bfc25e7b 100644 --- a/src/SimpleL7Proxy/Events/EventDataBuilder.cs +++ b/src/SimpleL7Proxy/Events/EventDataBuilder.cs @@ -71,9 +71,13 @@ public void PopulateProxyEventData(RequestData request, ProxyData proxyData) eventData["Url"] = request.FullURL; var timeTaken = DateTime.UtcNow - request.EnqueueTime; eventData.Duration = timeTaken; + // TTFB-Latency = time from enqueue to backend response headers received. + // Total-Latency is overwritten later in StampFinalLatency() (called after + // Context.Response.OutputStream.Close()) so it captures the full proxy-side send time. + eventData["TTFB-Latency"] = timeTaken.TotalMilliseconds.ToString("F3"); eventData["Total-Latency"] = timeTaken.TotalMilliseconds.ToString("F3"); eventData["Attempts"] = request.BackendAttempts.ToString(); - + if (proxyData != null) { eventData["Backend-Host"] = !string.IsNullOrEmpty(proxyData.BackendHostname) @@ -141,6 +145,9 @@ public void PopulateHeaderEventData(RequestData request, System.Collections.Spec /// /// Populates final event data with response information and incomplete requests. + /// Total-Latency is intentionally NOT stamped here — it is stamped later in + /// StampFinalLatency(), after the response output stream has been closed, so + /// the value includes the full proxy-side send time. /// public void PopulateFinalEventData(RequestData request, HttpListenerContext? context) { @@ -156,4 +163,19 @@ public void PopulateFinalEventData(RequestData request, HttpListenerContext? con _logger.LogTrace("Populated final event data for request {Guid}", request.Guid); } + + /// + /// Stamps Total-Latency and Duration with the time measured after the response + /// output stream has been fully closed. Call this immediately before Cleanup()/SendEvent() + /// so the event captures the true proxy-side send completion time. + /// + public void StampFinalLatency(RequestData request) + { + var trueLatency = DateTime.UtcNow - request.EnqueueTime; + request.EventData["Total-Latency"] = trueLatency.TotalMilliseconds.ToString("F3"); + request.EventData.Duration = trueLatency; + + _logger.LogTrace("Stamped final Total-Latency {Ms}ms for request {Guid}", + trueLatency.TotalMilliseconds.ToString("F3"), request.Guid); + } } diff --git a/src/SimpleL7Proxy/Proxy/ProxyWorker.cs b/src/SimpleL7Proxy/Proxy/ProxyWorker.cs index f2df4bb0..dfd44d69 100644 --- a/src/SimpleL7Proxy/Proxy/ProxyWorker.cs +++ b/src/SimpleL7Proxy/Proxy/ProxyWorker.cs @@ -555,6 +555,22 @@ public async Task TaskRunnerAsync() if (workerState != "Cleanup") eventData["WorkerState"] = workerState; + // Close the response output stream now so Total-Latency captures + // the full proxy-side send time, including the final TCP flush. + // Stamp AFTER close, BEFORE Cleanup()/SendEvent(). + if (!incomingRequest.AsyncTriggered && incomingRequest.Context != null) + { + try + { + incomingRequest.Context.Response.OutputStream.Close(); + } + catch (Exception) + { + // Stream may already be closed/disposed — safe to ignore. + } + } + _eventDataBuilder.StampFinalLatency(incomingRequest); + incomingRequest.Cleanup(); try From e4fe15e503b6efd2f9eb667db553a389e4323ed9 Mon Sep 17 00:00:00 2001 From: 6076437 Date: Fri, 5 Jun 2026 12:24:57 +0530 Subject: [PATCH 3/6] Decreasing max lines for MultiLineAllUsageProcessor to 10 --- src/Shared-parser/StreamProcessor/MultiAllUsageProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shared-parser/StreamProcessor/MultiAllUsageProcessor.cs b/src/Shared-parser/StreamProcessor/MultiAllUsageProcessor.cs index a837d2a3..219efd9e 100644 --- a/src/Shared-parser/StreamProcessor/MultiAllUsageProcessor.cs +++ b/src/Shared-parser/StreamProcessor/MultiAllUsageProcessor.cs @@ -16,7 +16,7 @@ public class MultiLineAllUsageProcessor : JsonStreamProcessor @"""(?:[uU]sage|[uU]sage[mM]etadata)"":\s*(\{(?:[^{}]|(?\{)|(?<-open>\}))*(?(open)(?!))\})", RegexOptions.Singleline | RegexOptions.Compiled); - protected override int MaxLines => 100; + protected override int MaxLines => 10; protected override int MinLineLength => 1; /// From 4ab96f84e77c8e49ef58d9fe5d9881f435e39c24 Mon Sep 17 00:00:00 2001 From: 6076437 Date: Thu, 11 Jun 2026 14:34:40 +0530 Subject: [PATCH 4/6] Setting the max line to 50 for stream processor --- src/Shared-parser/StreamProcessor/JsonStreamProcessor.cs | 6 +++--- src/Shared-parser/StreamProcessor/MultiAllUsageProcessor.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Shared-parser/StreamProcessor/JsonStreamProcessor.cs b/src/Shared-parser/StreamProcessor/JsonStreamProcessor.cs index 7b25c6f1..9d145133 100644 --- a/src/Shared-parser/StreamProcessor/JsonStreamProcessor.cs +++ b/src/Shared-parser/StreamProcessor/JsonStreamProcessor.cs @@ -77,9 +77,8 @@ public override async Task CopyToAsync(System.Net.Http.HttpContent sourceContent { //cancellationToken?.ThrowIfCancellationRequested(); - // Write and flush each line immediately so the client receives chunks as they arrive - await writer.WriteLineAsync(currentLine).ConfigureAwait(false); - await writer.FlushAsync().ConfigureAwait(false); + // Write each line immediately - no delays + Task t = writer.WriteLineAsync(currentLine); // Only process through lines that could have usage in them if (CaptureAllLines) @@ -93,6 +92,7 @@ public override async Task CopyToAsync(System.Net.Http.HttpContent sourceContent currentIndex = (currentIndex + 1) % MaxLines; // Wrap around lineCount++; } + await t.ConfigureAwait(false); } _logger?.LogDebug("Finished streaming {LineCount} lines from source", lineCount); diff --git a/src/Shared-parser/StreamProcessor/MultiAllUsageProcessor.cs b/src/Shared-parser/StreamProcessor/MultiAllUsageProcessor.cs index 219efd9e..5cebcba0 100644 --- a/src/Shared-parser/StreamProcessor/MultiAllUsageProcessor.cs +++ b/src/Shared-parser/StreamProcessor/MultiAllUsageProcessor.cs @@ -16,7 +16,7 @@ public class MultiLineAllUsageProcessor : JsonStreamProcessor @"""(?:[uU]sage|[uU]sage[mM]etadata)"":\s*(\{(?:[^{}]|(?\{)|(?<-open>\}))*(?(open)(?!))\})", RegexOptions.Singleline | RegexOptions.Compiled); - protected override int MaxLines => 10; + protected override int MaxLines => 50; protected override int MinLineLength => 1; /// From 63c87354f127a084eae57eab764f668906135ea0 Mon Sep 17 00:00:00 2001 From: 6076437 Date: Fri, 12 Jun 2026 12:02:11 +0530 Subject: [PATCH 5/6] Added Lifetime-Attempts, Policy-Cycle-Counter, Lifetime-Policy-Counter to Event Data --- ReleaseNotes/version2.2.md | 3 +++ src/SimpleL7Proxy/Events/EventDataBuilder.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/ReleaseNotes/version2.2.md b/ReleaseNotes/version2.2.md index 59126ed0..f129b62f 100644 --- a/ReleaseNotes/version2.2.md +++ b/ReleaseNotes/version2.2.md @@ -1,5 +1,8 @@ # Release Notes # +2.2.13.2 +Added Lifetime-Attempts, Policy-Cycle-Counter, Lifetime-Policy-Counter to Event Data + 2.2.13.1 Proxy: diff --git a/src/SimpleL7Proxy/Events/EventDataBuilder.cs b/src/SimpleL7Proxy/Events/EventDataBuilder.cs index bfc25e7b..ab0260b1 100644 --- a/src/SimpleL7Proxy/Events/EventDataBuilder.cs +++ b/src/SimpleL7Proxy/Events/EventDataBuilder.cs @@ -77,6 +77,9 @@ public void PopulateProxyEventData(RequestData request, ProxyData proxyData) eventData["TTFB-Latency"] = timeTaken.TotalMilliseconds.ToString("F3"); eventData["Total-Latency"] = timeTaken.TotalMilliseconds.ToString("F3"); eventData["Attempts"] = request.BackendAttempts.ToString(); + eventData["Lifetime-Attempts"] = request.LifetimeBackendAttempts.ToString(); + eventData["Policy-Cycle-Counter"] = request.PolicyCycleCounter.ToString(); + eventData["Lifetime-Policy-Counter"] = request.LifetimePolicyCycleCounter.ToString(); if (proxyData != null) { From c88e3a7638cb816c081bbbcf74671ea79811f6c5 Mon Sep 17 00:00:00 2001 From: 6076437 Date: Wed, 17 Jun 2026 21:40:39 +0530 Subject: [PATCH 6/6] updating the constant file --- src/SimpleL7Proxy/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SimpleL7Proxy/Constants.cs b/src/SimpleL7Proxy/Constants.cs index f639131b..311153ec 100644 --- a/src/SimpleL7Proxy/Constants.cs +++ b/src/SimpleL7Proxy/Constants.cs @@ -15,7 +15,7 @@ public static class Constants public const string Random = "random"; public const string Server = "simplel7proxy"; - public const string VERSION = "2.2.13"; + public const string VERSION = "2.2.13.2"; public const int AnyPriority = -1;