diff --git a/Changelog.md b/Changelog.md index 42cae2c..1bc70f9 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,7 @@ - Implemented much better mechanism to keep to track of concurrent connections, as well vastly improved the control and execution model of limited max connections parallel pulse. - `-b`, `--batch` is not longer a valid parameter, now the parameter is `-c` (connections) to better reflect the real behavioral effect. +- Implement better mechanism to keep track of content size, now it should require less resources and be available even when `--no-export` is used. ## Version 1.0.3.0 diff --git a/src/Pulse/Core/PulseMonitor.cs b/src/Pulse/Core/PulseMonitor.cs index 9a127ae..71f94dd 100644 --- a/src/Pulse/Core/PulseMonitor.cs +++ b/src/Pulse/Core/PulseMonitor.cs @@ -94,6 +94,7 @@ public async Task SendAsync(int requestId) { internal static async Task SendRequest(int id, Request requestRecipe, HttpClient httpClient, bool saveContent, CancellationToken cancellationToken = default) { HttpStatusCode statusCode = 0; string content = ""; + long contentLength = 0; int currentConcurrencyLevel = 0; StrippedException exception = StrippedException.Default; var headers = Enumerable.Empty>>(); @@ -105,6 +106,10 @@ internal static async Task SendRequest(int id, Request requestRecipe, Interlocked.Decrement(ref _concurrencyLevel); statusCode = response.StatusCode; headers = response.Headers; + var length = response.Content.Headers.ContentLength; + if (length.HasValue) { + contentLength = length.Value; + } if (saveContent) { content = await response.Content.ReadAsStringAsync(cancellationToken); } @@ -125,6 +130,7 @@ internal static async Task SendRequest(int id, Request requestRecipe, StatusCode = statusCode, Headers = headers, Content = content, + ContentLength = contentLength, Duration = duration, Exception = exception, MaximumConcurrencyLevel = currentConcurrencyLevel diff --git a/src/Pulse/Core/PulseSummary.cs b/src/Pulse/Core/PulseSummary.cs index 0510494..f79289c 100644 --- a/src/Pulse/Core/PulseSummary.cs +++ b/src/Pulse/Core/PulseSummary.cs @@ -22,11 +22,6 @@ public sealed class PulseSummary { /// public required Parameters Parameters { get; init; } - /// - /// The character encoding to use - /// - public Encoding CharEncoding { get; init; } = Encoding.Default; - /// /// Produces a summary, and saves unique requests if export is enabled. /// @@ -65,8 +60,7 @@ public sealed class PulseSummary { maxDuration = Math.Max(maxDuration, duration); avgDuration += multiplier * duration; // size - ReadOnlySpan span = result.Content ?? ReadOnlySpan.Empty; - var size = CharEncoding.GetByteCount(span); + var size = result.ContentLength; if (size > 0) { minSize = Math.Min(minSize, size); maxSize = Math.Max(maxSize, size); @@ -124,10 +118,8 @@ public sealed class PulseSummary { public (bool exportRequired, HashSet uniqueRequests) SummarizeSingle() { var result = Result.Results.First(); double duration = result.Duration.TotalMilliseconds; - ReadOnlySpan span = result.Content ?? ReadOnlySpan.Empty; - var size = CharEncoding.GetByteCount(span); - var statusCode = result.StatusCode; + ClearNextLinesError(3); WriteLine("Summary:" * Color.Green); WriteLine(["Request count: ", "1" * Color.Yellow]); @@ -142,7 +134,7 @@ public sealed class PulseSummary { WriteLine(["Success: ", "false" * Color.Red]); } WriteLine(["Request Duration: ", $"{duration:0.##}ms" * Color.Cyan]); - WriteLine(["Content Size: ", Utils.Strings.FormatBytes(size) * Color.Cyan]); + WriteLine(["Content Size: ", Utils.Strings.FormatBytes(result.ContentLength) * Color.Cyan]); if (statusCode is 0) { WriteLine(["Status code: ", "0 [Exception]" * Color.Red]); } else { diff --git a/src/Pulse/Core/Response.cs b/src/Pulse/Core/Response.cs index f3d3b82..f052cb7 100644 --- a/src/Pulse/Core/Response.cs +++ b/src/Pulse/Core/Response.cs @@ -28,6 +28,11 @@ public readonly struct Response { /// public required string Content { get; init; } + /// + /// The response content length + /// + public required long ContentLength { get; init; } + /// /// The time taken from sending the request to receiving the response /// diff --git a/tests/Pulse.Tests.Unit/ExporterTests.cs b/tests/Pulse.Tests.Unit/ExporterTests.cs index 5502927..49b6ba7 100644 --- a/tests/Pulse.Tests.Unit/ExporterTests.cs +++ b/tests/Pulse.Tests.Unit/ExporterTests.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Text; using Pulse.Configuration; @@ -41,10 +42,13 @@ public void Exporter_ToHtmlTable_ContainsAllHeaders() { new("X-Custom-Header", ["value1", "value2"]) }; + const string content = "Hello World"; + var response = new Response { Id = 1337, StatusCode = HttpStatusCode.OK, - Content = "Hello World", + Content = content, + ContentLength = Encoding.Default.GetByteCount(content), Headers = headers, Exception = StrippedException.Default, Duration = TimeSpan.FromSeconds(1), @@ -52,13 +56,13 @@ public void Exporter_ToHtmlTable_ContainsAllHeaders() { }; // Act - var content = Exporter.ToHtmlTable(response.Headers); + var fileContent = Exporter.ToHtmlTable(response.Headers); // Assert foreach (var header in headers) { - content.Should().Contain(header.Key); + fileContent.Should().Contain(header.Key); foreach (var value in header.Value) { - content.Should().Contain(value); + fileContent.Should().Contain(value); } } } @@ -68,10 +72,13 @@ public async Task Exporter_ExportHtmlAsync_CorrectFileName() { // Arrange var dirInfo = Directory.CreateTempSubdirectory(); try { + const string content = "Hello World"; + var response = new Response { Id = 1337, StatusCode = HttpStatusCode.OK, - Content = "Hello World", + Content = content, + ContentLength = Encoding.Default.GetByteCount(content), Headers = [], Exception = StrippedException.Default, Duration = TimeSpan.FromSeconds(1), @@ -100,10 +107,13 @@ public async Task Exporter_ExportHtmlAsync_ContainsAllHeaders() { new("X-Custom-Header", ["value1", "value2"]) }; + const string content = "Hello World"; + var response = new Response { Id = 1337, StatusCode = HttpStatusCode.OK, - Content = "Hello World", + Content = content, + ContentLength = Encoding.Default.GetByteCount(content), Headers = headers, Exception = StrippedException.Default, Duration = TimeSpan.FromSeconds(1), @@ -116,12 +126,12 @@ public async Task Exporter_ExportHtmlAsync_ContainsAllHeaders() { // Assert var file = dirInfo.GetFiles(); file.Length.Should().Be(1, "because 1 file was created"); - var content = File.ReadAllText(file[0].FullName); + var fileContent = File.ReadAllText(file[0].FullName); foreach (var header in headers) { - content.Should().Contain(header.Key); + fileContent.Should().Contain(header.Key); foreach (var value in header.Value) { - content.Should().Contain(value); + fileContent.Should().Contain(value); } } } finally { @@ -134,10 +144,13 @@ public async Task Exporter_ExportHtmlAsync_WithoutException_HasContent() { // Arrange var dirInfo = Directory.CreateTempSubdirectory(); try { + const string content = "Hello World"; + var response = new Response { Id = 1337, StatusCode = HttpStatusCode.OK, - Content = "Hello World", + Content = content, + ContentLength = Encoding.Default.GetByteCount(content), Headers = [], Exception = StrippedException.Default, Duration = TimeSpan.FromSeconds(1), @@ -150,8 +163,8 @@ public async Task Exporter_ExportHtmlAsync_WithoutException_HasContent() { // Assert var file = dirInfo.GetFiles(); file.Length.Should().Be(1, "because 1 file was created"); - var content = File.ReadAllText(file[0].FullName); - content.Should().Contain("Hello World", "because the content is present"); + var fileContent = File.ReadAllText(file[0].FullName); + fileContent.Should().Contain("Hello World", "because the content is present"); } finally { dirInfo.Delete(true); } @@ -162,14 +175,16 @@ public async Task Exporter_ExportHtmlAsync_WithException_HasExceptionAndNoConten // Arrange var dirInfo = Directory.CreateTempSubdirectory(); try { - var exception = new Exception("Test"); + const string content = "Hello World"; + var exception = new StrippedException(nameof(Exception), "test", ""); var response = new Response { Id = 1337, StatusCode = HttpStatusCode.OK, - Content = "Hello World", + Content = content, + ContentLength = Encoding.Default.GetByteCount(content), Headers = [], - Exception = StrippedException.FromException(exception), + Exception = exception, Duration = TimeSpan.FromSeconds(1), MaximumConcurrencyLevel = 1 }; @@ -180,9 +195,9 @@ public async Task Exporter_ExportHtmlAsync_WithException_HasExceptionAndNoConten // Assert var file = dirInfo.GetFiles(); file.Length.Should().Be(1, "because 1 file was created"); - var content = File.ReadAllText(file[0].FullName); - content.Should().NotContain("Hello World", "because the content is not present"); - content.Should().Contain(exception.Message, "because the exception is present"); + var fileContent = File.ReadAllText(file[0].FullName); + fileContent.Should().NotContain("Hello World", "because the content is not present"); + fileContent.Should().Contain(exception.Message, "because the exception is present"); } finally { dirInfo.Delete(true); }