Why did the load testing tool break up with the server?
Because it couldn't handle the stress of the relationship!
Load testing is a crucial aspect of performance testing that helps ensure an application can handle expected user loads. By simulating multiple users interacting with your application, you can identify performance bottlenecks and understand how your system behaves under stress. In this article, we’ll explore how to build a simple load testing tool using C#.
Prerequisites
To follow along, you’ll need:
- Visual Studio or any C# IDE
- Basic knowledge of C# and .NET
- Familiarity with HTTP requests and responses
Step 1: Setting Up Your Project
First, create a new Console Application in Visual Studio:
- Open Visual Studio and select Create a new project.
- Choose Console App (.NET Core) and click Next.
- Name your project
SimpleLoadTester
and click Create.
Step 2: Adding Necessary Packages
For our load testing tool, we’ll use the HttpClient
class to send HTTP requests. Ensure that your project references System.Net.Http
, which is available in .NET Core by default.
If you’re using .NET Framework, you may need to install the System.Net.Http
NuGet package:
- Right-click on your project in Solution Explorer.
- Select Manage NuGet Packages.
- Search for
System.Net.Http
and install the latest version.
Step 3: Designing the Load Testing Tool
Our simple load testing tool will:
- Accept a target URL
- Allow configuration of the number of requests and the concurrency level (number of simultaneous users)
- Measure and display the total time taken, average response time, and the number of successful and failed requests
Let’s break down the code step-by-step.
3.1. Define the Main Program Structure
In your Program.cs
file, define the structure of the program:
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace SimpleLoadTester
{
class Program
{
static async Task Main(string[] args)
{
if (args.Length < 3)
{
Console.WriteLine("Usage: SimpleLoadTester <url> <number of requests> <concurrency level>");
return;
}
string url = args[0];
int numberOfRequests = int.Parse(args[1]);
int concurrencyLevel = int.Parse(args[2]);
await RunLoadTest(url, numberOfRequests, concurrencyLevel);
}
static async Task RunLoadTest(string url, int numberOfRequests, int concurrencyLevel)
{
// Implementation will go here
}
}
}
This code snippet defines the entry point of the application, parses command-line arguments, and calls the RunLoadTest
method.
3.2. Implementing the Load Testing Logic
Now, let’s implement the core logic of the RunLoadTest
method:
static async Task RunLoadTest(string url, int numberOfRequests, int concurrencyLevel)
{
using HttpClient client = new HttpClient();
int successfulRequests = 0;
int failedRequests = 0;
long totalResponseTime = 0;
SemaphoreSlim concurrencySemaphore = new SemaphoreSlim(concurrencyLevel);
var tasks = new Task[numberOfRequests];
for (int i = 0; i < numberOfRequests; i++)
{
await concurrencySemaphore.WaitAsync();
tasks[i] = Task.Run(async () =>
{
try
{
var startTime = DateTime.Now;
HttpResponseMessage response = await client.GetAsync(url);
var endTime = DateTime.Now;
if (response.IsSuccessStatusCode)
{
Interlocked.Increment(ref successfulRequests);
}
else
{
Interlocked.Increment(ref failedRequests);
}
Interlocked.Add(ref totalResponseTime, (endTime - startTime).Milliseconds);
}
catch (Exception)
{
Interlocked.Increment(ref failedRequests);
}
finally
{
concurrencySemaphore.Release();
}
});
}
await Task.WhenAll(tasks);
Console.WriteLine($"Total Requests: {numberOfRequests}");
Console.WriteLine($"Successful Requests: {successfulRequests}");
Console.WriteLine($"Failed Requests: {failedRequests}");
Console.WriteLine($"Average Response Time: {(double)totalResponseTime / numberOfRequests} ms");
}
Explanation of the Code
- HttpClient: We use
HttpClient
to send HTTP GET requests to the target URL. - Concurrency Control: We use
SemaphoreSlim
to control the concurrency level, ensuring no more than the specified number of tasks run simultaneously. - Task Creation: For each request, we create a
Task
that:
- Measures the response time
- Tracks success or failure
- Increments counters using
Interlocked
methods for thread-safe operations
- Awaiting Tasks: We use
Task.WhenAll
to wait for all tasks to complete before displaying the results.
Step 4: Running the Load Test
To run the load test, open a terminal in the project directory and use the following command:
dotnet run <url> <number of requests> <concurrency level>
For example:
dotnet run https://example.com 100 10
This command will send 100 requests to https://example.com
with a concurrency level of 10.
Step 5: Analyzing Results
After the load test completes, the tool displays:
- Total Requests: The total number of requests sent
- Successful Requests: The number of requests that received a successful HTTP status code
- Failed Requests: The number of requests that failed
- Average Response Time: The average response time across all requests
Building a simple load testing tool in C# is a great way to learn about HTTP requests, multithreading, and performance testing. This basic tool can be extended with additional features such as support for different HTTP methods, request headers, payloads, and more sophisticated result analysis.
By understanding the fundamentals of load testing, you can better prepare your applications for production and ensure they deliver a reliable and responsive user experience.