From a3e6ff59024e1cedc80c80b0a55934f2d9ec6fd8 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Mon, 31 Aug 2020 18:45:15 +0200 Subject: [PATCH] Add Request/Response Headers/Content Start/Stop events (#41590) --- .../src/System/Net/Http/HttpTelemetry.cs | 36 +++- .../ChunkedEncodingWriteStream.cs | 4 + .../SocketsHttpHandler/ContentLengthWriteStream.cs | 4 + .../Net/Http/SocketsHttpHandler/Http2Connection.cs | 5 + .../Net/Http/SocketsHttpHandler/Http2Stream.cs | 14 +- .../Net/Http/SocketsHttpHandler/HttpConnection.cs | 13 +- .../SocketsHttpHandler/HttpContentWriteStream.cs | 2 + .../tests/FunctionalTests/TelemetryTest.cs | 216 +++++++++++++++++---- 8 files changed, 250 insertions(+), 44 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpTelemetry.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpTelemetry.cs index 98b660f..5879581 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpTelemetry.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpTelemetry.cs @@ -91,23 +91,53 @@ namespace System.Net.Http } [Event(7, Level = EventLevel.Informational)] - public void ResponseHeadersBegin() + public void RequestHeadersStart() { WriteEvent(eventId: 7); } [Event(8, Level = EventLevel.Informational)] - public void ResponseContentStart() + public void RequestHeadersStop() { WriteEvent(eventId: 8); } [Event(9, Level = EventLevel.Informational)] - public void ResponseContentStop() + public void RequestContentStart() { WriteEvent(eventId: 9); } + [Event(10, Level = EventLevel.Informational)] + public void RequestContentStop(long contentLength) + { + WriteEvent(eventId: 10, contentLength); + } + + [Event(11, Level = EventLevel.Informational)] + public void ResponseHeadersStart() + { + WriteEvent(eventId: 11); + } + + [Event(12, Level = EventLevel.Informational)] + public void ResponseHeadersStop() + { + WriteEvent(eventId: 12); + } + + [Event(13, Level = EventLevel.Informational)] + public void ResponseContentStart() + { + WriteEvent(eventId: 13); + } + + [Event(14, Level = EventLevel.Informational)] + public void ResponseContentStop() + { + WriteEvent(eventId: 14); + } + [NonEvent] public void Http11ConnectionEstablished() { diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingWriteStream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingWriteStream.cs index 4da5c7b..e66dbf0 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingWriteStream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingWriteStream.cs @@ -19,6 +19,8 @@ namespace System.Net.Http public override void Write(ReadOnlySpan buffer) { + BytesWritten += buffer.Length; + HttpConnection connection = GetConnectionOrThrow(); Debug.Assert(connection._currentRequest != null); @@ -39,6 +41,8 @@ namespace System.Net.Http public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken ignored) { + BytesWritten += buffer.Length; + HttpConnection connection = GetConnectionOrThrow(); Debug.Assert(connection._currentRequest != null); diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ContentLengthWriteStream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ContentLengthWriteStream.cs index 7867eac..e51c5e0 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ContentLengthWriteStream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ContentLengthWriteStream.cs @@ -17,6 +17,8 @@ namespace System.Net.Http public override void Write(ReadOnlySpan buffer) { + BytesWritten += buffer.Length; + // Have the connection write the data, skipping the buffer. Importantly, this will // force a flush of anything already in the buffer, i.e. any remaining request headers // that are still buffered. @@ -27,6 +29,8 @@ namespace System.Net.Http public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken ignored) // token ignored as it comes from SendAsync { + BytesWritten += buffer.Length; + // Have the connection write the data, skipping the buffer. Importantly, this will // force a flush of anything already in the buffer, i.e. any remaining request headers // that are still buffered. diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 7d1dc3e..e4e7171 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -1354,6 +1354,8 @@ namespace System.Net.Http ArrayBuffer headerBuffer = default; try { + if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestHeadersStart(); + // Serialize headers to a temporary buffer, and do as much work to prepare to send the headers as we can // before taking the write lock. headerBuffer = new ArrayBuffer(InitialConnectionBufferSize, usePool: true); @@ -1434,6 +1436,9 @@ namespace System.Net.Http return s.mustFlush || s.endStream; }, cancellationToken).ConfigureAwait(false); + + if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestHeadersStop(); + return http2Stream; } catch diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs index 788a68c..d193206 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs @@ -194,6 +194,8 @@ namespace System.Net.Http { using var writeStream = new Http2WriteStream(this); + if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestContentStart(); + ValueTask vt = _request.Content.InternalCopyToAsync(writeStream, context: null, _requestBodyCancellationSource.Token); if (vt.IsCompleted) { @@ -208,6 +210,8 @@ namespace System.Net.Http await vt.ConfigureAwait(false); } + + if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestContentStop(writeStream.BytesWritten); } if (NetEventSource.Log.IsEnabled()) Trace($"Finished sending request body."); @@ -568,8 +572,6 @@ namespace System.Net.Http public void OnHeadersStart() { - if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersBegin(); - Debug.Assert(!Monitor.IsEntered(SyncObject)); lock (SyncObject) { @@ -839,6 +841,8 @@ namespace System.Net.Http bool emptyResponse; try { + if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersStart(); + // Wait for response headers to be read. bool wait; @@ -851,6 +855,8 @@ namespace System.Net.Http (wait, emptyResponse) = TryEnsureHeaders(); Debug.Assert(!wait); } + + if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersStop(); } catch { @@ -1344,6 +1350,8 @@ namespace System.Net.Http { private Http2Stream? _http2Stream; + public long BytesWritten { get; private set; } + public Http2WriteStream(Http2Stream http2Stream) { Debug.Assert(http2Stream != null); @@ -1370,6 +1378,8 @@ namespace System.Net.Http public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) { + BytesWritten += buffer.Length; + Http2Stream? http2Stream = _http2Stream; if (http2Stream == null) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs index 9c3655b..f2bff59 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs @@ -364,6 +364,8 @@ namespace System.Net.Http CancellationTokenRegistration cancellationRegistration = RegisterCancellation(cancellationToken); try { + if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestHeadersStart(); + Debug.Assert(request.RequestUri != null); // Write request line await WriteStringAsync(normalizedMethod.Method, async).ConfigureAwait(false); @@ -457,6 +459,8 @@ namespace System.Net.Http // CRLF for end of headers. await WriteTwoBytesAsync((byte)'\r', (byte)'\n', async).ConfigureAwait(false); + if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestHeadersStop(); + if (request.Content == null) { // We have nothing more to send, so flush out any headers we haven't yet sent. @@ -535,7 +539,7 @@ namespace System.Net.Http var response = new HttpResponseMessage() { RequestMessage = request, Content = new HttpConnectionResponseContent() }; ParseStatusLine(await ReadNextResponseHeaderLineAsync(async).ConfigureAwait(false), response); - if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersBegin(); + if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersStart(); // Multiple 1xx responses handling. // RFC 7231: A client MUST be able to parse one or more 1xx responses received prior to a final response, @@ -584,6 +588,8 @@ namespace System.Net.Http ParseHeaderNameValue(this, line, response); } + if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.ResponseHeadersStop(); + if (allowExpect100ToContinue != null) { // If we sent an Expect: 100-continue header, and didn't receive a 100-continue. Handle the final response accordingly. @@ -817,6 +823,9 @@ namespace System.Net.Http // Now that we're sending content, prohibit retries on this connection. _canRetry = false; + Debug.Assert(stream.BytesWritten == 0); + if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestContentStart(); + // Copy all of the data to the server. if (async) { @@ -833,6 +842,8 @@ namespace System.Net.Http // Flush any content that might still be buffered. await FlushAsync(async).ConfigureAwait(false); + if (HttpTelemetry.Log.IsEnabled()) HttpTelemetry.Log.RequestContentStop(stream.BytesWritten); + if (NetEventSource.Log.IsEnabled()) Trace("Finished sending request content."); } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentWriteStream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentWriteStream.cs index 0284562..2ae430c 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentWriteStream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentWriteStream.cs @@ -12,6 +12,8 @@ namespace System.Net.Http { private abstract class HttpContentWriteStream : HttpContentStream { + public long BytesWritten { get; protected set; } + public HttpContentWriteStream(HttpConnection connection) : base(connection) => Debug.Assert(connection != null); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/TelemetryTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/TelemetryTest.cs index 470ba24..7822122 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/TelemetryTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/TelemetryTest.cs @@ -3,10 +3,11 @@ using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.Tracing; +using System.IO; using System.Linq; using System.Net.Test.Common; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; @@ -57,6 +58,8 @@ namespace System.Net.Http.Functional.Tests RemoteExecutor.Invoke(async (useVersionString, testMethod) => { + const int ResponseContentLength = 42; + Version version = Version.Parse(useVersionString); using var listener = new TestEventListener("System.Net.Http", EventLevel.Verbose, eventCounterInterval: 0.1d); @@ -68,6 +71,7 @@ namespace System.Net.Http.Functional.Tests { using HttpClientHandler handler = CreateHttpClientHandler(useVersionString); using HttpClient client = CreateHttpClient(handler, useVersionString); + using var invoker = new HttpMessageInvoker(handler); var request = new HttpRequestMessage(HttpMethod.Get, uri) { @@ -97,21 +101,18 @@ namespace System.Net.Http.Functional.Tests break; case "GetStreamAsync": - await client.GetStreamAsync(uri); + Stream responseStream = await client.GetStreamAsync(uri); + await responseStream.CopyToAsync(Stream.Null); break; case "InvokerSend": - using (var invoker = new HttpMessageInvoker(handler)) - { - await Task.Run(() => invoker.Send(request, cancellationToken: default)); - } + HttpResponseMessage syncResponse = await Task.Run(() => invoker.Send(request, cancellationToken: default)); + await syncResponse.Content.CopyToAsync(Stream.Null); break; case "InvokerSendAsync": - using (var invoker = new HttpMessageInvoker(handler)) - { - await invoker.SendAsync(request, cancellationToken: default); - } + HttpResponseMessage asyncResponse = await invoker.SendAsync(request, cancellationToken: default); + await asyncResponse.Content.CopyToAsync(Stream.Null); break; } }, @@ -121,7 +122,7 @@ namespace System.Net.Http.Functional.Tests { await Task.Delay(300); await connection.ReadRequestDataAsync(); - await connection.SendResponseAsync(); + await connection.SendResponseAsync(content: new string('a', ResponseContentLength)); }); }); @@ -137,15 +138,13 @@ namespace System.Net.Http.Functional.Tests Assert.DoesNotContain(events, e => e.EventName == "RequestFailed"); - ValidateConnectionEstablishedClosed(events, version.Major, version.Minor); - - Assert.Single(events, e => e.EventName == "ResponseHeadersBegin"); + ValidateConnectionEstablishedClosed(events, version); - if (!testMethod.StartsWith("InvokerSend")) - { - Assert.Single(events, e => e.EventName == "ResponseContentStart"); - Assert.Single(events, e => e.EventName == "ResponseContentStop"); - } + ValidateRequestResponseStartStopEvents( + events, + requestContentLength: null, + responseContentLength: testMethod.StartsWith("InvokerSend") ? null : ResponseContentLength, + count: 1); VerifyEventCounters(events, requestCount: 1, shouldHaveFailures: false); }, UseVersion.ToString(), testMethod).Dispose(); @@ -178,6 +177,7 @@ namespace System.Net.Http.Functional.Tests { using HttpClientHandler handler = CreateHttpClientHandler(useVersionString); using HttpClient client = CreateHttpClient(handler, useVersionString); + using var invoker = new HttpMessageInvoker(handler); var request = new HttpRequestMessage(HttpMethod.Get, uri) { @@ -211,17 +211,11 @@ namespace System.Net.Http.Functional.Tests break; case "InvokerSend": - using (var invoker = new HttpMessageInvoker(handler)) - { - await Assert.ThrowsAsync(async () => await Task.Run(() => invoker.Send(request, cts.Token))); - } + await Assert.ThrowsAsync(async () => await Task.Run(() => invoker.Send(request, cts.Token))); break; case "InvokerSendAsync": - using (var invoker = new HttpMessageInvoker(handler)) - { - await Assert.ThrowsAsync(async () => await invoker.SendAsync(request, cts.Token)); - } + await Assert.ThrowsAsync(async () => await invoker.SendAsync(request, cts.Token)); break; } @@ -250,11 +244,119 @@ namespace System.Net.Http.Functional.Tests EventWrittenEventArgs stop = Assert.Single(events, e => e.EventName == "RequestStop"); Assert.Empty(stop.Payload); + ValidateConnectionEstablishedClosed(events, version); + VerifyEventCounters(events, requestCount: 1, shouldHaveFailures: true); }, UseVersion.ToString(), testMethod).Dispose(); } - protected static void ValidateStartEventPayload(EventWrittenEventArgs startEvent) + [OuterLoop] + [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + [InlineData("PostAsync")] + [InlineData("Send")] + [InlineData("SendAsync")] + [InlineData("SendChunkedAsync")] + [InlineData("InvokerSend")] + [InlineData("InvokerSendAsync")] + public void EventSource_SendingRequestContent_LogsRequestContentStartStop(string testMethod) + { + if (UseVersion.Major != 1 && !testMethod.EndsWith("Async")) + { + // Synchronous requests are only supported for HTTP/1.1 + return; + } + + RemoteExecutor.Invoke(async (useVersionString, testMethod) => + { + const int RequestContentLength = 42; + const int ResponseContentLength = 43; + + Version version = Version.Parse(useVersionString); + using var listener = new TestEventListener("System.Net.Http", EventLevel.Verbose, eventCounterInterval: 0.1d); + + var events = new ConcurrentQueue(); + await listener.RunWithCallbackAsync(events.Enqueue, async () => + { + await GetFactoryForVersion(version).CreateClientAndServerAsync( + async uri => + { + using HttpClientHandler handler = CreateHttpClientHandler(useVersionString); + using HttpClient client = CreateHttpClient(handler, useVersionString); + using var invoker = new HttpMessageInvoker(handler); + + var request = new HttpRequestMessage(HttpMethod.Get, uri) + { + Version = version + }; + + var content = new ByteArrayContent(Encoding.ASCII.GetBytes(new string('a', RequestContentLength))); + request.Content = content; + + switch (testMethod) + { + case "PostAsync": + await client.PostAsync(uri, content); + break; + + case "Send": + await Task.Run(() => client.Send(request)); + break; + + case "SendAsync": + await client.SendAsync(request); + break; + + case "SendChunkedAsync": + request.Headers.TransferEncodingChunked = true; + await client.SendAsync(request); + break; + + case "InvokerSend": + HttpResponseMessage syncResponse = await Task.Run(() => invoker.Send(request, cancellationToken: default)); + await syncResponse.Content.CopyToAsync(Stream.Null); + break; + + case "InvokerSendAsync": + HttpResponseMessage asyncResponse = await invoker.SendAsync(request, cancellationToken: default); + await asyncResponse.Content.CopyToAsync(Stream.Null); + break; + } + }, + async server => + { + await server.AcceptConnectionAsync(async connection => + { + await Task.Delay(300); + await connection.ReadRequestDataAsync(); + await connection.SendResponseAsync(content: new string('a', ResponseContentLength)); + }); + }); + + await Task.Delay(300); + }); + Assert.DoesNotContain(events, ev => ev.EventId == 0); // errors from the EventSource itself + + EventWrittenEventArgs start = Assert.Single(events, e => e.EventName == "RequestStart"); + ValidateStartEventPayload(start); + + EventWrittenEventArgs stop = Assert.Single(events, e => e.EventName == "RequestStop"); + Assert.Empty(stop.Payload); + + Assert.DoesNotContain(events, e => e.EventName == "RequestFailed"); + + ValidateConnectionEstablishedClosed(events, version); + + ValidateRequestResponseStartStopEvents( + events, + RequestContentLength, + responseContentLength: testMethod.StartsWith("InvokerSend") ? null : ResponseContentLength, + count: 1); + + VerifyEventCounters(events, requestCount: 1, shouldHaveFailures: false); + }, UseVersion.ToString(), testMethod).Dispose(); + } + + private static void ValidateStartEventPayload(EventWrittenEventArgs startEvent) { Assert.Equal("RequestStart", startEvent.EventName); Assert.Equal(7, startEvent.Payload.Count); @@ -268,15 +370,15 @@ namespace System.Net.Http.Functional.Tests Assert.InRange((HttpVersionPolicy)startEvent.Payload[6], HttpVersionPolicy.RequestVersionOrLower, HttpVersionPolicy.RequestVersionExact); } - protected static void ValidateConnectionEstablishedClosed(ConcurrentQueue events, int versionMajor, int versionMinor, int count = 1) + private static void ValidateConnectionEstablishedClosed(ConcurrentQueue events, Version version, int count = 1) { EventWrittenEventArgs[] connectionsEstablished = events.Where(e => e.EventName == "ConnectionEstablished").ToArray(); Assert.Equal(count, connectionsEstablished.Length); foreach (EventWrittenEventArgs connectionEstablished in connectionsEstablished) { Assert.Equal(2, connectionEstablished.Payload.Count); - Assert.Equal(versionMajor, (byte)connectionEstablished.Payload[0]); - Assert.Equal(versionMinor, (byte)connectionEstablished.Payload[1]); + Assert.Equal(version.Major, (byte)connectionEstablished.Payload[0]); + Assert.Equal(version.Minor, (byte)connectionEstablished.Payload[1]); } EventWrittenEventArgs[] connectionsClosed = events.Where(e => e.EventName == "ConnectionClosed").ToArray(); @@ -284,12 +386,52 @@ namespace System.Net.Http.Functional.Tests foreach (EventWrittenEventArgs connectionClosed in connectionsClosed) { Assert.Equal(2, connectionClosed.Payload.Count); - Assert.Equal(versionMajor, (byte)connectionClosed.Payload[0]); - Assert.Equal(versionMinor, (byte)connectionClosed.Payload[1]); + Assert.Equal(version.Major, (byte)connectionClosed.Payload[0]); + Assert.Equal(version.Minor, (byte)connectionClosed.Payload[1]); } } - protected static void VerifyEventCounters(ConcurrentQueue events, int requestCount, bool shouldHaveFailures, int requestsLeftQueueVersion = -1) + private static void ValidateRequestResponseStartStopEvents(ConcurrentQueue events, int? requestContentLength, int? responseContentLength, int count) + { + EventWrittenEventArgs[] requestHeadersStarts = events.Where(e => e.EventName == "RequestHeadersStart").ToArray(); + Assert.Equal(count, requestHeadersStarts.Length); + Assert.All(requestHeadersStarts, r => Assert.Empty(r.Payload)); + + EventWrittenEventArgs[] requestHeadersStops = events.Where(e => e.EventName == "RequestHeadersStop").ToArray(); + Assert.Equal(count, requestHeadersStops.Length); + Assert.All(requestHeadersStops, r => Assert.Empty(r.Payload)); + + EventWrittenEventArgs[] requestContentStarts = events.Where(e => e.EventName == "RequestContentStart").ToArray(); + Assert.Equal(requestContentLength.HasValue ? count : 0, requestContentStarts.Length); + Assert.All(requestContentStarts, r => Assert.Empty(r.Payload)); + + EventWrittenEventArgs[] requestContentStops = events.Where(e => e.EventName == "RequestContentStop").ToArray(); + Assert.Equal(requestContentLength.HasValue ? count : 0, requestContentStops.Length); + foreach (EventWrittenEventArgs requestContentStop in requestContentStops) + { + object payload = Assert.Single(requestContentStop.Payload); + Assert.True(payload is long); + Assert.Equal(requestContentLength.Value, (long)payload); + } + + EventWrittenEventArgs[] responseHeadersStarts = events.Where(e => e.EventName == "ResponseHeadersStart").ToArray(); + Assert.Equal(count, responseHeadersStarts.Length); + Assert.All(responseHeadersStarts, r => Assert.Empty(r.Payload)); + + EventWrittenEventArgs[] responseHeadersStops = events.Where(e => e.EventName == "ResponseHeadersStop").ToArray(); + Assert.Equal(count, responseHeadersStops.Length); + Assert.All(responseHeadersStops, r => Assert.Empty(r.Payload)); + + EventWrittenEventArgs[] responseContentStarts = events.Where(e => e.EventName == "ResponseContentStart").ToArray(); + Assert.Equal(responseContentLength.HasValue ? count : 0, responseContentStarts.Length); + Assert.All(responseContentStarts, r => Assert.Empty(r.Payload)); + + EventWrittenEventArgs[] responseContentStops = events.Where(e => e.EventName == "ResponseContentStop").ToArray(); + Assert.Equal(responseContentLength.HasValue ? count : 0, responseContentStops.Length); + Assert.All(responseContentStops, r => Assert.Empty(r.Payload)); + } + + private static void VerifyEventCounters(ConcurrentQueue events, int requestCount, bool shouldHaveFailures, int requestsLeftQueueVersion = -1) { Dictionary eventCounters = events .Where(e => e.EventName == "EventCounters") @@ -435,7 +577,7 @@ namespace System.Net.Http.Functional.Tests Assert.DoesNotContain(events, e => e.EventName == "RequestFailed"); - ValidateConnectionEstablishedClosed(events, version.Major, version.Minor); + ValidateConnectionEstablishedClosed(events, version); EventWrittenEventArgs requestLeftQueue = Assert.Single(events, e => e.EventName == "RequestLeftQueue"); Assert.Equal(3, requestLeftQueue.Payload.Count); @@ -443,9 +585,7 @@ namespace System.Net.Http.Functional.Tests Assert.Equal(version.Major, (byte)requestLeftQueue.Payload[1]); Assert.Equal(version.Minor, (byte)requestLeftQueue.Payload[2]); - EventWrittenEventArgs[] responseHeadersBegin = events.Where(e => e.EventName == "ResponseHeadersBegin").ToArray(); - Assert.Equal(3, responseHeadersBegin.Length); - Assert.All(responseHeadersBegin, r => Assert.Empty(r.Payload)); + ValidateRequestResponseStartStopEvents(events, requestContentLength: null, responseContentLength: 0, count: 3); VerifyEventCounters(events, requestCount: 3, shouldHaveFailures: false, requestsLeftQueueVersion: version.Major); }, UseVersion.ToString()).Dispose(); -- 2.7.4