Skip to content

Commit 8327778

Browse files
authored
HttpStress: Optimize LogHttpEventListener (#56983)
Eliminate autoflush, blocking calls and Task allocations in LogHttpEventListener, which can go expensive, when logging several thousands of lines per sec.
1 parent 7b04bfc commit 8327778

File tree

1 file changed

+49
-18
lines changed

1 file changed

+49
-18
lines changed

src/libraries/System.Net.Http/tests/StressTests/HttpStress/HttpEventListener.cs

+49-18
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,47 @@
99
using System.Linq;
1010
using System.Threading;
1111
using System.Threading.Tasks;
12+
using System.Runtime.InteropServices;
13+
using System.Buffers;
1214

1315
namespace HttpStress
1416
{
1517
public sealed class LogHttpEventListener : EventListener
1618
{
1719
private int _lastLogNumber = 0;
18-
private StreamWriter _log;
20+
private FileStream _log;
1921
private Channel<string> _messagesChannel = Channel.CreateUnbounded<string>();
2022
private Task _processMessages;
21-
private CancellationTokenSource _stopProcessing;
23+
private CancellationTokenSource _stopProcessing = new CancellationTokenSource();
24+
private const string LogDirectory = "./clientlog";
25+
26+
private FileStream CreateNextLogFileStream()
27+
{
28+
string fn = Path.Combine(LogDirectory, $"client_{++_lastLogNumber:000}.log");
29+
if (File.Exists(fn))
30+
{
31+
File.Delete(fn);
32+
}
33+
return new FileStream(fn, FileMode.CreateNew, FileAccess.Write);
34+
}
2235

2336
public LogHttpEventListener()
2437
{
25-
foreach (var filename in Directory.GetFiles(".", "client*.log"))
38+
if (!Directory.Exists(LogDirectory))
39+
{
40+
Directory.CreateDirectory(LogDirectory);
41+
}
42+
43+
foreach (string filename in Directory.GetFiles(LogDirectory, "client*.log"))
2644
{
2745
try
2846
{
2947
File.Delete(filename);
3048
} catch {}
3149
}
32-
_log = new StreamWriter("client.log", false) { AutoFlush = true };
33-
50+
_log = CreateNextLogFileStream();
3451
_messagesChannel = Channel.CreateUnbounded<string>();
3552
_processMessages = ProcessMessagesAsync();
36-
_stopProcessing = new CancellationTokenSource();
3753
}
3854

3955
protected override void OnEventSourceCreated(EventSource eventSource)
@@ -46,40 +62,51 @@ protected override void OnEventSourceCreated(EventSource eventSource)
4662

4763
private async Task ProcessMessagesAsync()
4864
{
49-
await Task.Yield();
50-
5165
try
5266
{
67+
byte[] buffer = new byte[8192];
68+
var encoding = Encoding.ASCII;
69+
5370
int i = 0;
5471
await foreach (string message in _messagesChannel.Reader.ReadAllAsync(_stopProcessing.Token))
5572
{
5673
if ((++i % 10_000) == 0)
5774
{
58-
RotateFiles();
75+
await RotateFiles();
5976
}
60-
61-
_log.WriteLine(message);
77+
int maxLen = encoding.GetMaxByteCount(message.Length);
78+
if (maxLen > buffer.Length)
79+
{
80+
buffer = new byte[maxLen];
81+
}
82+
int byteCount = encoding.GetBytes(message, buffer);
83+
84+
await _log.WriteAsync(buffer.AsMemory(0, byteCount), _stopProcessing.Token);
6285
}
6386
}
6487
catch (OperationCanceledException)
6588
{
6689
return;
6790
}
6891

69-
void RotateFiles()
92+
async ValueTask RotateFiles()
7093
{
94+
await _log.FlushAsync(_stopProcessing.Token);
7195
// Rotate the log if it reaches 50 MB size.
72-
if (_log.BaseStream.Length > (50 << 20))
96+
if (_log.Length > (100 << 20))
7397
{
74-
_log.Close();
75-
_log = new StreamWriter($"client_{++_lastLogNumber:000}.log", false) { AutoFlush = true };
98+
await _log.DisposeAsync();
99+
_log = CreateNextLogFileStream();
76100
}
77101
}
78102
}
79103

104+
private StringBuilder? _cachedStringBuilder;
105+
80106
protected override async void OnEventWritten(EventWrittenEventArgs eventData)
81107
{
82-
var sb = new StringBuilder().Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] ");
108+
StringBuilder sb = Interlocked.Exchange(ref _cachedStringBuilder, null) ?? new StringBuilder();
109+
sb.Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] ");
83110
for (int i = 0; i < eventData.Payload?.Count; i++)
84111
{
85112
if (i > 0)
@@ -88,13 +115,17 @@ protected override async void OnEventWritten(EventWrittenEventArgs eventData)
88115
}
89116
sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]);
90117
}
91-
await _messagesChannel.Writer.WriteAsync(sb.ToString());
118+
sb.Append(Environment.NewLine);
119+
string s = sb.ToString();
120+
sb.Clear();
121+
Interlocked.Exchange(ref _cachedStringBuilder, sb);
122+
await _messagesChannel.Writer.WriteAsync(s, _stopProcessing.Token);
92123
}
93124

94125
public override void Dispose()
95126
{
96127
base.Dispose();
97-
128+
_log.Flush();
98129
if (!_processMessages.Wait(TimeSpan.FromSeconds(30)))
99130
{
100131
_stopProcessing.Cancel();

0 commit comments

Comments
 (0)