Plumbrs is a benchmarking tool for measuring and comparing HTTP server performance using a variety of asynchronous HTTP client libraries in Rust. It focuses on realistic workloads across servers and client implementations built on Tokio, helping you identify bottlenecks and optimize performance.
Plumbrs provides ready-to-use benchmarking tasks for several popular HTTP clients, allowing you to test throughput, latency, and runtime efficiency under various configurations.
- Auto (
auto) — Automatically select the best client (default). - Hyper (
hyper) — Hyper-based HTTP client (one per connection). - Hyper (
hyper-multichunk) — Hyper-based HTTP client with multi-chunked body (one per connection). - Hyper (legacy) (
hyper-legacy) — Legacy Hyper HTTP client (one per connection). - Hyper (legacy, one per runtime) (
hyper-rt1) — Legacy Hyper HTTP client shared across a runtime. - Hyper + h2 (
hyper-h2) — HTTP/2 client using Hyper with the h2 library (one per connection). - Reqwest (
reqwest) — Popular Reqwest HTTP client (one per runtime). - Help (
help) — Print available client types and exit.
- Benchmark-oriented design — Compare HTTP client performance under load.
- Multi-runtime support — Run benchmarks using multiple Tokio runtimes (single-threaded or multi-threaded).
- Connection configuration — Control the total number of connections or rely on client-specific pooling.
- Runtime tuning — Customize thread counts, scheduling, and runtime parameters.
- Optional Tokio metrics — Collect fine-grained runtime data with
--metricsoption.
-
URI (positional) HTTP URI for the request (for example,
http://192.168.0.1:80). -
-t, --threads <NUMBER>(default:1) Number of worker threads to run. -
-m, --multi-threaded <NUMBER>Number of threads per Tokio runtime. If omitted, each runtime uses a single-threaded executor. -
-v, --verboseEnable verbose output. -
-c, --concurrency <NUMBER>(default:1) Number of concurrent connections or HTTP/2 streams. -
-d, --duration <SECONDS>Duration of the test in seconds. -
-r, --requests <NUMBER>Maximum number of requests per worker. If omitted, runs indefinitely or until the duration elapses.
-
-M, --method <METHOD>(default:GET) HTTP method to use (for example,GET,POST,PUT,DELETE). -
-H, --header <KEY:VALUE>(repeatable) Add an HTTP header to the request. Can be specified multiple times. Format:KEY:VALUE(for example,-H "Accept:application/json" -H "X-Token:abc"). -
-T, --trailer <KEY:VALUE>(repeatable) Add an HTTP trailer to the request. Can be specified multiple times. Format:KEY:VALUE(for example,-T "Trailer-Name:value"). Note: Not available withreqwestclient. -
-B, --body <BODY>(repeatable) Body content for the HTTP request. Can be specified multiple times for multi-chunk (chunked transfer) encoding. -
-b, --body-from-file <PATH>File path for the body of the request. The file content will be streamed as the request body. -
--host <HOST>Set the host to benchmark (for example,http://192.168.0.1:8080). Note: Not available withhyper-legacyorhyper-rt1.
-
-C, --client <TYPE>(default:auto) Client type to use for benchmarking. Options:auto— Automatically select the best client (default).hyper— Hyper client; one per connection. Supports HTTP/1 and HTTP/2.hyper-multichunk— Hyper client; one per connection, with multi-chunked body. Supports HTTP/1 and HTTP/2.hyper-h2— Hyper client using the h2 library; one per connection. HTTP/2 only.hyper-legacy— Legacy Hyper client; one per connection. Supports HTTP/1 and HTTP/2.hyper-rt1— Legacy Hyper client; one per runtime. Supports HTTP/1 and HTTP/2.reqwest— Reqwest client; one per runtime. Supports HTTP/1 and HTTP/2.help— Show available client types and exit.
-
--cpsOpen a new connection for every request, measuring Connections Per Second (CPS). -
--latencyEnable latency estimation using Gil Tene's coordinated omission correction algorithm.
-
--http1-max-buf-size <NUMBER>Set the maximum buffer size for HTTP/1. Default is ~400kb. -
--http1-read-buf-exact-size <NUMBER>Set the exact size of the read buffer to always use for HTTP/1. Note: Setting this option unsets thehttp1-max-buf-sizeoption. -
--http1-writev <true|false>Set whether HTTP/1 connections should try to use vectored writes. Default isauto(hyper will try to guess which mode to use). -
--http1-title-case-headersSet whether HTTP/1 connections will write header names as title case at the socket level. -
--http1-preserve-header-caseSet whether to support preserving original header cases for HTTP/1. Note: Not available withhyper-legacyorhyper-rt1clients. -
--http1-max-headers <NUMBER>Set the maximum number of headers for HTTP/1. Default is 100. -
--http1-allow-spaces-after-header-name-in-responsesSet whether HTTP/1 connections will accept spaces between header names and the colon that follows them in responses. -
--http1-allow-obsolete-multiline-headers-in-responsesSet whether HTTP/1 connections will accept obsolete line folding for header values. -
--http1-ignore-invalid-headers-in-responsesSet whether HTTP/1 connections will silently ignore malformed header lines. -
--http09-responsesSet whether HTTP/0.9 responses should be tolerated.
| Option | hyper | hyper-legacy | hyper-rt1 | reqwest |
|---|---|---|---|---|
--http1-max-buf-size |
✅ | ✅ | ✅ | ❌ |
--http1-read-buf-exact-size |
✅ | ✅ | ✅ | ❌ |
--http1-writev |
✅ | ✅ | ✅ | ❌ |
--http1-title-case-headers |
✅ | ✅ | ✅ | ✅ |
--http1-preserve-header-case |
✅ | ✅ | ✅ | ❌ |
--http1-max-headers |
✅ | ✅ | ✅ | ❌ |
--http1-allow-spaces-after-header-name-in-responses |
✅ | ✅ | ✅ | ✅ |
--http1-allow-obsolete-multiline-headers-in-responses |
✅ | ✅ | ✅ | ✅ |
--http1-ignore-invalid-headers-in-responses |
✅ | ✅ | ✅ | ✅ |
--http09-responses |
✅ | ✅ | ✅ | ✅ |
-
--http2Use HTTP/2 only. -
--http2-adaptive-window <true|false>Enable or disable adaptive flow control for HTTP/2. Note: Not available withhyper-h2client. -
--http2-initial-max-send-streams <NUMBER>Set the initial maximum number of locally initiated (send) streams. Note: Not available withreqwestclient. -
--http2-max-concurrent-reset-streams <NUMBER>Set the initial maximum number of concurrently reset streams. Note: Not available withreqwestclient. -
--http2-initial-stream-window-size <NUMBER>Set the initial window size for HTTP/2 stream-level flow control. -
--http2-initial-connection-window-size <NUMBER>Set the initial window size for HTTP/2 connection-level flow control. -
--http2-max-frame-size <NUMBER>Set the maximum frame size for HTTP/2. -
--http2-max-header-list-size <NUMBER>Set the maximum header list size for HTTP/2. -
--http2-max-send-buffer-size <NUMBER>Set the maximum send buffer size for HTTP/2. Note: Not available withreqwestclient. -
--http2-keep-alive-while-idleEnable HTTP/2 keep-alive while the connection is idle. Note: Not available withhyper-h2client.
| Option | hyper | hyper-h2 | hyper-legacy | hyper-rt1 | reqwest |
|---|---|---|---|---|---|
--http2-adaptive-window |
✅ | ❌ | ✅ | ✅ | ✅ |
--http2-initial-max-send-streams |
✅ | ✅ | ✅ | ✅ | ❌ |
--http2-max-concurrent-reset-streams |
✅ | ✅ | ✅ | ✅ | ❌ |
--http2-initial-stream-window-size |
✅ | ✅ | ✅ | ✅ | ✅ |
--http2-initial-connection-window-size |
✅ | ✅ | ✅ | ✅ | ✅ |
--http2-max-frame-size |
✅ | ✅ | ✅ | ✅ | ✅ |
--http2-max-header-list-size |
✅ | ✅ | ✅ | ✅ | ✅ |
--http2-max-send-buffer-size |
✅ | ✅ | ✅ | ✅ | ❌ |
--http2-keep-alive-while-idle |
✅ | ❌ | ✅ | ✅ | ✅ |
-
--global-queue-interval <TICKS>Tokio global queue interval (in ticks). -
--event-interval <TICKS>Tokio event interval (in ticks). -
--max-io-events-per-tick <NUMBER>Maximum number of I/O events processed per tick. -
--disable-lifo-slot(requirestokio_unstable) Disable Tokio’s LIFO slot heuristic.
The following CLI constraints are enforced at runtime:
- URI required for most clients:
- For
hyper,hyper-legacy,hyper-rt1, andhyper-h2, a URI (positional argument) must be provided.
- For
- Host option restrictions:
--hostis not available withhyper-legacyorhyper-rt1.
- CPS restriction:
--cpscan only be used with--client hyper.
- Trailer restrictions:
-T, --traileris not available with thereqwestclient.
- Threading consistency:
- When
--multi-threaded <N>is provided,--threadsmust be an exact multiple of<N>.
- When
Basic GET request with 10 concurrent connections for 30 seconds:
plumbrs -c 10 -d 30 http://localhost:8080POST request with headers and a body using 4 threads and 100 concurrent connections:
plumbrs -t 4 -c 100 -M POST \
-H "Content-Type:application/json" -H "Accept:application/json" \
-B '{"key":"value"}' http://localhost:8080/apiPOST request with body from file:
plumbrs -t 4 -c 100 -M POST \
-H "Content-Type:application/json" \
-b ./payload.json http://localhost:8080/apiHTTP/2 with advanced flow control and tuning options:
plumbrs -C hyper --http2 \
--http2-adaptive-window true \
--http2-initial-stream-window-size 1048576 \
--http2-initial-connection-window-size 2097152 \
--http2-max-frame-size 32768 \
--http2-keep-alive-while-idle \
-c 100 -d 30 http://localhost:8080Connections Per Second (CPS) test:
plumbrs --cps -c 10 -r 1000 http://localhost:8080List available client types:
plumbrs -C helpHTTP/1 with custom buffer size and header options:
plumbrs -C hyper \
--http1-max-buf-size 524288 \
--http1-title-case-headers \
--http1-max-headers 150 \
-c 100 -d 30 http://localhost:8080Latency-corrected benchmarking:
plumbrs --latency -c 100 -d 30 http://localhost:8080Using HTTP trailers:
plumbrs -C hyper -T "X-Checksum:abc123" -T "X-Signature:xyz789" http://localhost:8080Some runtime options require Tokio’s unstable APIs. Build with:
RUSTFLAGS="--cfg tokio_unstable" cargo build --release
