A simple HTTP load testing library and CLI tool written in Rust.
Linux & macOS:
curl -fsSL https://raw.githubusercontent.com/fredyw/load-rs/main/install.sh | bashWindows (PowerShell):
iwr -useb https://raw.githubusercontent.com/fredyw/load-rs/main/install.ps1 | iexIf you have Rust installed, you can install load-rs directly from crates.io:
cargo install load-rsgit clone https://github.com/fredyw/load-rs.git
cd load-rs
./install.sh --sourceUsage: load-rs [OPTIONS] --requests <REQUESTS> --concurrency <CONCURRENCY> <URL>
Arguments:
<URL> Target URL to send requests to
Options:
-n, --requests <REQUESTS> Total number of requests to send
-z, --duration <DURATION> Maximum duration of the load test (e.g., 10s, 1m)
-c, --concurrency <CONCURRENCY> Number of concurrent requests to run at a time
-r, --rate <RATE> Requests per second (RPS) limit
-X, --method <METHOD> HTTP method to use for the requests [default: get]
-H, --header <HEADER> Custom HTTP header(s) in "key: value" format. Can be repeated
-d, --data <DATA> Request body as a string
-D, --data-file <DATA_FILE> File to read the request body from
-i, --data-dir <DATA_DIR> Directory of files to use as request bodies
-m, --manifest-file <MANIFEST_FILE> Request manifest file (JSON Lines format)
-C, --cacert <CA_CERT> Custom CA certificate file (PEM format)
-E, --cert <CERT> Public certificate file (PEM format)
-k, --key <KEY> Private key file (PEM format)
-I, --insecure Allows insecure connections by skipping TLS certificate verification
-O, --order <ORDER> Order to process files from --data-dir or --manifest-file [default: sequential]
-o, --output <OUTPUT_FILE> File to save responses to (JSON Lines format)
-S, --save-mode <SAVE_MODE> Specifies what to save in the response output [default: all]
-G, --debug Performs a single request and dumps the response
-s, --stats <STATS> Specifies which requests to include in the statistics [default: all]
-t, --timeout <TIMEOUT> Request timeout in seconds
-A, --user-agent <USER_AGENT> Custom user agent
-p, --proxy <PROXY> Proxy server URL
-u, --unit <UNIT> Unit of measurement (seconds or milliseconds) [default: milliseconds]
-q, --quiet Quiet mode: suppress progress updates
-K, --disable-keepalive Disables HTTP keep-alive
-j, --json Output results in JSON format
-h, --help Print help
-V, --version Print version
When the -o or --output option is specified, load-rs will stream the response of each request
to the specified file in JSON Lines format.
Each record in the responses.jsonl file contains:
name: The identifier of the request (e.g., manifestname, filename, or line number).iteration: The request iteration number.duration: The duration of the request (nanoseconds and seconds).status: The HTTP status code (present even for non-2xx responses).version: The HTTP version of the response.headers: A map of the response headers (captured for both success and failures).body: The response body as a string. If the body is not valid UTF-8, it will be base64-encoded.error: The error message if the request failed to connect or send.
You can control the granularity of the saved data using the --save-mode option:
all(default): Saves everything listed above.headers: Saves status and headers only (no body).body: Saves status and body only (no headers).
The manifest file is a JSON Lines file where each line is a JSON object that defines a request. The following fields are supported:
name: An optional identifier for the request, which will appear in the output.method: Optional HTTP method override (e.g., "GET", "POST").path: Optional URL path override (joined with the base URL).headers: A map of HTTP headers to be sent with the request.body: The request body as a string.binary_body: The request body as a base64-encoded string.
Note: If both body and binary_body are specified, body will be used.
Example manifest.jsonl
{"name": "login", "headers": {"Content-Type": "application/json"}, "body": "{\"key\": \"value1\"}"}
{"name": "search", "path": "/v1/search", "method": "GET"}
{"headers": {"Content-Type": "application/octet-stream"}, "binary_body": "SGVsbG8gd29ybGQ="}The -O or --order option allows you to control the order in which requests are sent when using
either the --data-dir or --manifest-file option. The following values are supported:
sequential(default): Requests are sent in the order they appear in the directory or manifest file.random: Requests are sent in a random order.
load-rs supports several options for configuring TLS:
-C, --cacert <CA_CERT>: Use a custom CA certificate file (PEM format) to verify the server's certificate.-E, --cert <CERT>: Use a client certificate file (PEM format) for mutual TLS authentication.-k, --key <KEY>: Use a private key file (PEM format) for the client certificate.-I, --insecure: Allows insecure connections by skipping TLS certificate verification.
The s or --stats option allows you to control which requests are included in the statistics. The following
values are supported:
success: Only include successful requests in the statistics.error: Only include failed requests in the statistics.all(default): Include all requests (successful and failed) in the statistics.
The -z or --duration flag allows you to run the load test for a specific amount of time instead of a fixed number of requests. You can specify the duration in seconds (e.g., 30, 30s), minutes (e.g., 5m), or hours (e.g., 1h).
If both --requests and --duration are provided, the test will stop whichever limit is reached first.
The -r or --rate flag allows you to limit the number of requests per second (RPS). This is useful for simulating steady traffic or avoiding overwhelming the target server.
The -t or --timeout option allows you to set a timeout in seconds for each request. By default, requests do not time out.
The -A or --user-agent option allows you to set a custom user agent string for each request. By default, requests do not include a custom user agent.
The -p or --proxy option allows you to route requests through a proxy server.
The -u or --unit option allows you to configure the unit of measurement used in the output for duration and latencies. Supported values are seconds and milliseconds. By default, it is in milliseconds.
The -q or --quiet option suppresses the real-time progress bar and updates during the test.
This can slightly improve performance by reducing terminal I/O and local CPU overhead, and is useful for automation or when redirecting output to a file.
The --disable-keepalive flag disables HTTP keep-alive. By default, load-rs reuses connections to improve performance. Disabling it forces the tool to create a new TCP connection for every request, which is useful for simulating "cold" clients or high connection churn.
The --json flag causes load-rs to output the final test results in a structured JSON format instead of the human-readable summary. This is ideal for CI/CD pipelines, automated benchmarking, or feeding the results into other tools for analysis.
The -G or --debug option can be used to perform a single request and dump the response to the console.
This is useful for verifying that your requests are correct and that the server is responding as expected.
When using this option, the -n, -c, and -o options are ignored.
GET request
load-rs -n 100 -c 10 http://localhost:8080
POST request with a JSON body
load-rs -n 100 -c 10 -X POST -d '{"key": "value"}' http://localhost:8080
POST request with a body from a file
load-rs -n 100 -c 10 -X POST -D /path/to/body.json http://localhost:8080
POST request with bodies from a directory
load-rs -n 100 -c 10 -X POST -i /path/to/bodies http://localhost:8080
POST request with a manifest file
load-rs -n 100 -c 10 -X POST -m /path/to/manifest.jsonl http://localhost:8080
To build the project, you need to have Rust installed. You can install it from here.
Once you have Rust installed, you can build the project by running the following command:
./build.sh --releaseThe binary will be located in target/release/load-rs.
To run the tests, including formatting and linting checks, you can use the following command:
./test.shTo create a new release, use the provided release.sh script:
./release.sh <version>Example:
./release.sh 0.3.0load-rs can be used as a library in your own Rust projects. Add it to your Cargo.toml:
[dependencies]
load-rs = "VERSION" # Replace with the latest version from crates.io
tokio = { version = "1", features = ["full"] }
anyhow = "1"The following example shows how to run a simple GET load test:
use load_rs::{LoadTestRunner, HttpMethod, Stats, LoadTestEvent};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let url = "http://localhost:8080";
let requests = 100;
let concurrency = 10;
// Initialize the runner using the builder pattern
let runner = LoadTestRunner::builder(url, requests, concurrency)
.stats(Stats::All)
.save_mode(load_rs::SaveMode::Headers)
.timeout(30)
.disable_keepalive(true)
.build()
.await?;
// Run the test
let result = runner.run(
HttpMethod::Get,
None, // headers
None, // body
Some(std::path::Path::new("responses.jsonl")), // output_file
|event| {
// Handle real-time events
match event {
LoadTestEvent::ProgressUpdate(stats) => {
println!("Progress: {}/{} requests, RPS: {:.2}",
stats.completed, requests, stats.rps);
}
LoadTestEvent::RequestFinished(res) => {
if !res.success {
eprintln!("Request {} failed: {:?}", res.iteration, res.error);
}
}
}
}
).await?;
println!("\nTest Complete!");
println!("Average Latency: {:?}", result.avg);
println!("P95 Latency: {:?}", result.p95);
Ok(())
}For more complex scenarios, you can use a custom request generator to dynamically create requests (e.g., with unique IDs or timestamps in the body).
use load_rs::{LoadTestRunner, Stats};
use reqwest::{Client, Method};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let runner = LoadTestRunner::builder("http://localhost:8080", 1000, 50)
.stats(Stats::All)
.build()
.await?;
// You can share a client or any other state in the generator
let client = Client::new();
let result = runner.run_with_generator(
move |iteration| {
// This closure is called for each request
let req = client.request(Method::POST, "http://localhost:8080/ingest")
.header("X-Iteration", iteration.to_string())
.body(format!("{{\"id\": {}, \"timestamp\": {}}}",
iteration,
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs()))
.build()?;
// Return the request and an optional filename for saving the response
Ok((req, None))
},
Some(std::path::Path::new("responses.jsonl")), // output_file
|_| {} // event callback
).await?;
println!("Total Success: {}", result.success);
Ok(())
}Contributions are welcome! Please feel free to submit a pull request or open an issue.
If you are an AI agent, please refer to AGENTS.md for specific guidelines.
This project is licensed under the Apache License 2.0. See the LICENSE file for details.

