Last active
September 5, 2022 23:59
-
-
Save neon-sunset/d936e54bfec1031d1628c338be4a7f5d to your computer and use it in GitHub Desktop.
Lightweight HTTP throughput benchmark template for high-load scenarios. Run dotnet new console, dotnet add package RangeExtensions, copy template to Program.cs and then run with dotnet run -c release.
This file contains hidden or 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
| using System.Diagnostics; | |
| // Set your own execution time here. | |
| using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60)); | |
| // dotnet add package RangeExtensions | |
| // Feel free to spawn an arbitrary (within reason) count of workers to simulate desired concurrency. | |
| // Keep in mind that HttpClient might reuse HttpMessageHandler's, feel free to tune to your taste. | |
| var workers = (..64) // ..Environment.ProcessorCount is also a good idea for simpler cases. | |
| .AsEnumerable() | |
| .Select(i => Execute(i, cts.Token)); | |
| var reports = await Task.WhenAll(workers); | |
| var aggregated = reports.Aggregate(default(Report), (seed, r) => seed + r); | |
| // Print all reports. | |
| // Console.WriteLine(string.Join('\n', reports)); | |
| Console.WriteLine(aggregated); | |
| static async Task<Report> Execute(int i, CancellationToken ct) | |
| { | |
| Console.WriteLine($"Starting worker {i}!"); | |
| uint successful = 0, failed = 0; | |
| double kbTransferred = 0; | |
| using var http = new HttpClient(); | |
| // Set your URL here. Keep in mind you might also need to set user-agent to make some CDNs happy. | |
| // You can do it with http.DefaultRequestHeaders.Add("User-Agent", yourUserAgent). | |
| var uri = new Uri("http://localhost:5000/posts?name=example"); | |
| var stopwatch = Stopwatch.StartNew(); | |
| while (!ct.IsCancellationRequested) | |
| { | |
| var response = await http.GetAsync(uri, CancellationToken.None); | |
| if (response.IsSuccessStatusCode) | |
| { | |
| successful++; | |
| kbTransferred += (response.Content.Headers.ContentLength ?? 0) / 1024D; | |
| } | |
| else | |
| { | |
| failed++; | |
| } | |
| } | |
| Console.WriteLine($"Worker {i} stopped!"); | |
| var seconds = stopwatch.Elapsed.TotalSeconds; | |
| return new( | |
| Successful: successful, | |
| Failed: failed, | |
| MegabytesPerSecond: kbTransferred / 1024 / seconds, | |
| RPS: (successful + failed) / seconds; | |
| } | |
| readonly record struct Report( | |
| ulong Successful, | |
| ulong Failed, | |
| double MegabytesPerSecond, | |
| double RPS) | |
| { | |
| public static Report operator +(Report left, Report right) => new( | |
| left.Successful + right.Successful, | |
| left.Failed + right.Failed, | |
| left.MegabytesPerSecond + right.MegabytesPerSecond, | |
| left.RPS + right.RPS); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment