HTTP stress test improvements (#42313)
authorMarie Píchová <11718369+ManickaP@users.noreply.github.com>
Wed, 23 Sep 2020 15:41:26 +0000 (17:41 +0200)
committerGitHub <noreply@github.com>
Wed, 23 Sep 2020 15:41:26 +0000 (17:41 +0200)
Fixes:
    stress client double read of content fixed
    fixed stress client hangs at start and stop
    leveraged HttpVersionPolicy
    increased pipeline timeout since we doubled the runs
    fixed base docker images to avoid missing IO.Pipelines Kestrel exception.

Re-hauled tracing:
    added server file logging
    added log file rotation

Minor renames.

Contributes to: #42211 and #42198

14 files changed:
eng/docker/libraries-sdk-aspnetcore.linux.Dockerfile
eng/docker/libraries-sdk-aspnetcore.windows.Dockerfile
eng/docker/libraries-sdk.linux.Dockerfile
eng/docker/libraries-sdk.windows.Dockerfile
eng/pipelines/libraries/stress/http.yml
src/libraries/System.Net.Http/tests/StressTests/HttpStress/ClientOperations.cs
src/libraries/System.Net.Http/tests/StressTests/HttpStress/Configuration.cs
src/libraries/System.Net.Http/tests/StressTests/HttpStress/Dockerfile
src/libraries/System.Net.Http/tests/StressTests/HttpStress/HttpEventListener.cs
src/libraries/System.Net.Http/tests/StressTests/HttpStress/HttpStress.csproj
src/libraries/System.Net.Http/tests/StressTests/HttpStress/Program.cs
src/libraries/System.Net.Http/tests/StressTests/HttpStress/StressClient.cs
src/libraries/System.Net.Http/tests/StressTests/HttpStress/StressServer.cs
src/libraries/System.Net.Http/tests/StressTests/HttpStress/windows.Dockerfile

index 064ca05..b0bf080 100644 (file)
@@ -1,6 +1,6 @@
 # Builds and copies library artifacts into target dotnet sdk image
 ARG BUILD_BASE_IMAGE=mcr.microsoft.com/dotnet-buildtools/prereqs:centos-7-f39df28-20191023143754
-ARG SDK_BASE_IMAGE=mcr.microsoft.com/dotnet/sdk:5.0-buster-slim
+ARG SDK_BASE_IMAGE=mcr.microsoft.com/dotnet/nightly/sdk:5.0-buster-slim
 
 FROM $BUILD_BASE_IMAGE as corefxbuild
 
index b5355f5..6e86046 100644 (file)
@@ -1,6 +1,6 @@
 # escape=`
 # Simple Dockerfile which copies library build artifacts into target dotnet sdk image
-ARG SDK_BASE_IMAGE=mcr.microsoft.com/dotnet/sdk:5.0-nanoserver-1809
+ARG SDK_BASE_IMAGE=mcr.microsoft.com/dotnet/nightly/sdk:5.0-nanoserver-1809
 FROM $SDK_BASE_IMAGE as target
 
 ARG TESTHOST_LOCATION=".\\artifacts\\bin\\testhost"
index 6da12f9..4f1cb51 100644 (file)
@@ -1,6 +1,6 @@
 # Builds and copies library artifacts into target dotnet sdk image
 ARG BUILD_BASE_IMAGE=mcr.microsoft.com/dotnet-buildtools/prereqs:centos-7-f39df28-20191023143754
-ARG SDK_BASE_IMAGE=mcr.microsoft.com/dotnet/sdk:5.0-buster-slim
+ARG SDK_BASE_IMAGE=mcr.microsoft.com/dotnet/nightly/sdk:5.0-buster-slim
 
 FROM $BUILD_BASE_IMAGE as corefxbuild
 
index 4c498e1..e88f52d 100644 (file)
@@ -1,6 +1,6 @@
 # escape=`
 # Simple Dockerfile which copies clr and library build artifacts into target dotnet sdk image
-ARG SDK_BASE_IMAGE=mcr.microsoft.com/dotnet/sdk:5.0-nanoserver-1809
+ARG SDK_BASE_IMAGE=mcr.microsoft.com/dotnet/nightly/sdk:5.0-nanoserver-1809
 FROM $SDK_BASE_IMAGE as target
 
 ARG TESTHOST_LOCATION=".\\artifacts\\bin\\testhost"
index 80f0e12..429e7b5 100644 (file)
@@ -25,7 +25,7 @@ variables:
 jobs:
 - job: linux
   displayName: Docker Linux
-  timeoutInMinutes: 120
+  timeoutInMinutes: 150
   pool:
     name: NetCorePublic-Pool
     queue: BuildPool.Ubuntu.1604.Amd64.Open
@@ -65,7 +65,7 @@ jobs:
 
 - job: windows
   displayName: Docker NanoServer
-  timeoutInMinutes: 120
+  timeoutInMinutes: 150
   pool:
     name: NetCorePublic-Pool
     queue: BuildPool.Server.Amd64.VS2019.Open
index f80d0c4..d9fca39 100644 (file)
@@ -216,19 +216,19 @@ namespace HttpStress
                     ctx.PopulateWithRandomHeaders(req.Headers);
                     ulong expectedChecksum = CRC.CalculateHeaderCrc(req.Headers.Select(x => (x.Key, x.Value)));
 
-                    using HttpResponseMessage res = await ctx.SendAsync(req);
+                    using HttpResponseMessage m = await ctx.SendAsync(req);
 
-                    ValidateStatusCode(res);
+                    ValidateStatusCode(m);
 
-                    await res.Content.ReadAsStringAsync();
+                    await m.Content.ReadAsStringAsync();
 
-                    bool isValidChecksum = ValidateServerChecksum(res.Headers, expectedChecksum);
+                    bool isValidChecksum = ValidateServerChecksum(m.Headers, expectedChecksum);
                     string failureDetails = isValidChecksum ? "server checksum matches client checksum" : "server checksum mismatch";
 
                     // Validate that request headers are being echoed
                     foreach (KeyValuePair<string, IEnumerable<string>> reqHeader in req.Headers)
                     {
-                        if (!res.Headers.TryGetValues(reqHeader.Key, out IEnumerable<string>? values))
+                        if (!m.Headers.TryGetValues(reqHeader.Key, out IEnumerable<string>? values))
                         {
                             throw new Exception($"Expected response header name {reqHeader.Key} missing. {failureDetails}");
                         }
@@ -239,11 +239,11 @@ namespace HttpStress
                     }
 
                     // Validate trailing headers are being echoed
-                    if (res.TrailingHeaders.Count() > 0)
+                    if (m.TrailingHeaders.Count() > 0)
                     {
                         foreach (KeyValuePair<string, IEnumerable<string>> reqHeader in req.Headers)
                         {
-                            if (!res.TrailingHeaders.TryGetValues(reqHeader.Key + "-trailer", out IEnumerable<string>? values))
+                            if (!m.TrailingHeaders.TryGetValues(reqHeader.Key + "-trailer", out IEnumerable<string>? values))
                             {
                                 throw new Exception($"Expected trailing header name {reqHeader.Key}-trailer missing. {failureDetails}");
                             }
@@ -330,6 +330,7 @@ namespace HttpStress
                     using HttpResponseMessage m = await ctx.SendAsync(req);
 
                     ValidateStatusCode(m);
+
                     string checksumMessage = ValidateServerChecksum(m.Headers, checksum) ? "server checksum matches client checksum" : "server checksum mismatch";
                     ValidateContent(content, await m.Content.ReadAsStringAsync(), checksumMessage);
                 }),
@@ -344,6 +345,7 @@ namespace HttpStress
                     using HttpResponseMessage m = await ctx.SendAsync(req);
 
                     ValidateStatusCode(m);
+
                     string checksumMessage = ValidateServerChecksum(m.Headers, checksum) ? "server checksum matches client checksum" : "server checksum mismatch";
                     ValidateContent(formData.expected, await m.Content.ReadAsStringAsync(), checksumMessage);
                 }),
@@ -361,7 +363,7 @@ namespace HttpStress
                     string response = await m.Content.ReadAsStringAsync();
 
                     string checksumMessage = ValidateServerChecksum(m.TrailingHeaders, checksum, required: false) ? "server checksum matches client checksum" : "server checksum mismatch";
-                    ValidateContent(content, await m.Content.ReadAsStringAsync(), checksumMessage);
+                    ValidateContent(content, response, checksumMessage);
                 }),
 
                 ("POST Duplex Slow",
@@ -414,6 +416,7 @@ namespace HttpStress
                     using HttpResponseMessage m = await ctx.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
 
                     ValidateStatusCode(m);
+
                     string checksumMessage = ValidateServerChecksum(m.Headers, checksum) ? "server checksum matches client checksum" : "server checksum mismatch";
                     ValidateContent(content, await m.Content.ReadAsStringAsync(), checksumMessage);
                 }),
@@ -431,8 +434,8 @@ namespace HttpStress
                     {
                         throw new Exception($"Expected {expectedLength}, got {m.Content.Headers.ContentLength}");
                     }
-                    string r = await m.Content.ReadAsStringAsync();
-                    if (r.Length > 0) throw new Exception($"Got unexpected response: {r}");
+                    string response = await m.Content.ReadAsStringAsync();
+                    if (response.Length > 0) throw new Exception($"Got unexpected response: {response}");
                 }),
 
                 ("PUT",
@@ -445,8 +448,8 @@ namespace HttpStress
 
                     ValidateStatusCode(m);
 
-                    string r = await m.Content.ReadAsStringAsync();
-                    if (r != "") throw new Exception($"Got unexpected response: {r}");
+                    string response = await m.Content.ReadAsStringAsync();
+                    if (response != "") throw new Exception($"Got unexpected response: {response}");
                 }),
 
                 ("PUT Slow",
@@ -459,8 +462,8 @@ namespace HttpStress
 
                     ValidateStatusCode(m);
 
-                    string r = await m.Content.ReadAsStringAsync();
-                    if (r != "") throw new Exception($"Got unexpected response: {r}");
+                    string response = await m.Content.ReadAsStringAsync();
+                    if (response != "") throw new Exception($"Got unexpected response: {response}");
                 }),
 
                 ("GET Slow",
index d7ec129..baba7d9 100644 (file)
@@ -43,8 +43,8 @@ namespace HttpStress
         public double CancellationProbability { get; set; }
 
         public bool UseHttpSys { get; set; }
-        public string? LogPath { get; set; }
         public bool LogAspNet { get; set; }
+        public bool Trace { get; set; }
         public int? ServerMaxConcurrentStreams { get; set; }
         public int? ServerMaxFrameSize { get; set; }
         public int? ServerInitialConnectionWindowSize { get; set; }
index 5c16f4b..00b1dd4 100644 (file)
@@ -1,4 +1,4 @@
-ARG SDK_BASE_IMAGE=mcr.microsoft.com/dotnet/sdk:5.0-buster-slim
+ARG SDK_BASE_IMAGE=mcr.microsoft.com/dotnet/nightly/sdk:5.0-buster-slim
 FROM $SDK_BASE_IMAGE
 
 RUN echo "DOTNET_SDK_VERSION="$DOTNET_SDK_VERSION
index d9bcc3a..2e52a43 100644 (file)
@@ -3,18 +3,37 @@
 
 using System;
 using System.Diagnostics.Tracing;
+using System.Threading.Channels;
 using System.Text;
 using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
 
 namespace HttpStress
 {
     public sealed class LogHttpEventListener : EventListener
     {
-        private readonly StreamWriter _log;
+        private int _lastLogNumber = 0;
+        private StreamWriter _log;
+        private Channel<string> _messagesChannel = Channel.CreateUnbounded<string>();
+        private Task _processMessages;
+        private CancellationTokenSource _stopProcessing;
 
-        public LogHttpEventListener(string logPath)
+        public LogHttpEventListener()
         {
-            _log = new StreamWriter(logPath, true) { AutoFlush = true };
+            foreach (var filename in Directory.GetFiles(".", "client*.log"))
+            {
+                try
+                {
+                    File.Delete(filename);
+                } catch {}
+            }
+            _log = new StreamWriter("client.log", false) { AutoFlush = true };
+
+            _messagesChannel = Channel.CreateUnbounded<string>();
+            _processMessages = ProcessMessagesAsync();
+            _stopProcessing = new CancellationTokenSource();
         }
 
         protected override void OnEventSourceCreated(EventSource eventSource)
@@ -25,63 +44,63 @@ namespace HttpStress
             }
         }
 
-        protected override void OnEventWritten(EventWrittenEventArgs eventData)
+        private async Task ProcessMessagesAsync()
         {
-            lock (_log)
+            await Task.Yield();
+
+            try
             {
-                var sb = new StringBuilder().Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] ");
-                for (int i = 0; i < eventData.Payload?.Count; i++)
+                int i = 0;
+                await foreach (string message in _messagesChannel.Reader.ReadAllAsync(_stopProcessing.Token))
                 {
-                    if (i > 0)
+                    if ((++i % 10_000) == 0)
                     {
-                        sb.Append(", ");
+                        RotateFiles();
                     }
-                    sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]);
+
+                    _log.WriteLine(message);
                 }
-                _log.WriteLine(sb.ToString());
             }
-        }
+            catch (OperationCanceledException)
+            {
+                return;
+            }
 
-        public override void Dispose()
-        {
-            _log.Dispose();
-            base.Dispose();
+            void RotateFiles()
+            {
+                // Rotate the log if it reaches 50 MB size.
+                if (_log.BaseStream.Length > (50 << 20))
+                {
+                    _log.Close();
+                    _log = new StreamWriter($"client_{++_lastLogNumber:000}.log", false) { AutoFlush = true };
+                }
+            }
         }
-    }
-
-    public sealed class ConsoleHttpEventListener : EventListener
-    {
-        public ConsoleHttpEventListener()
-        { }
 
-        protected override void OnEventSourceCreated(EventSource eventSource)
+        protected override async void OnEventWritten(EventWrittenEventArgs eventData)
         {
-            if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http")
+            var sb = new StringBuilder().Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] ");
+            for (int i = 0; i < eventData.Payload?.Count; i++)
             {
-                EnableEvents(eventSource, EventLevel.LogAlways);
+                if (i > 0)
+                {
+                    sb.Append(", ");
+                }
+                sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]);
             }
+            await _messagesChannel.Writer.WriteAsync(sb.ToString());
         }
 
-        protected override void OnEventWritten(EventWrittenEventArgs eventData)
+        public override void Dispose()
         {
-            lock (Console.Out)
+            base.Dispose();
+
+            if (!_processMessages.Wait(TimeSpan.FromSeconds(30)))
             {
-                Console.ForegroundColor = ConsoleColor.DarkYellow;
-                Console.Write($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] ");
-                Console.ResetColor();
-                for (int i = 0; i < eventData.Payload?.Count; i++)
-                {
-                    if (i > 0)
-                    {
-                        Console.Write(", ");
-                    }
-                    Console.ForegroundColor = ConsoleColor.DarkGray;
-                    Console.Write(eventData.PayloadNames?[i] + ": ");
-                    Console.ResetColor();
-                    Console.Write(eventData.Payload[i]);
-                }
-                Console.WriteLine();
+                _stopProcessing.Cancel();
+                _processMessages.Wait();
             }
+            _log.Dispose();
         }
     }
 }
index b107d5b..57cbb08 100644 (file)
@@ -8,6 +8,9 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
+    <PackageReference Include="Serilog.Extensions.Logging.File" Version="2.0.0" />
+    <PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
     <PackageReference Include="System.CommandLine.Experimental" Version="0.3.0-alpha.19577.1" />
     <PackageReference Include="System.Net.Http.WinHttpHandler" Version="4.5.4" />
   </ItemGroup>
index 7496838..70d2cdb 100644 (file)
@@ -18,7 +18,6 @@ using HttpStress;
 /// </summary>
 public static class Program
 {
-
     public enum ExitCode { Success = 0, StressError = 1, CliError = 2 };
 
     public static async Task<int> Main(string[] args)
@@ -46,7 +45,7 @@ public static class Program
         cmd.AddOption(new Option("-connectionLifetime", "Max connection lifetime length (milliseconds).") { Argument = new Argument<int?>("connectionLifetime", null) });
         cmd.AddOption(new Option("-ops", "Indices of the operations to use") { Argument = new Argument<int[]?>("space-delimited indices", null) });
         cmd.AddOption(new Option("-xops", "Indices of the operations to exclude") { Argument = new Argument<int[]?>("space-delimited indices", null) });
-        cmd.AddOption(new Option("-trace", "Enable System.Net.Http.InternalDiagnostics tracing.") { Argument = new Argument<string>("\"console\" or path") });
+        cmd.AddOption(new Option("-trace", "Enable System.Net.Http.InternalDiagnostics (client) and/or ASP.NET dignostics (server) tracing.") { Argument = new Argument<bool>("enable", false) });
         cmd.AddOption(new Option("-aspnetlog", "Enable ASP.NET warning and error logging.") { Argument = new Argument<bool>("enable", false) });
         cmd.AddOption(new Option("-listOps", "List available options.") { Argument = new Argument<bool>("enable", false) });
         cmd.AddOption(new Option("-seed", "Seed for generating pseudo-random parameters for a given -n argument.") { Argument = new Argument<int?>("seed", null) });
@@ -99,7 +98,7 @@ public static class Program
 
             UseHttpSys = cmdline.ValueForOption<bool>("-httpSys"),
             LogAspNet = cmdline.ValueForOption<bool>("-aspnetlog"),
-            LogPath = cmdline.HasOption("-trace") ? cmdline.ValueForOption<string>("-trace") : null,
+            Trace = cmdline.ValueForOption<bool>("-trace"),
             ServerMaxConcurrentStreams = cmdline.ValueForOption<int?>("-serverMaxConcurrentStreams"),
             ServerMaxFrameSize = cmdline.ValueForOption<int?>("-serverMaxFrameSize"),
             ServerInitialConnectionWindowSize = cmdline.ValueForOption<int?>("-serverInitialConnectionWindowSize"),
@@ -158,11 +157,12 @@ public static class Program
         Console.WriteLine(" System.Net.Http: " + GetAssemblyInfo(typeof(System.Net.Http.HttpClient).Assembly));
         Console.WriteLine("          Server: " + (config.UseHttpSys ? "http.sys" : "Kestrel"));
         Console.WriteLine("      Server URL: " + config.ServerUri);
-        Console.WriteLine("         Tracing: " + (config.LogPath == null ? (object)false : config.LogPath.Length == 0 ? (object)true : config.LogPath));
+        Console.WriteLine("  Client Tracing: " + (config.Trace && config.RunMode.HasFlag(RunMode.client) ? "ON (client.log)" : "OFF"));
+        Console.WriteLine("  Server Tracing: " + (config.Trace && config.RunMode.HasFlag(RunMode.server) ? "ON (server.log)" : "OFF"));
         Console.WriteLine("     ASP.NET Log: " + config.LogAspNet);
         Console.WriteLine("     Concurrency: " + config.ConcurrentRequests);
         Console.WriteLine("  Content Length: " + config.MaxContentLength);
-        Console.WriteLine("   HTTP2 Version: " + config.HttpVersion);
+        Console.WriteLine("    HTTP Version: " + config.HttpVersion);
         Console.WriteLine("        Lifetime: " + (config.ConnectionLifetime.HasValue ? $"{config.ConnectionLifetime.Value.TotalMilliseconds}ms" : "(infinite)"));
         Console.WriteLine("      Operations: " + string.Join(", ", usedClientOperations.Select(o => o.name)));
         Console.WriteLine("     Random Seed: " + config.RandomSeed);
index 60286d4..17db811 100644 (file)
@@ -19,8 +19,6 @@ namespace HttpStress
 {
     public class StressClient : IDisposable
     {
-        private const string UNENCRYPTED_HTTP2_ENV_VAR = "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2UNENCRYPTEDSUPPORT";
-
         private readonly (string name, Func<RequestContext, Task> operation)[] _clientOperations;
         private readonly Uri _baseAddress;
         private readonly Configuration _config;
@@ -40,12 +38,40 @@ namespace HttpStress
             _aggregator = new StressResultAggregator(clientOperations);
 
             // Handle command-line arguments.
-            _eventListener =
-                configuration.LogPath == null ?
-                null :
-                (configuration.LogPath == "console" ? 
-                    (EventListener)new ConsoleHttpEventListener() :
-                    (EventListener)new LogHttpEventListener(configuration.LogPath));
+            _eventListener = configuration.Trace ? new LogHttpEventListener() : null;
+        }
+
+        private HttpClient CreateHttpClient()
+        {
+            HttpMessageHandler CreateHttpHandler()
+            {
+                if (_config.UseWinHttpHandler)
+                {
+                    return new System.Net.Http.WinHttpHandler()
+                    {
+                        ServerCertificateValidationCallback = delegate { return true; }
+                    };
+                }
+                else
+                {
+                    return new SocketsHttpHandler()
+                    {
+                        PooledConnectionLifetime = _config.ConnectionLifetime.GetValueOrDefault(Timeout.InfiniteTimeSpan),
+                        SslOptions = new SslClientAuthenticationOptions
+                        {
+                            RemoteCertificateValidationCallback = delegate { return true; }
+                        }
+                    };
+                }
+            }
+
+            return new HttpClient(CreateHttpHandler()) 
+            { 
+                BaseAddress = _baseAddress,
+                Timeout = _config.DefaultTimeout,
+                DefaultRequestVersion = _config.HttpVersion,
+                DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact
+            };
         }
 
         public void Start()
@@ -61,6 +87,7 @@ namespace HttpStress
                     throw new InvalidOperationException("Stress client already running");
                 }
 
+                InitializeClient().Wait();
                 _stopwatch.Start();
                 _clientTask = StartCore();
             }
@@ -69,14 +96,21 @@ namespace HttpStress
         public void Stop()
         {
             _cts.Cancel();
-            _clientTask?.Wait();
+            for (int i = 0; i < 60; ++i)
+            {
+                if (_clientTask == null || _clientTask.Wait(TimeSpan.FromSeconds(1)))
+                {
+                    break;
+                }
+                Console.WriteLine("Client is stopping ...");
+            }
             _stopwatch.Stop();
             _cts.Dispose();
         }
 
         public void PrintFinalReport()
         {
-            lock(Console.Out)
+            lock (Console.Out)
             {
                 Console.ForegroundColor = ConsoleColor.Magenta;
                 Console.WriteLine("HttpStress Run Final Report");
@@ -94,53 +128,45 @@ namespace HttpStress
             _eventListener?.Dispose();
         }
 
-        private async Task StartCore()
+        private async Task InitializeClient()
         {
-            if (_baseAddress.Scheme == "http")
-            {
-                Environment.SetEnvironmentVariable(UNENCRYPTED_HTTP2_ENV_VAR, "1");
-            }
+            Console.WriteLine($"Trying connect to the server {_baseAddress}.");
 
-            HttpMessageHandler CreateHttpHandler()
+            // Before starting the full-blown test, make sure can communicate with the server
+            // Needed for scenaria where we're deploying server & client in separate containers, simultaneously.
+            await SendTestRequestToServer(maxRetries: 10);
+
+            Console.WriteLine($"Connected successfully.");
+
+            async Task SendTestRequestToServer(int maxRetries)
             {
-                if (_config.UseWinHttpHandler)
+                using HttpClient client = CreateHttpClient();
+                client.Timeout = TimeSpan.FromSeconds(5);
+                for (int remainingRetries = maxRetries; ; remainingRetries--)
                 {
-                    return new System.Net.Http.WinHttpHandler()
+                    var sw = Stopwatch.StartNew();
+                    try
                     {
-                        ServerCertificateValidationCallback = delegate { return true; }
-                    };
-                }
-                else
-                {
-                    return new SocketsHttpHandler()
+                        await client.GetAsync("/");
+                        break;
+                    }
+                    catch (HttpRequestException) when (remainingRetries > 0)
                     {
-                        PooledConnectionLifetime = _config.ConnectionLifetime.GetValueOrDefault(Timeout.InfiniteTimeSpan),
-                        SslOptions = new SslClientAuthenticationOptions
+                        Console.WriteLine($"Stress client could not connect to host {_baseAddress}, {remainingRetries} attempts remaining");
+                        var delay = TimeSpan.FromSeconds(1) - sw.Elapsed;
+                        if (delay > TimeSpan.Zero)
                         {
-                            RemoteCertificateValidationCallback = delegate { return true; }
+                            await Task.Delay(delay);
                         }
-                    };
+                    }
                 }
             }
+        }
 
-            HttpClient CreateHttpClient() => 
-                new HttpClient(CreateHttpHandler()) 
-                { 
-                    BaseAddress = _baseAddress,
-                    Timeout = _config.DefaultTimeout,
-                    DefaultRequestVersion = _config.HttpVersion,
-                };
-
+        private async Task StartCore()
+        {
             using HttpClient client = CreateHttpClient();
 
-            Console.WriteLine($"Trying connect to the server {_baseAddress}.");
-
-            // Before starting the full-blown test, make sure can communicate with the server
-            // Needed for scenaria where we're deploying server & client in separate containers, simultaneously.
-            await SendTestRequestToServer(maxRetries: 10);
-
-            Console.WriteLine($"Connected succesfully.");
-
             // Spin up a thread dedicated to outputting stats for each defined interval
             new Thread(() =>
             {
@@ -196,24 +222,6 @@ namespace HttpStress
                     return ((int)rol5 + h1) ^ h2;
                 }
             }
-            
-            async Task SendTestRequestToServer(int maxRetries)
-            {
-                using HttpClient client = CreateHttpClient();
-                for (int remainingRetries = maxRetries; ; remainingRetries--)
-                {
-                    try
-                    {
-                        await client.GetAsync("/");
-                        break;
-                    }
-                    catch (HttpRequestException) when (remainingRetries > 0)
-                    {
-                        Console.WriteLine($"Stress client could not connect to host {_baseAddress}, {remainingRetries} attempts remaining");
-                        await Task.Delay(millisecondsDelay: 1000);
-                    }
-                }
-            }
         }
 
         /// <summary>Aggregate view of a particular stress failure type</summary>
index c9401e3..91361c1 100644 (file)
@@ -26,6 +26,7 @@ using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Primitives;
 using Microsoft.AspNetCore.Server.Kestrel.Core;
+using Serilog;
 
 namespace HttpStress
 {
@@ -34,7 +35,6 @@ namespace HttpStress
         // Header indicating expected response content length to be returned by the server
         public const string ExpectedResponseContentLength = "Expected-Response-Content-Length";
 
-        private EventListener? _eventListener;
         private readonly IWebHost _webHost;
 
         public string ServerUri { get; }
@@ -120,9 +120,33 @@ namespace HttpStress
                 });
             };
 
-            // Output only warnings and errors from Kestrel
+            LoggerConfiguration loggerConfiguration = new LoggerConfiguration();
+            if (configuration.Trace)
+            {
+                // Clear existing logs first.                
+                foreach (var filename in Directory.GetFiles(".", "server*.log"))
+                {
+                    try
+                    {
+                        File.Delete(filename);
+                    } catch {}
+                }
+
+                loggerConfiguration = loggerConfiguration
+                    // Output diagnostics to the file
+                    .WriteTo.File("server.log", fileSizeLimitBytes: 50 << 20, rollOnFileSizeLimit: true)
+                    .MinimumLevel.Debug();
+            }
+            if (configuration.LogAspNet)
+            {
+                loggerConfiguration = loggerConfiguration
+                    // Output only warnings and errors
+                    .WriteTo.Console(Serilog.Events.LogEventLevel.Warning);
+            }
+            Log.Logger = loggerConfiguration.CreateLogger();
+
             host = host
-                .ConfigureLogging(log => log.AddFilter("Microsoft.AspNetCore", level => configuration.LogAspNet ? level >= LogLevel.Warning : false))
+                .UseSerilog()
                 // Set up how each request should be handled by the server.
                 .Configure(app =>
                 {
@@ -130,22 +154,14 @@ namespace HttpStress
                     app.UseEndpoints(MapRoutes);
                 });
 
-            // Handle command-line arguments.
-            _eventListener =
-                configuration.LogPath == null ?
-                null :
-                (configuration.LogPath == "console" ? 
-                    (EventListener)new ConsoleHttpEventListener() :
-                    (EventListener)new LogHttpEventListener(configuration.LogPath));
-
-            SetUpJustInTimeLogging();
-
             _webHost = host.Build();
             _webHost.Start();
         }
 
         private static void MapRoutes(IEndpointRouteBuilder endpoints)
         {
+            var loggerFactory = endpoints.ServiceProvider.GetService<ILoggerFactory>();
+            var logger = loggerFactory.CreateLogger<StressServer>();
             var head = new[] { "HEAD" };
 
             endpoints.MapGet("/", async context =>
@@ -299,29 +315,6 @@ namespace HttpStress
         public void Dispose()
         {
             _webHost.Dispose();
-            _eventListener?.Dispose();
-        }
-
-        private void SetUpJustInTimeLogging()
-        {
-            if (_eventListener == null && !Console.IsInputRedirected)
-            {
-                // If no command-line requested logging, enable the user to press 'L' to enable logging to the console
-                // during execution, so that it can be done just-in-time when something goes awry.
-                new Thread(() =>
-                {
-                    while (true)
-                    {
-                        if (Console.ReadKey(intercept: true).Key == ConsoleKey.L)
-                        {
-                            Console.WriteLine("Enabling console event logger");
-                            _eventListener = new ConsoleHttpEventListener();
-                            break;
-                        }
-                    }
-                })
-                { IsBackground = true }.Start();
-            }
         }
 
         private static (string scheme, string hostname, int port) ParseServerUri(string serverUri)
index 5e8160e..43f1da1 100644 (file)
@@ -1,5 +1,5 @@
 # escape=`
-ARG SDK_BASE_IMAGE=mcr.microsoft.com/dotnet/sdk:5.0-nanoserver-1809
+ARG SDK_BASE_IMAGE=mcr.microsoft.com/dotnet/nightly/sdk:5.0-nanoserver-1809
 FROM $SDK_BASE_IMAGE
 
 # Use powershell as the default shell