Read server response in Http2ConnectionBenchmark (#19694)
This commit is contained in:
parent
cd6e6ae0bc
commit
92e98b7ede
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue