Skip to content

Commit ecb904f

Browse files
authored
chore: Added base configuration for benchmarks (#816) [skip ci]
* chore: Added base configuration for benchmarks * chore: Added `NetEvolve.HealthChecks.SqlServer.Legacy` in addition
1 parent 5df7faa commit ecb904f

File tree

10 files changed

+292
-1
lines changed

10 files changed

+292
-1
lines changed

HealthChecks.sln

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGenerator.SqlHealthCh
101101
EndProject
102102
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetEvolve.HealthChecks.AWS.SQS", "src\NetEvolve.HealthChecks.AWS.SQS\NetEvolve.HealthChecks.AWS.SQS.csproj", "{7E9D776E-9BE0-4597-94D7-2CDAE041FEA1}"
103103
EndProject
104+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{66320409-64EC-F7C5-3DEF-65E7510DAAD1}"
105+
ProjectSection(SolutionItems) = preProject
106+
benchmarks\Directory.Build.props = benchmarks\Directory.Build.props
107+
benchmarks\Directory.Build.targets = benchmarks\Directory.Build.targets
108+
benchmarks\Directory.Packages.props = benchmarks\Directory.Packages.props
109+
EndProjectSection
110+
EndProject
111+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks.HealthChecks", "benchmarks\Benchmarks.HealthChecks\Benchmarks.HealthChecks.csproj", "{BADFE044-BBDC-879D-A9A1-2B19AA82E9E2}"
112+
EndProject
104113
Global
105114
GlobalSection(SolutionConfigurationPlatforms) = preSolution
106115
Debug|Any CPU = Debug|Any CPU
@@ -567,6 +576,18 @@ Global
567576
{7E9D776E-9BE0-4597-94D7-2CDAE041FEA1}.Release|x64.Build.0 = Release|Any CPU
568577
{7E9D776E-9BE0-4597-94D7-2CDAE041FEA1}.Release|x86.ActiveCfg = Release|Any CPU
569578
{7E9D776E-9BE0-4597-94D7-2CDAE041FEA1}.Release|x86.Build.0 = Release|Any CPU
579+
{BADFE044-BBDC-879D-A9A1-2B19AA82E9E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
580+
{BADFE044-BBDC-879D-A9A1-2B19AA82E9E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
581+
{BADFE044-BBDC-879D-A9A1-2B19AA82E9E2}.Debug|x64.ActiveCfg = Debug|Any CPU
582+
{BADFE044-BBDC-879D-A9A1-2B19AA82E9E2}.Debug|x64.Build.0 = Debug|Any CPU
583+
{BADFE044-BBDC-879D-A9A1-2B19AA82E9E2}.Debug|x86.ActiveCfg = Debug|Any CPU
584+
{BADFE044-BBDC-879D-A9A1-2B19AA82E9E2}.Debug|x86.Build.0 = Debug|Any CPU
585+
{BADFE044-BBDC-879D-A9A1-2B19AA82E9E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
586+
{BADFE044-BBDC-879D-A9A1-2B19AA82E9E2}.Release|Any CPU.Build.0 = Release|Any CPU
587+
{BADFE044-BBDC-879D-A9A1-2B19AA82E9E2}.Release|x64.ActiveCfg = Release|Any CPU
588+
{BADFE044-BBDC-879D-A9A1-2B19AA82E9E2}.Release|x64.Build.0 = Release|Any CPU
589+
{BADFE044-BBDC-879D-A9A1-2B19AA82E9E2}.Release|x86.ActiveCfg = Release|Any CPU
590+
{BADFE044-BBDC-879D-A9A1-2B19AA82E9E2}.Release|x86.Build.0 = Release|Any CPU
570591
EndGlobalSection
571592
GlobalSection(SolutionProperties) = preSolution
572593
HideSolutionNode = FALSE
@@ -610,6 +631,7 @@ Global
610631
{49EB16EF-0492-49A6-A7BE-82C3C06E88F7} = {EF615D18-42E2-48A4-8EBA-E652DC574C56}
611632
{BCD5D4D2-2E7D-4DA8-8253-FA5761FBB6F5} = {EF615D18-42E2-48A4-8EBA-E652DC574C56}
612633
{7E9D776E-9BE0-4597-94D7-2CDAE041FEA1} = {EF615D18-42E2-48A4-8EBA-E652DC574C56}
634+
{BADFE044-BBDC-879D-A9A1-2B19AA82E9E2} = {66320409-64EC-F7C5-3DEF-65E7510DAAD1}
613635
EndGlobalSection
614636
GlobalSection(ExtensibilityGlobals) = postSolution
615637
SolutionGuid = {28B4CC2B-39E8-49C0-9687-78121BD83A53}

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,15 @@
1212
This is a mono repository for several NuGet packages based on the [Microsoft.Extensions.Diagnostics.HealthChecks](https://www.nuget.org/packages/Microsoft.Extensions.Diagnostics.HealthChecks) package. The main goal of this repository is to provide a set of health checks for different services and frameworks, which are fully configurable either via code or configuration.
1313

1414
### What is the difference between this repository and the [AspNetCore.Diagnostics.HealthChecks](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks) repository?
15-
The main difference is that we try to focus on providing packages that are fully configurable via code or configuration. This means that you can configure the health checks in your `Program.cs` file, or in your `appsettings.json` file, or in any other configuration provider. In some cases, we provide the same healthcheck for a service with an alternative implementation. For example, we provide a healthcheck for MySql that is based on `MySql.Data` and one that is based on `MySqlConnector`. This allows you to choose the implementation that best suits your needs or fits your existing dependencies.
15+
The main difference is that we try to focus on providing packages that are fully configurable via code or configuration. This means that you can configure the health checks in your `Program.cs` file, or in your `appsettings.json` file, or in any other configuration provider. While we continue to focus on configurability, we always consider the possibility of performance optimization. In some cases, we provide the same healthcheck for a service with an alternative implementation. For example, we provide a healthcheck for MySql that is based on `MySql.Data` and one that is based on `MySqlConnector`. This allows you to choose the implementation that best suits your needs or fits your existing dependencies.
16+
17+
While we continue to focus on configurability, we always consider the possibility of performance optimization.
18+
<!-- benchmark:sqlserver -->
19+
| Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
20+
|---------------------------------- |---------:|---------:|---------:|-------------:|--------:|----------:|------------:|
21+
| AspNetCore.HealthChecks.SqlServer | 651.3 μs | 31.19 μs | 87.45 μs | baseline | | 10.42 KB | |
22+
| NetEvolve.HealthChecks.SqlServer | 596.6 μs | 23.81 μs | 70.22 μs | 1.11x faster | 0.20x | 5.25 KB | 1.98x less |
23+
<!-- /benchmark:sqlserver -->
1624

1725
In addition, we try to support the latest LTS and STS versions of .NET ([.NET Support Policy](https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core)) as well as the latest preview version of .NET for at least 3 years, but we **can't guarantee** this. This depends on the support of related NuGet packages and the .NET platform itself. See the [Supported .NET Version](#supported-net-version) section for more details.
1826

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net9.0</TargetFramework>
4+
<OutputType>Exe</OutputType>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>disable</ImplicitUsings>
7+
<GenerateDocumentationFile>false</GenerateDocumentationFile>
8+
<IsXampleProject>true</IsXampleProject>
9+
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
10+
</PropertyGroup>
11+
<ItemGroup>
12+
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="9.0.0" />
13+
<PackageReference Include="BenchmarkDotNet" Version="0.15.1" />
14+
<PackageReference Include="Testcontainers.MsSql" Version="4.5.0" />
15+
</ItemGroup>
16+
<ItemGroup>
17+
<ProjectReference Include="..\..\src\NetEvolve.HealthChecks.SqlServer.Legacy\NetEvolve.HealthChecks.SqlServer.Legacy.csproj" />
18+
<ProjectReference Include="..\..\src\NetEvolve.HealthChecks.SqlServer\NetEvolve.HealthChecks.SqlServer.csproj" />
19+
</ItemGroup>
20+
</Project>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
namespace Benchmarks.HealthChecks.Benchmarks;
2+
3+
using System;
4+
using System.Threading.Tasks;
5+
using BenchmarkDotNet.Attributes;
6+
using global::Benchmarks.HealthChecks.Internals;
7+
using Microsoft.Extensions.Configuration;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Logging.Abstractions;
10+
using NetEvolve.HealthChecks.SqlServer;
11+
using NetEvolve.HealthChecks.SqlServer.Legacy;
12+
using Testcontainers.MsSql;
13+
14+
public class SqlServer
15+
{
16+
private readonly MsSqlContainer _databaseOne = new MsSqlBuilder().WithLogger(NullLogger.Instance).Build();
17+
private readonly MsSqlContainer _databaseTwo = new MsSqlBuilder().WithLogger(NullLogger.Instance).Build();
18+
19+
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
20+
private BenchmarkHealthCheckService _netEvolveHealthCheckExecutor;
21+
private BenchmarkHealthCheckService _netEvolveLegacyHealthCheckExecutor;
22+
private BenchmarkHealthCheckService _anotherHealthCheckExecutor;
23+
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
24+
25+
[GlobalSetup]
26+
public async Task GlobalSetupAsync()
27+
{
28+
await _databaseOne.StartAsync().ConfigureAwait(false);
29+
await _databaseTwo.StartAsync().ConfigureAwait(false);
30+
31+
var configuration = new ConfigurationBuilder().Build();
32+
33+
var netEvolveServices = new ServiceCollection()
34+
.AddSingleton<IConfiguration>(configuration)
35+
.AddSingleton<BenchmarkHealthCheckService>();
36+
_ = netEvolveServices
37+
.AddHealthChecks()
38+
.AddSqlServer(
39+
nameof(_databaseOne),
40+
options => options.ConnectionString = _databaseOne.GetConnectionString()
41+
)
42+
.AddSqlServer(
43+
nameof(_databaseTwo),
44+
options => options.ConnectionString = _databaseTwo.GetConnectionString()
45+
);
46+
var netEvolveServiceProvider = netEvolveServices.BuildServiceProvider();
47+
_netEvolveHealthCheckExecutor = netEvolveServiceProvider.GetRequiredService<BenchmarkHealthCheckService>();
48+
49+
var netEvolveLegacyServices = new ServiceCollection()
50+
.AddSingleton<IConfiguration>(configuration)
51+
.AddSingleton<BenchmarkHealthCheckService>();
52+
_ = netEvolveLegacyServices
53+
.AddHealthChecks()
54+
.AddSqlServerLegacy(nameof(_databaseOne), options => _databaseOne.GetConnectionString())
55+
.AddSqlServerLegacy(nameof(_databaseTwo), options => _databaseTwo.GetConnectionString());
56+
var netEvolveLegacyServiceProvider = netEvolveLegacyServices.BuildServiceProvider();
57+
_netEvolveLegacyHealthCheckExecutor =
58+
netEvolveLegacyServiceProvider.GetRequiredService<BenchmarkHealthCheckService>();
59+
60+
var aspnetcoreServices = new ServiceCollection()
61+
.AddSingleton<IConfiguration>(configuration)
62+
.AddSingleton<BenchmarkHealthCheckService>();
63+
_ = aspnetcoreServices
64+
.AddHealthChecks()
65+
.AddSqlServer(_databaseOne.GetConnectionString(), name: nameof(_databaseOne))
66+
.AddSqlServer(_databaseTwo.GetConnectionString(), name: nameof(_databaseTwo));
67+
var anotherServiceProvider = aspnetcoreServices.BuildServiceProvider();
68+
_anotherHealthCheckExecutor = anotherServiceProvider.GetRequiredService<BenchmarkHealthCheckService>();
69+
}
70+
71+
[GlobalCleanup]
72+
public async Task GlobalCleanupAsync()
73+
{
74+
if (_databaseOne is not null)
75+
{
76+
await _databaseOne.StopAsync().ConfigureAwait(false);
77+
await _databaseOne.DisposeAsync().ConfigureAwait(false);
78+
}
79+
80+
if (_databaseTwo is not null)
81+
{
82+
await _databaseTwo.StopAsync().ConfigureAwait(false);
83+
await _databaseTwo.DisposeAsync().ConfigureAwait(false);
84+
}
85+
}
86+
87+
[Benchmark(Baseline = true, Description = "AspNetCore.HealthChecks.SqlServer")]
88+
public Task BenchmarkAspNetCoreAsync() => _ = _anotherHealthCheckExecutor.CheckHealthAsync();
89+
90+
[Benchmark(Description = "NetEvolve.HealthChecks.SqlServer")]
91+
public Task BenchmarkNetEvolveAsync() => _ = _netEvolveHealthCheckExecutor.CheckHealthAsync();
92+
93+
[Benchmark(Description = "NetEvolve.HealthChecks.SqlServer.Legacy")]
94+
public Task BenchmarkNetEvolveLegacyAsync() => _ = _netEvolveLegacyHealthCheckExecutor.CheckHealthAsync();
95+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
namespace Benchmarks.HealthChecks.Internals;
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Diagnostics;
6+
using System.Linq;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.Extensions.Diagnostics.HealthChecks;
11+
using Microsoft.Extensions.Options;
12+
13+
internal sealed class BenchmarkHealthCheckService(
14+
IServiceScopeFactory scopeFactory,
15+
IOptions<HealthCheckServiceOptions> options
16+
) : HealthCheckService
17+
{
18+
public override async Task<HealthReport> CheckHealthAsync(
19+
Func<HealthCheckRegistration, bool>? predicate,
20+
CancellationToken cancellationToken = default
21+
)
22+
{
23+
var registrations = options.Value.Registrations;
24+
if (predicate != null)
25+
{
26+
registrations = [.. registrations.Where(predicate)];
27+
}
28+
29+
var totalTime = Stopwatch.StartNew();
30+
31+
var tasks = new Task<HealthReportEntry>[registrations.Count];
32+
var index = 0;
33+
34+
foreach (var registration in registrations)
35+
{
36+
tasks[index++] = Task.Run(() => RunCheckAsync(registration, cancellationToken), cancellationToken);
37+
}
38+
39+
_ = await Task.WhenAll(tasks).ConfigureAwait(false);
40+
41+
index = 0;
42+
var entries = new Dictionary<string, HealthReportEntry>(StringComparer.OrdinalIgnoreCase);
43+
foreach (var registration in registrations)
44+
{
45+
entries[registration.Name] = await tasks[index++].ConfigureAwait(false);
46+
}
47+
48+
return new HealthReport(entries, totalTime.Elapsed);
49+
}
50+
51+
private async Task<HealthReportEntry> RunCheckAsync(
52+
HealthCheckRegistration registration,
53+
CancellationToken cancellationToken
54+
)
55+
{
56+
cancellationToken.ThrowIfCancellationRequested();
57+
58+
var scope = scopeFactory.CreateAsyncScope();
59+
await using (scope.ConfigureAwait(false))
60+
{
61+
var healthCheck = registration.Factory(scope.ServiceProvider);
62+
63+
var stopwatch = Stopwatch.StartNew();
64+
var context = new HealthCheckContext { Registration = registration };
65+
66+
HealthReportEntry entry;
67+
CancellationTokenSource? timeoutCancellationTokenSource = null;
68+
try
69+
{
70+
HealthCheckResult result;
71+
72+
var checkCancellationToken = cancellationToken;
73+
if (registration.Timeout > TimeSpan.Zero)
74+
{
75+
timeoutCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
76+
timeoutCancellationTokenSource.CancelAfter(registration.Timeout);
77+
checkCancellationToken = timeoutCancellationTokenSource.Token;
78+
}
79+
80+
result = await healthCheck.CheckHealthAsync(context, checkCancellationToken).ConfigureAwait(false);
81+
82+
entry = new HealthReportEntry(
83+
status: result.Status,
84+
description: result.Description,
85+
duration: stopwatch.Elapsed,
86+
exception: result.Exception,
87+
data: result.Data,
88+
tags: registration.Tags
89+
);
90+
}
91+
catch (Exception ex)
92+
{
93+
entry = new HealthReportEntry(
94+
status: registration.FailureStatus,
95+
description: ex.Message,
96+
duration: stopwatch.Elapsed,
97+
exception: ex,
98+
data: null,
99+
tags: registration.Tags
100+
);
101+
}
102+
finally
103+
{
104+
timeoutCancellationTokenSource?.Dispose();
105+
}
106+
107+
return entry;
108+
}
109+
}
110+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace Benchmarks.HealthChecks.Internals;
2+
3+
using BenchmarkDotNet.Columns;
4+
using BenchmarkDotNet.Configs;
5+
using BenchmarkDotNet.Diagnosers;
6+
using BenchmarkDotNet.Exporters;
7+
using BenchmarkDotNet.Jobs;
8+
using BenchmarkDotNet.Loggers;
9+
using BenchmarkDotNet.Reports;
10+
11+
internal sealed class NetEvolveConfig : ManualConfig
12+
{
13+
public NetEvolveConfig()
14+
{
15+
_ = AddJob(Job.Default)
16+
.AddColumnProvider(DefaultColumnProviders.Instance)
17+
.AddColumn(BaselineRatioColumn.RatioMean, BaselineAllocationRatioColumn.RatioMean)
18+
.AddDiagnoser(MemoryDiagnoser.Default)
19+
.AddExporter(MarkdownExporter.GitHub)
20+
.AddLogger(ConsoleLogger.Default);
21+
22+
SummaryStyle = SummaryStyle.Default.WithRatioStyle(RatioStyle.Trend);
23+
}
24+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
using BenchmarkDotNet.Running;
2+
using Benchmarks.HealthChecks.Internals;
3+
4+
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, new NetEvolveConfig());

benchmarks/Directory.Build.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<Project></Project>

benchmarks/Directory.Build.targets

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<Project></Project>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<Project>
2+
<PropertyGroup>
3+
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
4+
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
5+
</PropertyGroup>
6+
</Project>

0 commit comments

Comments
 (0)