Skip to content

Commit 2c8edea

Browse files
committed
Update BenchmarkDotNet and add Http2Connection benchmark
1 parent c507134 commit 2c8edea

13 files changed

+302
-85
lines changed

eng/Versions.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@
220220
<MicrosoftAspNetCoreAzureAppServicesSiteExtension31PackageVersion>3.1.3</MicrosoftAspNetCoreAzureAppServicesSiteExtension31PackageVersion>
221221
<!-- 3rd party dependencies -->
222222
<AngleSharpPackageVersion>0.9.9</AngleSharpPackageVersion>
223-
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
223+
<BenchmarkDotNetPackageVersion>0.12.0</BenchmarkDotNetPackageVersion>
224224
<CastleCorePackageVersion>4.2.1</CastleCorePackageVersion>
225225
<FSharpCorePackageVersion>4.2.1</FSharpCorePackageVersion>
226226
<GoogleProtobufPackageVersion>3.8.0</GoogleProtobufPackageVersion>
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Collections.Generic;
7+
using System.IO.Pipelines;
8+
using System.Net.Http.HPack;
9+
using System.Threading.Tasks;
10+
using BenchmarkDotNet.Attributes;
11+
using Microsoft.AspNetCore.Http;
12+
using Microsoft.AspNetCore.Http.Features;
13+
using Microsoft.AspNetCore.Server.Kestrel.Core;
14+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
15+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
16+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
17+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
18+
using Microsoft.AspNetCore.Testing;
19+
using Microsoft.Extensions.Logging.Abstractions;
20+
using Microsoft.Extensions.Primitives;
21+
using Microsoft.Net.Http.Headers;
22+
23+
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
24+
{
25+
public class Http2ConnectionBenchmark
26+
{
27+
private MemoryPool<byte> _memoryPool;
28+
private Pipe _pipe;
29+
private HttpRequestHeaders _httpRequestHeaders;
30+
private Http2Connection _connection;
31+
private int _currentStreamId;
32+
private HPackEncoder _hpackEncoder;
33+
private byte[] _headersBuffer;
34+
35+
[GlobalSetup]
36+
public void GlobalSetup()
37+
{
38+
_memoryPool = SlabMemoryPoolFactory.Create();
39+
40+
var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
41+
_pipe = new Pipe(options);
42+
43+
_httpRequestHeaders = new HttpRequestHeaders();
44+
_httpRequestHeaders.Append(HeaderNames.Method, new StringValues("GET"));
45+
_httpRequestHeaders.Append(HeaderNames.Path, new StringValues("/"));
46+
_httpRequestHeaders.Append(HeaderNames.Scheme, new StringValues("http"));
47+
_httpRequestHeaders.Append(HeaderNames.Authority, new StringValues("localhost:80"));
48+
49+
_hpackEncoder = new HPackEncoder();
50+
_headersBuffer = new byte[1024 * 16];
51+
52+
var serviceContext = new ServiceContext
53+
{
54+
DateHeaderValueManager = new DateHeaderValueManager(),
55+
ServerOptions = new KestrelServerOptions(),
56+
Log = new KestrelTrace(NullLogger.Instance),
57+
SystemClock = new MockSystemClock()
58+
};
59+
serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = int.MaxValue;
60+
serviceContext.DateHeaderValueManager.OnHeartbeat(default);
61+
62+
_connection = new Http2Connection(new HttpConnectionContext
63+
{
64+
MemoryPool = _memoryPool,
65+
ConnectionId = "TestConnectionId",
66+
Protocols = Core.HttpProtocols.Http2,
67+
Transport = new MockDuplexPipe(_pipe.Reader, new NullPipeWriter()),
68+
ServiceContext = serviceContext,
69+
ConnectionFeatures = new FeatureCollection(),
70+
TimeoutControl = new MockTimeoutControl(),
71+
});
72+
73+
_currentStreamId = 1;
74+
75+
_ = _connection.ProcessRequestsAsync(new DummyApplication());
76+
77+
_pipe.Writer.Write(Http2Connection.ClientPreface);
78+
PipeWriterHttp2FrameExtensions.WriteSettings(_pipe.Writer, new Http2PeerSettings());
79+
_pipe.Writer.FlushAsync().GetAwaiter().GetResult();
80+
}
81+
82+
[Benchmark]
83+
public async Task EmptyRequest()
84+
{
85+
PipeWriterHttp2FrameExtensions.WriteStartStream(_pipe.Writer, streamId: _currentStreamId, EnumerateHeaders(_httpRequestHeaders), _hpackEncoder, _headersBuffer, endStream: true);
86+
_currentStreamId += 2;
87+
await _pipe.Writer.FlushAsync();
88+
}
89+
90+
[GlobalCleanup]
91+
public void Dispose()
92+
{
93+
_pipe.Writer.Complete();
94+
_memoryPool?.Dispose();
95+
}
96+
97+
private static IEnumerable<KeyValuePair<string, string>> EnumerateHeaders(IHeaderDictionary headers)
98+
{
99+
foreach (var header in headers)
100+
{
101+
foreach (var value in header.Value)
102+
{
103+
yield return new KeyValuePair<string, string>(header.Key, value);
104+
}
105+
}
106+
}
107+
}
108+
}

src/Servers/Kestrel/perf/Kestrel.Performance/Microsoft.AspNetCore.Server.Kestrel.Performance.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
@@ -10,6 +10,8 @@
1010
</PropertyGroup>
1111

1212
<ItemGroup>
13+
<Compile Include="$(KestrelSharedSourceRoot)test\DummyApplication.cs" />
14+
<Compile Include="$(KestrelSharedSourceRoot)test\PipeWriterHttp2FrameExtensions.cs" />
1315
<Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
1416
<Compile Include="$(KestrelSharedSourceRoot)test\TestApplicationErrorLogger.cs" />
1517
<Compile Include="$(KestrelSharedSourceRoot)test\TestHttp1Connection.cs" />
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.IO.Pipelines;
5+
6+
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
7+
{
8+
internal class MockDuplexPipe : IDuplexPipe
9+
{
10+
public MockDuplexPipe(PipeReader input, PipeWriter output)
11+
{
12+
Input = input;
13+
Output = output;
14+
}
15+
16+
public PipeReader Input { get; }
17+
public PipeWriter Output { get; }
18+
}
19+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
6+
7+
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
8+
{
9+
internal class MockSystemClock : ISystemClock
10+
{
11+
public DateTimeOffset UtcNow { get; }
12+
public long UtcNowTicks { get; }
13+
public DateTimeOffset UtcNowUnsynchronized { get; }
14+
}
15+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.AspNetCore.Server.Kestrel.Core;
5+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl;
6+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
7+
8+
namespace Microsoft.AspNetCore.Server.Kestrel.Performance
9+
{
10+
internal class MockTimeoutControl : ITimeoutControl
11+
{
12+
public TimeoutReason TimerReason { get; } = TimeoutReason.KeepAlive;
13+
14+
public void BytesRead(long count)
15+
{
16+
}
17+
18+
public void BytesWrittenToBuffer(MinDataRate minRate, long count)
19+
{
20+
}
21+
22+
public void CancelTimeout()
23+
{
24+
}
25+
26+
public void InitializeHttp2(InputFlowControl connectionInputFlowControl)
27+
{
28+
}
29+
30+
public void ResetTimeout(long ticks, TimeoutReason timeoutReason)
31+
{
32+
}
33+
34+
public void SetTimeout(long ticks, TimeoutReason timeoutReason)
35+
{
36+
}
37+
38+
public void StartRequestBody(MinDataRate minRate)
39+
{
40+
}
41+
42+
public void StartTimingRead()
43+
{
44+
}
45+
46+
public void StartTimingWrite()
47+
{
48+
}
49+
50+
public void StopRequestBody()
51+
{
52+
}
53+
54+
public void StopTimingRead()
55+
{
56+
}
57+
58+
public void StopTimingWrite()
59+
{
60+
}
61+
}
62+
}

src/Servers/Kestrel/perf/Kestrel.Performance/Mocks/NullPipeWriter.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
1010
{
1111
internal class NullPipeWriter : PipeWriter
1212
{
13-
private byte[] _buffer = new byte[1024 * 128];
13+
// Should be large enough for any content attempting to write to the buffer
14+
private readonly byte[] _buffer = new byte[1024 * 128];
1415

1516
public override void Advance(int bytes)
1617
{
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Collections.Generic;
7+
using System.IO.Pipelines;
8+
using System.Net.Http.HPack;
9+
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
10+
11+
namespace Microsoft.AspNetCore.Testing
12+
{
13+
internal static class PipeWriterHttp2FrameExtensions
14+
{
15+
public static void WriteSettings(this PipeWriter writer, Http2PeerSettings clientSettings)
16+
{
17+
var frame = new Http2Frame();
18+
frame.PrepareSettings(Http2SettingsFrameFlags.NONE);
19+
var settings = clientSettings.GetNonProtocolDefaults();
20+
var payload = new byte[settings.Count * Http2FrameReader.SettingSize];
21+
frame.PayloadLength = payload.Length;
22+
Http2FrameWriter.WriteSettings(settings, payload);
23+
Http2FrameWriter.WriteHeader(frame, writer);
24+
writer.Write(payload);
25+
}
26+
27+
public static void WriteStartStream(this PipeWriter writer, int streamId, IEnumerable<KeyValuePair<string, string>> headers, HPackEncoder hpackEncoder, byte[] headerEncodingBuffer, bool endStream)
28+
{
29+
var frame = new Http2Frame();
30+
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);
31+
32+
var buffer = headerEncodingBuffer.AsSpan();
33+
var done = hpackEncoder.BeginEncode(headers, buffer, out var length);
34+
frame.PayloadLength = length;
35+
36+
if (done)
37+
{
38+
frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS;
39+
}
40+
41+
if (endStream)
42+
{
43+
frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM;
44+
}
45+
46+
Http2FrameWriter.WriteHeader(frame, writer);
47+
writer.Write(buffer.Slice(0, length));
48+
49+
while (!done)
50+
{
51+
frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);
52+
53+
done = hpackEncoder.Encode(buffer, out length);
54+
frame.PayloadLength = length;
55+
56+
if (done)
57+
{
58+
frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS;
59+
}
60+
61+
Http2FrameWriter.WriteHeader(frame, writer);
62+
writer.Write(buffer.Slice(0, length));
63+
}
64+
}
65+
66+
public static void WriteData(this PipeWriter writer, int streamId, Memory<byte> data, bool endStream)
67+
{
68+
var frame = new Http2Frame();
69+
70+
frame.PrepareData(streamId);
71+
frame.PayloadLength = data.Length;
72+
frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE;
73+
74+
Http2FrameWriter.WriteHeader(frame, writer);
75+
writer.Write(data.Span);
76+
}
77+
}
78+
}

0 commit comments

Comments
 (0)