Read server response in Http2ConnectionBenchmark (#19694)

This commit is contained in:
James Newton-King 2020-03-10 15:50:16 +13:00 committed by GitHub
parent cd6e6ae0bc
commit 92e98b7ede
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 158 additions and 28 deletions

View File

@ -3,9 +3,11 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic; using System.Buffers.Binary;
using System.Diagnostics;
using System.IO;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Net.Http.HPack; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -25,20 +27,29 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
public class Http2ConnectionBenchmark public class Http2ConnectionBenchmark
{ {
private MemoryPool<byte> _memoryPool; private MemoryPool<byte> _memoryPool;
private Pipe _pipe;
private HttpRequestHeaders _httpRequestHeaders; private HttpRequestHeaders _httpRequestHeaders;
private Http2Connection _connection; private Http2Connection _connection;
private Http2HeadersEnumerator _requestHeadersEnumerator; private Http2HeadersEnumerator _requestHeadersEnumerator;
private int _currentStreamId; private int _currentStreamId;
private byte[] _headersBuffer; private byte[] _headersBuffer;
private DuplexPipe.DuplexPipePair _connectionPair;
private Http2Frame _httpFrame;
private string _responseData;
private int _dataWritten;
[Params(0, 10, 1024 * 1024)]
public int ResponseDataLength { get; set; }
[GlobalSetup] [GlobalSetup]
public void GlobalSetup() public void GlobalSetup()
{ {
_memoryPool = SlabMemoryPoolFactory.Create(); _memoryPool = SlabMemoryPoolFactory.Create();
_httpFrame = new Http2Frame();
_responseData = new string('!', ResponseDataLength);
var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
_pipe = new Pipe(options);
_connectionPair = DuplexPipe.CreateConnectionPair(options, options);
_httpRequestHeaders = new HttpRequestHeaders(); _httpRequestHeaders = new HttpRequestHeaders();
_httpRequestHeaders.Append(HeaderNames.Method, new StringValues("GET")); _httpRequestHeaders.Append(HeaderNames.Method, new StringValues("GET"));
@ -55,15 +66,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
Log = new KestrelTrace(NullLogger.Instance), Log = new KestrelTrace(NullLogger.Instance),
SystemClock = new MockSystemClock() SystemClock = new MockSystemClock()
}; };
serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = int.MaxValue;
serviceContext.DateHeaderValueManager.OnHeartbeat(default); serviceContext.DateHeaderValueManager.OnHeartbeat(default);
_connection = new Http2Connection(new HttpConnectionContext _connection = new Http2Connection(new HttpConnectionContext
{ {
MemoryPool = _memoryPool, MemoryPool = _memoryPool,
ConnectionId = "TestConnectionId", ConnectionId = "TestConnectionId",
Protocols = Core.HttpProtocols.Http2, Protocols = HttpProtocols.Http2,
Transport = new MockDuplexPipe(_pipe.Reader, new NullPipeWriter()), Transport = _connectionPair.Transport,
ServiceContext = serviceContext, ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(), ConnectionFeatures = new FeatureCollection(),
TimeoutControl = new MockTimeoutControl(), TimeoutControl = new MockTimeoutControl(),
@ -73,11 +83,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
_currentStreamId = 1; _currentStreamId = 1;
_ = _connection.ProcessRequestsAsync(new DummyApplication()); _ = _connection.ProcessRequestsAsync(new DummyApplication(c => ResponseDataLength == 0 ? Task.CompletedTask : c.Response.WriteAsync(_responseData), new MockHttpContextFactory()));
_pipe.Writer.Write(Http2Connection.ClientPreface); _connectionPair.Application.Output.Write(Http2Connection.ClientPreface);
_pipe.Writer.WriteSettings(new Http2PeerSettings()); _connectionPair.Application.Output.WriteSettings(new Http2PeerSettings
_pipe.Writer.FlushAsync().GetAwaiter().GetResult(); {
InitialWindowSize = 2147483647
});
_connectionPair.Application.Output.FlushAsync().GetAwaiter().GetResult();
// Read past connection setup frames
ReceiveFrameAsync(_connectionPair.Application.Input, _httpFrame).GetAwaiter().GetResult();
Debug.Assert(_httpFrame.Type == Http2FrameType.SETTINGS);
ReceiveFrameAsync(_connectionPair.Application.Input, _httpFrame).GetAwaiter().GetResult();
Debug.Assert(_httpFrame.Type == Http2FrameType.WINDOW_UPDATE);
ReceiveFrameAsync(_connectionPair.Application.Input, _httpFrame).GetAwaiter().GetResult();
Debug.Assert(_httpFrame.Type == Http2FrameType.SETTINGS);
} }
[Benchmark] [Benchmark]
@ -85,15 +106,77 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{ {
_requestHeadersEnumerator.Initialize(_httpRequestHeaders); _requestHeadersEnumerator.Initialize(_httpRequestHeaders);
_requestHeadersEnumerator.MoveNext(); _requestHeadersEnumerator.MoveNext();
_pipe.Writer.WriteStartStream(streamId: _currentStreamId, _requestHeadersEnumerator, _headersBuffer, endStream: true); _connectionPair.Application.Output.WriteStartStream(streamId: _currentStreamId, _requestHeadersEnumerator, _headersBuffer, endStream: true, frame: _httpFrame);
await _connectionPair.Application.Output.FlushAsync();
while (true)
{
await ReceiveFrameAsync(_connectionPair.Application.Input, _httpFrame);
if (_httpFrame.StreamId != _currentStreamId && _httpFrame.StreamId != 0)
{
throw new Exception($"Unexpected stream ID: {_httpFrame.StreamId}");
}
if (_httpFrame.Type == Http2FrameType.DATA)
{
_dataWritten += _httpFrame.DataPayloadLength;
}
if (_dataWritten > 1024 * 32)
{
_connectionPair.Application.Output.WriteWindowUpdateAsync(streamId: 0, _dataWritten, _httpFrame);
await _connectionPair.Application.Output.FlushAsync();
_dataWritten = 0;
}
if ((_httpFrame.HeadersFlags & Http2HeadersFrameFlags.END_STREAM) == Http2HeadersFrameFlags.END_STREAM)
{
break;
}
}
_currentStreamId += 2; _currentStreamId += 2;
await _pipe.Writer.FlushAsync(); }
internal async ValueTask ReceiveFrameAsync(PipeReader pipeReader, Http2Frame frame, uint maxFrameSize = Http2PeerSettings.DefaultMaxFrameSize)
{
while (true)
{
var result = await pipeReader.ReadAsync();
var buffer = result.Buffer;
var consumed = buffer.Start;
var examined = buffer.Start;
try
{
if (Http2FrameReader.TryReadFrame(ref buffer, frame, maxFrameSize, out var framePayload))
{
consumed = examined = framePayload.End;
return;
}
else
{
examined = buffer.End;
}
if (result.IsCompleted)
{
throw new IOException("The reader completed without returning a frame.");
}
}
finally
{
pipeReader.AdvanceTo(consumed, examined);
}
}
} }
[GlobalCleanup] [GlobalCleanup]
public void Dispose() public void Dispose()
{ {
_pipe.Writer.Complete(); _connectionPair.Application.Output.Complete();
_memoryPool?.Dispose(); _memoryPool?.Dispose();
} }
} }

View File

@ -0,0 +1,42 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
public class MockHttpContextFactory : IHttpContextFactory
{
private readonly object _lock = new object();
private readonly Queue<DefaultHttpContext> _cache = new Queue<DefaultHttpContext>();
public HttpContext Create(IFeatureCollection featureCollection)
{
DefaultHttpContext httpContext;
lock (_lock)
{
if (!_cache.TryDequeue(out httpContext))
{
httpContext = new DefaultHttpContext();
}
}
httpContext.Initialize(featureCollection);
return httpContext;
}
public void Dispose(HttpContext httpContext)
{
lock (_lock)
{
var defaultHttpContext = (DefaultHttpContext)httpContext;
defaultHttpContext.Uninitialize();
_cache.Enqueue(defaultHttpContext);
}
}
}
}

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Net.Http.HPack; using System.Net.Http.HPack;
@ -24,9 +25,9 @@ namespace Microsoft.AspNetCore.Testing
writer.Write(payload); writer.Write(payload);
} }
public static void WriteStartStream(this PipeWriter writer, int streamId, Http2HeadersEnumerator headers, byte[] headerEncodingBuffer, bool endStream) public static void WriteStartStream(this PipeWriter writer, int streamId, Http2HeadersEnumerator headers, byte[] headerEncodingBuffer, bool endStream, Http2Frame frame = null)
{ {
var frame = new Http2Frame(); frame ??= new Http2Frame();
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId); frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
var buffer = headerEncodingBuffer.AsSpan(); var buffer = headerEncodingBuffer.AsSpan();
@ -63,9 +64,9 @@ namespace Microsoft.AspNetCore.Testing
} }
} }
public static void WriteStartStream(this PipeWriter writer, int streamId, Span<byte> headerData, bool endStream) public static void WriteStartStream(this PipeWriter writer, int streamId, Span<byte> headerData, bool endStream, Http2Frame frame = null)
{ {
var frame = new Http2Frame(); frame ??= new Http2Frame();
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId); frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
frame.PayloadLength = headerData.Length; frame.PayloadLength = headerData.Length;
frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS; frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS;
@ -79,10 +80,9 @@ namespace Microsoft.AspNetCore.Testing
writer.Write(headerData); writer.Write(headerData);
} }
public static void WriteData(this PipeWriter writer, int streamId, Memory<byte> data, bool endStream) public static void WriteData(this PipeWriter writer, int streamId, Memory<byte> data, bool endStream, Http2Frame frame = null)
{ {
var frame = new Http2Frame(); frame ??= new Http2Frame();
frame.PrepareData(streamId); frame.PrepareData(streamId);
frame.PayloadLength = data.Length; frame.PayloadLength = data.Length;
frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE; frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE;
@ -90,5 +90,14 @@ namespace Microsoft.AspNetCore.Testing
Http2FrameWriter.WriteHeader(frame, writer); Http2FrameWriter.WriteHeader(frame, writer);
writer.Write(data.Span); writer.Write(data.Span);
} }
public static void WriteWindowUpdateAsync(this PipeWriter writer, int streamId, int sizeIncrement, Http2Frame frame = null)
{
frame ??= new Http2Frame();
frame.PrepareWindowUpdate(streamId, sizeIncrement);
Http2FrameWriter.WriteHeader(frame, writer);
BinaryPrimitives.WriteUInt32BigEndian(writer.GetSpan(4), (uint)sizeIncrement);
writer.Advance(4);
}
} }
} }

View File

@ -6,6 +6,7 @@ using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing;
using System.IO; using System.IO;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Linq; using System.Linq;
@ -1051,12 +1052,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
protected Task SendWindowUpdateAsync(int streamId, int sizeIncrement) protected Task SendWindowUpdateAsync(int streamId, int sizeIncrement)
{ {
var outputWriter = _pair.Application.Output; var outputWriter = _pair.Application.Output;
var frame = new Http2Frame(); outputWriter.WriteWindowUpdateAsync(streamId, sizeIncrement);
frame.PrepareWindowUpdate(streamId, sizeIncrement);
Http2FrameWriter.WriteHeader(frame, outputWriter);
var buffer = outputWriter.GetSpan(4);
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)sizeIncrement);
outputWriter.Advance(4);
return FlushAsync(outputWriter); return FlushAsync(outputWriter);
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;