Last active
November 27, 2023 08:31
-
-
Save yanxurui/c71c9762d7f79c704d446452facfcdf8 to your computer and use it in GitHub Desktop.
A wrk like http benchmarking tool written in C# .NET 8 that supports http/3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is a wrk like http benchmarking tool written in C# that supports http/3 | |
using CommandLine; | |
using System.Collections.Concurrent; | |
using System.Diagnostics; | |
using System.Net; | |
using System.Net.Security; | |
using System.Security.Cryptography.X509Certificates; | |
var latencies = new ConcurrentBag<double>(); | |
var failedRequest = new ConcurrentBag<HttpStatusCode>(); | |
int requestCount = 0; | |
long responseSize = 0; | |
await Parser.Default.ParseArguments<Options>(args).WithParsedAsync(async o => | |
{ | |
var tasks = new List<Task>(); | |
for (int i = 0; i < o.Threads; i++) | |
{ | |
// Use Task.Run) to run the task in a new thread | |
tasks.Add(Task.Run(() => RunThread(o))); | |
} | |
await Task.WhenAll(tasks); | |
// Print stats | |
Console.WriteLine("Running {0}s test @ {1}", o.Duration, o.Url); | |
Console.WriteLine("{0} threads and {1} connections", o.Threads, o.Connections); | |
Console.WriteLine("\t{0} requests in {1}s, {2:F2}GB read", requestCount, o.Duration, 1.0*responseSize/(1<<30)); | |
if (failedRequest.Count > 0) | |
{ | |
Console.WriteLine("Non-2xx or 3xx responses: {0}", failedRequest.Count); | |
} | |
Console.WriteLine("Requests/sec: {0}", requestCount / o.Duration); | |
double avg = latencies.Average(); | |
Console.WriteLine("Average Latency: {0:F2}ms", avg); | |
} ); | |
async Task RunThread(Options o) | |
{ | |
// Console.WriteLine("[Thread] Thread id: " + Thread.CurrentThread.ManagedThreadId); | |
var handler = new HttpClientHandler(); | |
handler.ClientCertificateOptions = ClientCertificateOption.Manual; | |
handler.ServerCertificateCustomValidationCallback = | |
(HttpRequestMessage httpMessage, X509Certificate2 certificate2, X509Chain x509Chain, SslPolicyErrors sslPolicyErrors) => | |
{ | |
return true; // Ignore the checks and return true | |
}; | |
var client = new HttpClient(handler); | |
switch(o.HttpVersion) | |
{ | |
case 0: | |
// not specified | |
break; | |
case 1: | |
case 2: | |
case 3: | |
client.DefaultRequestVersion = o.HttpVersion == 1 ? HttpVersion.Version11 : o.HttpVersion == 2 ? HttpVersion.Version20 : HttpVersion.Version30; | |
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact; | |
break; | |
default: | |
throw new NotSupportedException("Invalid http version"); | |
} | |
var tasks = new List<Task>(); | |
for (int i = 0; i < (o.Connections/o.Threads); i++) | |
{ | |
tasks.Add(RunConnection(client, o)); | |
} | |
await Task.WhenAll(tasks); | |
client.Dispose(); | |
} | |
async Task RunConnection(HttpClient client, Options o) | |
{ | |
// Console.WriteLine("[Connection] Thread id: " + Thread.CurrentThread.ManagedThreadId); | |
var sw = Stopwatch.StartNew(); | |
double t; | |
while (sw.Elapsed.TotalSeconds < o.Duration) | |
{ | |
t = sw.Elapsed.TotalMilliseconds; | |
HttpResponseMessage resp = await client.GetAsync(o.Url); | |
string body = await resp.Content.ReadAsStringAsync(); | |
Interlocked.Increment(ref requestCount); | |
Interlocked.Add(ref responseSize, body.Length); | |
if (resp.StatusCode == HttpStatusCode.OK) | |
{ | |
latencies.Add(sw.Elapsed.TotalMilliseconds - t); | |
} | |
else | |
{ | |
failedRequest.Add(resp.StatusCode); | |
} | |
} | |
} | |
public class Options | |
{ | |
[Option('U', "url", Required = true, HelpText = "Url to do load test against")] | |
public required string Url { get; set; } | |
[Option('D', "duration", Required = true, HelpText = "Duration (seconds) to run the test")] | |
public required uint Duration { get; set; } | |
[Option('T', "thread", Required = true, HelpText = "Count of threads")] | |
public required ushort Threads { get; set; } | |
[Option('C', "connection", Required = true, HelpText = "Connections in total. Connections/Threads in each thread.")] | |
public required ushort Connections { get; set; } | |
[Option('H', "http", Required = false, HelpText = "Http version, allowed values are 1, 2, 3")] | |
public required ushort HttpVersion { get; set; } = 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment