Skip to content
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

Improve HTTP benchmarks #4612

Merged
merged 1 commit into from
Oct 24, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Extensions.Http.Resilience.Bench;
namespace Microsoft.Extensions.Http.Resilience.PerformanceTests;

internal sealed class EmptyHandler : DelegatingHandler
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Extensions.Http.Resilience.Bench;
namespace Microsoft.Extensions.Http.Resilience.PerformanceTests;

public class HedgingBenchmark
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

#pragma warning disable EA0006 // Replace uses of 'Enum.GetName' and 'Enum.ToString' with the '[EnumStrings]' code generator for improved performance

namespace Microsoft.Extensions.Http.Resilience.Bench;
namespace Microsoft.Extensions.Http.Resilience.PerformanceTests;

[Flags]
[SuppressMessage("Performance", "EA0004:Make types declared in an executable internal", Justification = "Needs to be public for BenchmarkDotNet consumption")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Extensions.Http.Resilience.Bench;
namespace Microsoft.Extensions.Http.Resilience.PerformanceTests;

public class HttpResilienceBenchmark
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>Microsoft.Extensions.Http.Resilience.FaultInjection.PerformanceTests</RootNamespace>
<RootNamespace>Microsoft.Extensions.Http.Resilience.PerformanceTests</RootNamespace>
<Description>Benchmarks for Microsoft.Extensions.Http.Resilience.</Description>
</PropertyGroup>

Expand All @@ -10,4 +10,10 @@
<ProjectReference Include="..\..\..\src\Libraries\Microsoft.Extensions.Compliance.Testing\Microsoft.Extensions.Compliance.Testing.csproj" />
<ProjectReference Include="..\..\..\src\Libraries\Microsoft.Extensions.Compliance.Redaction\Microsoft.Extensions.Compliance.Redaction.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Polly" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Extensions.Http.Resilience.Bench;
namespace Microsoft.Extensions.Http.Resilience.PerformanceTests;

internal sealed class NoRemoteCallHandler : DelegatingHandler
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Toolchains.InProcess.Emit;

namespace Microsoft.Extensions.Http.Resilience.FaultInjection.Benchmark
namespace Microsoft.Extensions.Http.Resilience.PerformanceTests
{
internal static class Program
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Polly;
using Polly.Timeout;

namespace Microsoft.Extensions.Http.Resilience.PerformanceTests;

public class RetryBenchmark
{
private static readonly Uri _uri = new(HttpClientFactory.PrimaryEndpoint);

private HttpClient _v7 = null!;
private HttpClient _v8 = null!;
private CancellationToken _cancellationToken;

private static HttpRequestMessage Request => new(HttpMethod.Post, _uri);

[GlobalSetup]
public void Prepare()
{
_cancellationToken = new CancellationTokenSource().Token;

var services = new ServiceCollection();

// The ResiliencePipelineBuilder added by Polly includes telemetry, which affects the results.
// Since Polly v7 does not include telemetry either, let's disable the telemetry for v8 for fair results.
services.TryAddTransient<ResiliencePipelineBuilder>();

services
.AddHttpClient("v8")
.ConfigurePrimaryHttpMessageHandler(() => new NoRemoteCallHandler())
.AddResilienceHandler("my-retries", builder => builder.AddRetry(new HttpRetryStrategyOptions
{
BackoffType = DelayBackoffType.Constant,
Delay = TimeSpan.FromSeconds(1),
MaxRetryAttempts = 3
}));

var builder = Policy.Handle<HttpRequestException>().Or<TimeoutRejectedException>().OrResult<HttpResponseMessage>(r =>
{
var statusCode = (int)r.StatusCode;

return statusCode >= 500 ||
r.StatusCode == HttpStatusCode.RequestTimeout ||
statusCode == 429;
});

services
.AddHttpClient("v7")
.ConfigurePrimaryHttpMessageHandler(() => new NoRemoteCallHandler())
.AddPolicyHandler(builder.WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1)));

var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();

_v7 = factory.CreateClient("v7");
_v8 = factory.CreateClient("v8");
}

[Benchmark(Baseline = true)]
public Task<HttpResponseMessage> Retry_Polly_V7()
{
return _v7!.SendAsync(Request, _cancellationToken);
}

[Benchmark]
public Task<HttpResponseMessage> Retry_Polly_V8()
{
return _v8!.SendAsync(Request, _cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Polly;
using Polly.Timeout;

namespace Microsoft.Extensions.Http.Resilience.PerformanceTests;

public class StandardResilienceBenchmark
{
private static readonly Uri _uri = new(HttpClientFactory.PrimaryEndpoint);

private HttpClient _v7 = null!;
private HttpClient _v8 = null!;
private CancellationToken _cancellationToken;

private static HttpRequestMessage Request => new(HttpMethod.Post, _uri);

[GlobalSetup]
public void Prepare()
{
_cancellationToken = new CancellationTokenSource().Token;

var services = new ServiceCollection();

// The ResiliencePipelineBuilder added by Polly includes telemetry, which affects the results.
// Since Polly v7 does not include telemetry either, let's disable the telemetry for v8 for fair results.
services.TryAddTransient<ResiliencePipelineBuilder>();

services
.AddHttpClient("v8")
.ConfigurePrimaryHttpMessageHandler(() => new NoRemoteCallHandler())
.AddStandardResilienceHandler();

var builder = Policy.Handle<HttpRequestException>().Or<TimeoutRejectedException>().OrResult<HttpResponseMessage>(r =>
{
var statusCode = (int)r.StatusCode;

return statusCode >= 500 ||
r.StatusCode == HttpStatusCode.RequestTimeout ||
statusCode == 429;
});

var policy = Policy.WrapAsync(
Policy.TimeoutAsync<HttpResponseMessage>(3),
builder.AdvancedCircuitBreakerAsync(0.1, TimeSpan.FromSeconds(30), 100, TimeSpan.FromSeconds(5)),
builder.WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1)),
Policy.BulkheadAsync<HttpResponseMessage>(1000, 100),
Policy.TimeoutAsync<HttpResponseMessage>(30));

services
.AddHttpClient("v7")
.ConfigurePrimaryHttpMessageHandler(() => new NoRemoteCallHandler())
.AddPolicyHandler(_ => policy);

var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();

_v7 = factory.CreateClient("v7");
_v8 = factory.CreateClient("v8");
}

[Benchmark(Baseline = true)]
public Task<HttpResponseMessage> StandardPipeline_Polly_V7()
{
return _v7!.SendAsync(Request, _cancellationToken);
}

[Benchmark]
public Task<HttpResponseMessage> StandardPipeline_Polly_V8()
{
return _v8!.SendAsync(Request, _cancellationToken);
}
}
2 changes: 2 additions & 0 deletions eng/packages/General.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<PackageVersion Include="Microsoft.Extensions.Features" Version="$(MicrosoftExtensionsFeaturesVersion)" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(MicrosoftExtensionsHostingAbstractionsVersion)" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(MicrosoftExtensionsHostingVersion)" />
<PackageVersion Include="Microsoft.Extensions.Http.Polly" Version="$(MicrosoftExtensionsHttpPollyVersion)" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="$(MicrosoftExtensionsHttpVersion)" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsLoggingAbstractionsVersion)" />
<PackageVersion Include="Microsoft.Extensions.Logging.Configuration" Version="$(MicrosoftExtensionsLoggingConfigurationVersion)" />
Expand All @@ -36,6 +37,7 @@
<PackageVersion Include="Microsoft.Extensions.Primitives" Version="$(MicrosoftExtensionsPrimitivesVersion)" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
<PackageVersion Include="Polly" Version="8.0.0" />
<PackageVersion Include="Polly.Core" Version="8.0.0" />
<PackageVersion Include="Polly.Extensions" Version="8.0.0" />
<PackageVersion Include="Polly.RateLimiting" Version="8.0.0" />
Expand Down