Skip to content
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
1 change: 1 addition & 0 deletions perf/benchmarkapps/GrpcClient/ClientOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ public class ClientOptions
public GrpcClientType GrpcClientType { get; set; }
public int Streams { get; set; }
public int Deadline { get; set; }
public bool WinHttpHandler { get; set; }
}
9 changes: 3 additions & 6 deletions perf/benchmarkapps/GrpcClient/GrpcClient.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework Condition="'$(NetFxFramework)'!='true'">net9.0</TargetFramework>
<TargetFramework Condition="'$(NetFxFramework)'=='true'">net462</TargetFramework>
<OutputType>Exe</OutputType>
<DefineConstants Condition="'$(EnableGrpcWeb)' == 'true'">$(DefineConstants);GRPC_WEB</DefineConstants>
<!-- Enable server GC to more closely simulate a microservice app making calls -->
Expand All @@ -23,6 +24,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Console" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="System.CommandLine" />
<PackageReference Include="System.Net.Http.WinHttpHandler" />

<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.Core" />
Expand All @@ -49,9 +51,4 @@
<PackageReference Include="Grpc.Net.Client" />
</ItemGroup>


<ItemGroup Condition="$([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), 5.0))">

</ItemGroup>

</Project>
4 changes: 4 additions & 0 deletions perf/benchmarkapps/GrpcClient/NamedPipeConnectionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

namespace GrpcClient;

#if NET9_0_OR_GREATER

public class NamedPipeConnectionFactory
{
private readonly string _pipeName;
Expand Down Expand Up @@ -54,3 +56,5 @@ public async ValueTask<Stream> ConnectAsync(SocketsHttpConnectionContext _,
}
}
}

#endif
106 changes: 69 additions & 37 deletions perf/benchmarkapps/GrpcClient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Net.Http;
using System.Net.Security;
using System.Net.Sockets;
using System.Reflection;
Expand Down Expand Up @@ -78,6 +79,7 @@ public static async Task<int> Main(string[] args)
var streamsOption = new Option<int>(new string[] { "--streams" }, () => 1, "Maximum concurrent streams per connection");
var enableCertAuthOption = new Option<bool>(new string[] { "--enableCertAuth" }, () => false, "Flag indicating whether client sends a client certificate");
var deadlineOption = new Option<int>(new string[] { "--deadline" }, "Duration of deadline in seconds");
var winHttpHandlerOption = new Option<bool>(new string[] { "--winhttphandler" }, () => false, "Whether to use WinHttpHandler with Grpc.Net.Client");

var rootCommand = new RootCommand();
rootCommand.AddOption(urlOption);
Expand All @@ -97,6 +99,7 @@ public static async Task<int> Main(string[] args)
rootCommand.AddOption(streamsOption);
rootCommand.AddOption(enableCertAuthOption);
rootCommand.AddOption(deadlineOption);
rootCommand.AddOption(winHttpHandlerOption);

rootCommand.SetHandler(async (InvocationContext context) =>
{
Expand All @@ -118,6 +121,7 @@ public static async Task<int> Main(string[] args)
_options.Streams = context.ParseResult.GetValueForOption(streamsOption);
_options.EnableCertAuth = context.ParseResult.GetValueForOption(enableCertAuthOption);
_options.Deadline = context.ParseResult.GetValueForOption(deadlineOption);
_options.WinHttpHandler = context.ParseResult.GetValueForOption(winHttpHandlerOption);

var runtimeVersion = typeof(object).GetTypeInfo().Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "Unknown";
var isServerGC = GCSettings.IsServerGC;
Expand Down Expand Up @@ -159,8 +163,10 @@ public static async Task<int> Main(string[] args)
return await rootCommand.InvokeAsync(args);
}

#if NET9_0_OR_GREATER
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
Justification = "DependencyInjection only used with safe types.")]
#endif
private static ILoggerFactory CreateLoggerFactory()
{
return LoggerFactory.Create(c =>
Expand Down Expand Up @@ -244,7 +250,7 @@ private static async Task StartScenario()
var text = "Exception from test: " + ex.Message;
Log(text);
_errorStringBuilder.AppendLine();
_errorStringBuilder.Append(CultureInfo.InvariantCulture, $"[{DateTime.Now:hh:mm:ss.fff}] {text}");
_errorStringBuilder.Append(string.Format(CultureInfo.InvariantCulture, "[{0:hh:mm:ss.fff}] {1}", DateTime.Now, text) );
}
}

Expand Down Expand Up @@ -425,7 +431,9 @@ private static void CreateChannels()
var initialUri = _options.Url!;
var resolvedUri = initialUri.Authority;

Log($"Framework version: {System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription}");
Log($"gRPC client type: {_options.GrpcClientType}");
Log($"WinHttpHandler: {(_options.GrpcClientType == GrpcClientType.GrpcNetClient ? _options.WinHttpHandler.ToString() : "N/A")}");
Log($"Log level: {_options.LogLevel}");
Log($"Protocol: '{_options.Protocol}'");
Log($"Creating channels to '{resolvedUri}'");
Expand Down Expand Up @@ -465,43 +473,12 @@ private static ChannelBase CreateChannel(string target)
var address = useTls ? "https://" : "http://";
address += target;

var httpClientHandler = new SocketsHttpHandler();
httpClientHandler.UseProxy = false;
httpClientHandler.AllowAutoRedirect = false;
if (_options.EnableCertAuth)
{
var basePath = Path.GetDirectoryName(AppContext.BaseDirectory);
var certPath = Path.Combine(basePath!, "Certs", "client.pfx");
var clientCertificates = X509CertificateLoader.LoadPkcs12CollectionFromFile(certPath, "1111");
httpClientHandler.SslOptions.ClientCertificates = clientCertificates;
}

if (!string.IsNullOrEmpty(_options.UdsFileName))
{
var connectionFactory = new UnixDomainSocketConnectionFactory(new UnixDomainSocketEndPoint(ResolveUdsPath(_options.UdsFileName)));
httpClientHandler.ConnectCallback = connectionFactory.ConnectAsync;
}
else if (!string.IsNullOrEmpty(_options.NamedPipeName))
{
var connectionFactory = new NamedPipeConnectionFactory(_options.NamedPipeName);
httpClientHandler.ConnectCallback = connectionFactory.ConnectAsync;
}

httpClientHandler.SslOptions.RemoteCertificateValidationCallback =
(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) => true;

HttpMessageHandler httpMessageHandler = httpClientHandler;

Version? versionOverride = null;
if (_options.Protocol == "h3")
{
// Stop gRPC channel from creating TCP socket.
httpClientHandler.ConnectCallback = (context, cancellationToken) => throw new InvalidOperationException("Should never be called for H3.");

// Force H3 on all requests.
versionOverride = new Version(3, 0);
}
// Force H3 on all requests.
var versionOverride = _options.Protocol == "h3"
? new Version(3, 0)
: null;

var httpMessageHandler = CreateMessageHandler();
return GrpcChannel.ForAddress(address, new GrpcChannelOptions
{
HttpHandler = httpMessageHandler,
Expand All @@ -511,6 +488,61 @@ private static ChannelBase CreateChannel(string target)
}
}

private static HttpMessageHandler CreateMessageHandler()
{
if (_options.WinHttpHandler)
{
return CreateWinHttpHandler();
}

#if NET9_0_OR_GREATER
var httpClientHandler = new SocketsHttpHandler();
httpClientHandler.UseProxy = false;
httpClientHandler.AllowAutoRedirect = false;
if (_options.EnableCertAuth)
{
var basePath = Path.GetDirectoryName(AppContext.BaseDirectory);
var certPath = Path.Combine(basePath!, "Certs", "client.pfx");
var clientCertificates = X509CertificateLoader.LoadPkcs12CollectionFromFile(certPath, "1111");
httpClientHandler.SslOptions.ClientCertificates = clientCertificates;
}

if (!string.IsNullOrEmpty(_options.UdsFileName))
{
var connectionFactory = new UnixDomainSocketConnectionFactory(new UnixDomainSocketEndPoint(ResolveUdsPath(_options.UdsFileName)));
httpClientHandler.ConnectCallback = connectionFactory.ConnectAsync;
}
else if (!string.IsNullOrEmpty(_options.NamedPipeName))
{
var connectionFactory = new NamedPipeConnectionFactory(_options.NamedPipeName);
httpClientHandler.ConnectCallback = connectionFactory.ConnectAsync;
}

httpClientHandler.SslOptions.RemoteCertificateValidationCallback =
(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) => true;

if (_options.Protocol == "h3")
{
// Stop gRPC channel from creating TCP socket.
httpClientHandler.ConnectCallback = (context, cancellationToken) => throw new InvalidOperationException("Should never be called for H3.");
}

return httpClientHandler;
#else
return CreateWinHttpHandler();
#endif
}

private static WinHttpHandler CreateWinHttpHandler()
{
#pragma warning disable CA1416 // Validate platform compatibility
return new WinHttpHandler
{
ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true,
};
#pragma warning restore CA1416 // Validate platform compatibility
}

private static string ResolveUdsPath(string udsFileName) => Path.Combine(Path.GetTempPath(), udsFileName);

private static SslCredentials GetSslCredentials()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

namespace GrpcClient;

#if NET9_0_OR_GREATER

public class UnixDomainSocketConnectionFactory
{
private readonly EndPoint _endPoint;
Expand All @@ -46,3 +48,5 @@ public async ValueTask<Stream> ConnectAsync(SocketsHttpConnectionContext _, Canc
}
}
}

#endif