Skip to content

Commit 20c7ae1

Browse files
authored
Add named pipes microbenchmark (#46472)
1 parent 12c5755 commit 20c7ae1

File tree

1 file changed

+166
-0
lines changed

1 file changed

+166
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.IO.Pipes;
5+
using System.Security.Principal;
6+
using System.Text;
7+
using BenchmarkDotNet.Attributes;
8+
using Microsoft.AspNetCore.Builder;
9+
using Microsoft.AspNetCore.Hosting;
10+
using Microsoft.AspNetCore.Http;
11+
using Microsoft.Extensions.DependencyInjection;
12+
using Microsoft.Extensions.Hosting;
13+
using Microsoft.Extensions.Logging;
14+
15+
namespace Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks;
16+
17+
[OperatingSystemsFilter(true, OS.Windows)]
18+
public class NamedPipesTransportBenchmark
19+
{
20+
private const int _parallelCount = 10;
21+
private const int _parallelCallCount = 1000;
22+
private const string _plaintextExpectedResponse =
23+
"HTTP/1.1 200 OK\r\n" +
24+
"Content-Length: 13\r\n" +
25+
"Date: Fri, 02 Mar 2018 18:37:05 GMT\r\n" +
26+
"Content-Type: text/plain\r\n" +
27+
"Server: Kestrel\r\n" +
28+
"\r\n" +
29+
"Hello, World!";
30+
private static readonly byte[] _responseBuffer = new byte[_plaintextExpectedResponse.Length];
31+
32+
private string _pipeName;
33+
private IHost _host;
34+
35+
[Params(1, 2, 8, 16)]
36+
public int ListenerQueueCount { get; set; }
37+
38+
[GlobalSetup]
39+
public void GlobalSetupPlaintext()
40+
{
41+
_pipeName = "MicrobenchmarksTestPipe-" + Path.GetRandomFileName();
42+
43+
_host = new HostBuilder()
44+
.ConfigureWebHost(webHostBuilder =>
45+
{
46+
webHostBuilder
47+
// Prevent VS from attaching to hosting startup which could impact results
48+
.UseSetting("preventHostingStartup", "true")
49+
.UseKestrel()
50+
// Bind to a single non-HTTPS endpoint
51+
.UseUrls($"http://pipe:/{_pipeName}")
52+
.Configure(app => app.UseMiddleware<PlaintextMiddleware>())
53+
.UseNamedPipes(options =>
54+
{
55+
options.ListenerQueueCount = ListenerQueueCount;
56+
});
57+
})
58+
.Build();
59+
60+
_host.Start();
61+
62+
ValidateResponseAsync(RequestParsingData.PlaintextTechEmpowerRequest, _plaintextExpectedResponse).Wait();
63+
}
64+
65+
private async Task ValidateResponseAsync(byte[] request, string expectedResponse)
66+
{
67+
var clientStream = CreateClientStream(_pipeName);
68+
await clientStream.ConnectAsync();
69+
await clientStream.WriteAsync(request);
70+
await clientStream.ReadAtLeastAsync(_responseBuffer, _responseBuffer.Length);
71+
await clientStream.DisposeAsync();
72+
73+
var response = Encoding.ASCII.GetString(_responseBuffer);
74+
75+
// Exclude date header since the value changes on every request
76+
var expectedResponseLines = expectedResponse.Split("\r\n").Where(s => !s.StartsWith("Date:", StringComparison.Ordinal));
77+
var responseLines = response.Split("\r\n").Where(s => !s.StartsWith("Date:", StringComparison.Ordinal));
78+
79+
if (!Enumerable.SequenceEqual(expectedResponseLines, responseLines))
80+
{
81+
throw new InvalidOperationException(string.Join(Environment.NewLine,
82+
"Invalid response", "Expected:", expectedResponse, "Actual:", response));
83+
}
84+
}
85+
86+
[GlobalCleanup]
87+
public void GlobalCleanup()
88+
{
89+
_host.Dispose();
90+
}
91+
92+
[Benchmark(OperationsPerInvoke = _parallelCount * _parallelCallCount)]
93+
public async Task Plaintext()
94+
{
95+
var parallelTasks = new Task[_parallelCount];
96+
for (var i = 0; i < _parallelCount; i++)
97+
{
98+
parallelTasks[i] = Task.Run(async () =>
99+
{
100+
var clientStreamCount = 0;
101+
while (clientStreamCount < _parallelCallCount)
102+
{
103+
try
104+
{
105+
var namedPipeClient = CreateClientStream(_pipeName);
106+
await namedPipeClient.ConnectAsync();
107+
await namedPipeClient.WriteAsync(RequestParsingData.PlaintextTechEmpowerRequest);
108+
await namedPipeClient.ReadAtLeastAsync(_responseBuffer, _responseBuffer.Length);
109+
namedPipeClient.Dispose();
110+
111+
clientStreamCount++;
112+
}
113+
catch (IOException)
114+
{
115+
}
116+
}
117+
});
118+
}
119+
120+
await Task.WhenAll(parallelTasks);
121+
}
122+
123+
private static NamedPipeClientStream CreateClientStream(string pipeName)
124+
{
125+
var clientStream = new NamedPipeClientStream(
126+
serverName: ".",
127+
pipeName: pipeName,
128+
direction: PipeDirection.InOut,
129+
options: PipeOptions.WriteThrough | PipeOptions.Asynchronous,
130+
impersonationLevel: TokenImpersonationLevel.Anonymous);
131+
return clientStream;
132+
}
133+
134+
// Copied from https://github.com/aspnet/benchmarks/blob/dev/src/Benchmarks/Middleware/PlaintextMiddleware.cs
135+
public class PlaintextMiddleware
136+
{
137+
private static readonly PathString _path = new PathString("/plaintext");
138+
private static readonly byte[] _helloWorldPayload = Encoding.UTF8.GetBytes("Hello, World!");
139+
140+
private readonly RequestDelegate _next;
141+
142+
public PlaintextMiddleware(RequestDelegate next)
143+
{
144+
_next = next;
145+
}
146+
147+
public Task Invoke(HttpContext httpContext)
148+
{
149+
if (httpContext.Request.Path.StartsWithSegments(_path, StringComparison.Ordinal))
150+
{
151+
return WriteResponse(httpContext.Response);
152+
}
153+
154+
return _next(httpContext);
155+
}
156+
157+
public static Task WriteResponse(HttpResponse response)
158+
{
159+
var payloadLength = _helloWorldPayload.Length;
160+
response.StatusCode = 200;
161+
response.ContentType = "text/plain";
162+
response.ContentLength = payloadLength;
163+
return response.Body.WriteAsync(_helloWorldPayload, 0, payloadLength);
164+
}
165+
}
166+
}

0 commit comments

Comments
 (0)