--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Diagnostics.Tracing;
+using System.Text;
+using System.IO;
+
+namespace HttpStress
+{
+ public sealed class LogHttpEventListener : EventListener
+ {
+ private readonly StreamWriter _log;
+
+ public LogHttpEventListener(string logPath)
+ {
+ _log = new StreamWriter(logPath, true) { AutoFlush = true };
+ }
+
+ protected override void OnEventSourceCreated(EventSource eventSource)
+ {
+ if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http")
+ {
+ EnableEvents(eventSource, EventLevel.LogAlways);
+ }
+ }
+
+ protected override void OnEventWritten(EventWrittenEventArgs eventData)
+ {
+ lock (_log)
+ {
+ var sb = new StringBuilder().Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] ");
+ for (int i = 0; i < eventData.Payload?.Count; i++)
+ {
+ if (i > 0)
+ {
+ sb.Append(", ");
+ }
+ sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]);
+ }
+ _log.WriteLine(sb.ToString());
+ }
+ }
+
+ public override void Dispose()
+ {
+ _log.Dispose();
+ base.Dispose();
+ }
+ }
+
+ public sealed class ConsoleHttpEventListener : EventListener
+ {
+ public ConsoleHttpEventListener()
+ { }
+
+ protected override void OnEventSourceCreated(EventSource eventSource)
+ {
+ if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http")
+ {
+ EnableEvents(eventSource, EventLevel.LogAlways);
+ }
+ }
+
+ protected override void OnEventWritten(EventWrittenEventArgs eventData)
+ {
+ lock (Console.Out)
+ {
+ 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();
+ }
+ }
+ }
+}
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Diagnostics;
+using System.Diagnostics.Tracing;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
private readonly Stopwatch _stopwatch = new Stopwatch();
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private Task? _clientTask;
+ private EventListener? _eventListener;
public long TotalErrorCount => _aggregator.TotalErrorCount;
_config = configuration;
_baseAddress = new Uri(configuration.ServerUri);
_aggregator = new StressResultAggregator(clientOperations);
+
+ // Handle command-line arguments.
+ _eventListener =
+ configuration.LogPath == null ?
+ null :
+ (configuration.LogPath == "console" ?
+ (EventListener)new ConsoleHttpEventListener() :
+ (EventListener)new LogHttpEventListener(configuration.LogPath));
}
public void Start()
}
}
- public void Dispose() => Stop();
+ public void Dispose()
+ {
+ Stop();
+ _eventListener?.Dispose();
+ }
private async Task StartCore()
{
// Representative error text of stress failure
public string ErrorText { get; }
// Operation id => failure timestamps
- public Dictionary<int, List<DateTime>> Failures { get; }
+ public Dictionary<int, List<(DateTime timestamp, TimeSpan duration)>> Failures { get; }
public StressFailureType(string errorText)
{
ErrorText = errorText;
- Failures = new Dictionary<int, List<DateTime>>();
+ Failures = new Dictionary<int, List<(DateTime timestamp, TimeSpan duration)>>();
}
public int FailureCount => Failures.Values.Select(x => x.Count).Sum();
lock (failureType)
{
- if(!failureType.Failures.TryGetValue(operationIndex, out List<DateTime>? timestamps))
+ if(!failureType.Failures.TryGetValue(operationIndex, out List<(DateTime timestamp, TimeSpan duration)>? timestamps))
{
- timestamps = new List<DateTime>();
+ timestamps = new List<(DateTime timestamp, TimeSpan duration)>();
failureType.Failures.Add(operationIndex, timestamps);
}
- timestamps.Add(timestamp);
+ timestamps.Add((timestamp, elapsed));
}
(Type exception, string message, string callSite)[] ClassifyFailure(Exception exn)
Console.WriteLine(failure.ErrorText);
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Yellow;
- foreach (KeyValuePair<int, List<DateTime>> operation in failure.Failures)
+ foreach (KeyValuePair<int, List<(DateTime timestamp, TimeSpan duration)>> operation in failure.Failures)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write($"\t{_operationNames[operation.Key].PadRight(30)}");
Console.Write("Fail: ");
Console.ResetColor();
Console.Write(operation.Value.Count);
- Console.WriteLine($"\tTimestamps: {string.Join(", ", operation.Value.Select(x => x.ToString("HH:mm:ss")))}");
+ Console.WriteLine($"\tTimestamps: {string.Join(", ", operation.Value.Select(x => $"{x.timestamp:HH:mm:ss.fffffff} in {x.duration}"))}");
}
Console.ForegroundColor = ConsoleColor.Cyan;
// Handle command-line arguments.
_eventListener =
- configuration.LogPath == null ? null :
- new HttpEventListener(configuration.LogPath != "console" ? new StreamWriter(configuration.LogPath) { AutoFlush = true } : null);
+ configuration.LogPath == null ?
+ null :
+ (configuration.LogPath == "console" ?
+ (EventListener)new ConsoleHttpEventListener() :
+ (EventListener)new LogHttpEventListener(configuration.LogPath));
SetUpJustInTimeLogging();
public void Dispose()
{
- _webHost.Dispose(); _eventListener?.Dispose();
+ _webHost.Dispose();
+ _eventListener?.Dispose();
}
private void SetUpJustInTimeLogging()
if (Console.ReadKey(intercept: true).Key == ConsoleKey.L)
{
Console.WriteLine("Enabling console event logger");
- _eventListener = new HttpEventListener();
+ _eventListener = new ConsoleHttpEventListener();
break;
}
}
}
}
- /// <summary>EventListener that dumps HTTP events out to either the console or a stream writer.</summary>
- private sealed class HttpEventListener : EventListener
- {
- private readonly StreamWriter? _writer;
-
- public HttpEventListener(StreamWriter? writer = null) => _writer = writer;
-
- protected override void OnEventSourceCreated(EventSource eventSource)
- {
- if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Http")
- EnableEvents(eventSource, EventLevel.LogAlways);
- }
-
- protected override void OnEventWritten(EventWrittenEventArgs eventData)
- {
- lock (Console.Out)
- {
- if (_writer != null)
- {
- var sb = new StringBuilder().Append($"[{eventData.EventName}] ");
- for (int i = 0; i < eventData.Payload?.Count; i++)
- {
- if (i > 0)
- sb.Append(", ");
- sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]);
- }
- _writer.WriteLine(sb);
- }
- else
- {
- Console.ForegroundColor = ConsoleColor.DarkYellow;
- Console.Write($"[{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();
- }
- }
- }
- }
-
private static string CreateResponseContent(HttpContext ctx)
{
return ServerContentUtils.CreateStringContent(GetExpectedContentLength());
### Running using docker-compose
-The prefered way of running the stress suite is using docker-compose,
+The preferred way of running the stress suite is using docker-compose,
which can be used to target both linux and windows containers.
Docker and compose-compose are required for this step (both included in [docker for windows](https://docs.docker.com/docker-for-windows/)).
-{
-
-}
+{}
\ No newline at end of file