Skip to content

Commit

Permalink
Add sample HTTP benchmark
Browse files Browse the repository at this point in the history
Add an example of benchmarking an ASP.NET Core HTTP API.
  • Loading branch information
martincostello committed Aug 21, 2024
1 parent a068bbd commit 4f5594b
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/DotNetBenchmarks/DotNetBenchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="BenchmarkDotNet" />
</ItemGroup>
</Project>
1 change: 1 addition & 0 deletions src/DotNetBenchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
typeof(HashBenchmarks),
typeof(ILoggerFactoryBenchmarks),
typeof(IndexOfAnyBenchmarks),
typeof(TodoAppBenchmarks),
];

var switcher = new BenchmarkSwitcher(benchmarks);
Expand Down
63 changes: 63 additions & 0 deletions src/DotNetBenchmarks/TodoAppBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) Martin Costello, 2024. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;

namespace DotNetBenchmarks;

[EventPipeProfiler(EventPipeProfile.CpuSampling)]
[MemoryDiagnoser]
public class TodoAppBenchmarks : IAsyncDisposable
{
private TodoServer _app = new();
private HttpClient _client;
private bool _disposed;

[GlobalSetup]
public async Task StartServer()
{
if (_app is { } app)
{
await app.StartAsync();
_client = app.CreateHttpClient();
}
}

[GlobalCleanup]
public async Task StopServer()
{
if (_app is { } app)
{
await app.StopAsync();
_app = null;
}
}

[Benchmark]
public async Task<byte[]> GetAllTodos()
=> await _client!.GetByteArrayAsync("/todos");

[Benchmark]
public async Task<byte[]> GetOneTodo()
=> await _client!.GetByteArrayAsync("/todos/5");

public async ValueTask DisposeAsync()
{
GC.SuppressFinalize(this);

if (!_disposed)
{
_client?.Dispose();
_client = null;

if (_app is not null)
{
await _app.DisposeAsync();
_app = null;
}
}

_disposed = true;
}
}
49 changes: 49 additions & 0 deletions src/DotNetBenchmarks/TodoApplication.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) Martin Costello, 2024. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace DotNetBenchmarks;

internal static partial class TodoApplication
{
public static WebApplicationBuilder AddTodoApi(this WebApplicationBuilder builder)
{
builder.Services.ConfigureHttpJsonOptions((options) =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});

return builder;
}

public static WebApplication UseTodoApi(this WebApplication app)
{
Todo[] samples =
[
new(1, "Walk the dog"),
new(2, "Do the dishes", DateOnly.FromDateTime(DateTime.UtcNow)),
new(3, "Do the laundry", DateOnly.FromDateTime(DateTime.UtcNow.AddDays(1))),
new(4, "Clean the bathroom"),
new(5, "Clean the car", DateOnly.FromDateTime(DateTime.UtcNow.AddDays(2))),
];

var group = app.MapGroup("/todos");

group.MapGet("/", () => samples);
group.MapGet("/{id}", (int id) =>
samples.FirstOrDefault(a => a.Id == id) is { } todo
? Results.Ok(todo)
: Results.NotFound());

return app;
}

public sealed record Todo(int Id, string Title, DateOnly? DueBy = null, bool IsComplete = false);

[JsonSerializable(typeof(Todo[]))]
internal sealed partial class AppJsonSerializerContext : JsonSerializerContext;
}
70 changes: 70 additions & 0 deletions src/DotNetBenchmarks/TodoServer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) Martin Costello, 2024. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace DotNetBenchmarks;

internal sealed class TodoServer : IAsyncDisposable
{
private WebApplication _app;
private Uri _baseAddress;
private bool _disposed;

public TodoServer()
{
var builder = WebApplication.CreateSlimBuilder();

builder.Logging.ClearProviders();
builder.WebHost.UseUrls("http://127.0.0.1:0");

builder.AddTodoApi();

_app = builder.Build();
_app.UseTodoApi();
}

public HttpClient CreateHttpClient()
=> new() { BaseAddress = _baseAddress };

public async Task StartAsync()
{
if (_app is { } app)
{
await app.StartAsync();

var server = app.Services.GetRequiredService<IServer>();
var addresses = server.Features.Get<IServerAddressesFeature>();

_baseAddress = addresses!.Addresses
.Select((p) => new Uri(p))
.Last();
}
}

public async Task StopAsync()
{
if (_app is { } app)
{
await app.StopAsync();
_app = null;
}
}

public async ValueTask DisposeAsync()
{
GC.SuppressFinalize(this);

if (!_disposed && _app is not null)
{
await _app.DisposeAsync();
}

_disposed = true;
}
}

0 comments on commit 4f5594b

Please sign in to comment.