Skip to content

Update BenchmarkDotNet and add Http2Connection benchmark #19482

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@
<MicrosoftAspNetCoreAzureAppServicesSiteExtension31PackageVersion>3.1.3</MicrosoftAspNetCoreAzureAppServicesSiteExtension31PackageVersion>
<!-- 3rd party dependencies -->
<AngleSharpPackageVersion>0.9.9</AngleSharpPackageVersion>
<BenchmarkDotNetPackageVersion>0.10.13</BenchmarkDotNetPackageVersion>
<BenchmarkDotNetPackageVersion>0.12.0</BenchmarkDotNetPackageVersion>
<CastleCorePackageVersion>4.2.1</CastleCorePackageVersion>
<FSharpCorePackageVersion>4.2.1</FSharpCorePackageVersion>
<GoogleProtobufPackageVersion>3.8.0</GoogleProtobufPackageVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ public FirstRequestConfig()

Add(JitOptimizationsValidator.FailOnError);

Add(Job.Core
Add(Job.Default
.With(CsProjCoreToolchain.From(NetCoreAppSettings.NetCoreApp21))
.With(new GcMode { Server = true })
.WithTargetCount(10)
.WithIterationCount(10)
.WithInvocationCount(1)
.WithUnrollFactor(1)
.With(RunStrategy.ColdStart));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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;
using System.Buffers;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Net.Http.HPack;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;

namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
public class Http2ConnectionBenchmark
{
private MemoryPool<byte> _memoryPool;
private Pipe _pipe;
private HttpRequestHeaders _httpRequestHeaders;
private Http2Connection _connection;
private Http2HeadersEnumerator _requestHeadersEnumerator;
private int _currentStreamId;
private HPackEncoder _hpackEncoder;
private byte[] _headersBuffer;

[GlobalSetup]
public void GlobalSetup()
{
_memoryPool = SlabMemoryPoolFactory.Create();

var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false);
_pipe = new Pipe(options);

_httpRequestHeaders = new HttpRequestHeaders();
_httpRequestHeaders.Append(HeaderNames.Method, new StringValues("GET"));
_httpRequestHeaders.Append(HeaderNames.Path, new StringValues("/"));
_httpRequestHeaders.Append(HeaderNames.Scheme, new StringValues("http"));
_httpRequestHeaders.Append(HeaderNames.Authority, new StringValues("localhost:80"));

_hpackEncoder = new HPackEncoder();
_headersBuffer = new byte[1024 * 16];

var serviceContext = new ServiceContext
{
DateHeaderValueManager = new DateHeaderValueManager(),
ServerOptions = new KestrelServerOptions(),
Log = new KestrelTrace(NullLogger.Instance),
SystemClock = new MockSystemClock()
};
serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = int.MaxValue;
serviceContext.DateHeaderValueManager.OnHeartbeat(default);

_connection = new Http2Connection(new HttpConnectionContext
{
MemoryPool = _memoryPool,
ConnectionId = "TestConnectionId",
Protocols = Core.HttpProtocols.Http2,
Transport = new MockDuplexPipe(_pipe.Reader, new NullPipeWriter()),
ServiceContext = serviceContext,
ConnectionFeatures = new FeatureCollection(),
TimeoutControl = new MockTimeoutControl(),
});

_requestHeadersEnumerator = new Http2HeadersEnumerator();

_currentStreamId = 1;

_ = _connection.ProcessRequestsAsync(new DummyApplication());

_pipe.Writer.Write(Http2Connection.ClientPreface);
PipeWriterHttp2FrameExtensions.WriteSettings(_pipe.Writer, new Http2PeerSettings());
_pipe.Writer.FlushAsync().GetAwaiter().GetResult();
}

[Benchmark]
public async Task EmptyRequest()
{
_requestHeadersEnumerator.Initialize(_httpRequestHeaders);
PipeWriterHttp2FrameExtensions.WriteStartStream(_pipe.Writer, streamId: _currentStreamId, _requestHeadersEnumerator, _hpackEncoder, _headersBuffer, endStream: true);
_currentStreamId += 2;
await _pipe.Writer.FlushAsync();
}

[GlobalCleanup]
public void Dispose()
{
_pipe.Writer.Complete();
_memoryPool?.Dispose();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
Expand All @@ -10,6 +10,8 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="$(KestrelSharedSourceRoot)test\DummyApplication.cs" />
<Compile Include="$(KestrelSharedSourceRoot)test\PipeWriterHttp2FrameExtensions.cs" />
<Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
<Compile Include="$(KestrelSharedSourceRoot)test\TestApplicationErrorLogger.cs" />
<Compile Include="$(KestrelSharedSourceRoot)test\TestHttp1Connection.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// 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.IO.Pipelines;

namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
internal class MockDuplexPipe : IDuplexPipe
{
public MockDuplexPipe(PipeReader input, PipeWriter output)
{
Input = input;
Output = output;
}

public PipeReader Input { get; }
public PipeWriter Output { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// 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;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;

namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
internal class MockSystemClock : ISystemClock
{
public DateTimeOffset UtcNow { get; }
public long UtcNowTicks { get; }
public DateTimeOffset UtcNowUnsynchronized { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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 Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2.FlowControl;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;

namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
internal class MockTimeoutControl : ITimeoutControl
{
public TimeoutReason TimerReason { get; } = TimeoutReason.KeepAlive;

public void BytesRead(long count)
{
}

public void BytesWrittenToBuffer(MinDataRate minRate, long count)
{
}

public void CancelTimeout()
{
}

public void InitializeHttp2(InputFlowControl connectionInputFlowControl)
{
}

public void ResetTimeout(long ticks, TimeoutReason timeoutReason)
{
}

public void SetTimeout(long ticks, TimeoutReason timeoutReason)
{
}

public void StartRequestBody(MinDataRate minRate)
{
}

public void StartTimingRead()
{
}

public void StartTimingWrite()
{
}

public void StopRequestBody()
{
}

public void StopTimingRead()
{
}

public void StopTimingWrite()
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
{
internal class NullPipeWriter : PipeWriter
{
private byte[] _buffer = new byte[1024 * 128];
// Should be large enough for any content attempting to write to the buffer
private readonly byte[] _buffer = new byte[1024 * 128];

public override void Advance(int bytes)
{
Expand Down
78 changes: 78 additions & 0 deletions src/Servers/Kestrel/shared/test/PipeWriterHttp2FrameExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// 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;
using System.Buffers;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Net.Http.HPack;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;

namespace Microsoft.AspNetCore.Testing
{
internal static class PipeWriterHttp2FrameExtensions
{
public static void WriteSettings(this PipeWriter writer, Http2PeerSettings clientSettings)
{
var frame = new Http2Frame();
frame.PrepareSettings(Http2SettingsFrameFlags.NONE);
var settings = clientSettings.GetNonProtocolDefaults();
var payload = new byte[settings.Count * Http2FrameReader.SettingSize];
frame.PayloadLength = payload.Length;
Http2FrameWriter.WriteSettings(settings, payload);
Http2FrameWriter.WriteHeader(frame, writer);
writer.Write(payload);
}

public static void WriteStartStream(this PipeWriter writer, int streamId, Http2HeadersEnumerator headers, HPackEncoder hpackEncoder, byte[] headerEncodingBuffer, bool endStream)
{
var frame = new Http2Frame();
frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId);

var buffer = headerEncodingBuffer.AsSpan();
var done = hpackEncoder.BeginEncode(headers, buffer, out var length);
frame.PayloadLength = length;

if (done)
{
frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS;
}

if (endStream)
{
frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM;
}

Http2FrameWriter.WriteHeader(frame, writer);
writer.Write(buffer.Slice(0, length));

while (!done)
{
frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId);

done = hpackEncoder.Encode(buffer, out length);
frame.PayloadLength = length;

if (done)
{
frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS;
}

Http2FrameWriter.WriteHeader(frame, writer);
writer.Write(buffer.Slice(0, length));
}
}

public static void WriteData(this PipeWriter writer, int streamId, Memory<byte> data, bool endStream)
{
var frame = new Http2Frame();

frame.PrepareData(streamId);
frame.PayloadLength = data.Length;
frame.DataFlags = endStream ? Http2DataFrameFlags.END_STREAM : Http2DataFrameFlags.NONE;

Http2FrameWriter.WriteHeader(frame, writer);
writer.Write(data.Span);
}
}
}
Loading