Plumbrs is a high-performance HTTP/HTTP2 request generator designed for benchmarking servers and comparing Rust HTTP client libraries. Built on Tokio, it helps you measure throughput, latency, and identify bottlenecks.
- Auto (
auto) — Automatically select the best client (default). Useshyperforhttps://URIs. - Hyper (
hyper) — Hyper-based HTTP client (one per connection). Supports HTTPS. - Hyper MCP (
hyper-mcp) — Hyper client for MCP (Model Context Protocol) servers (requiresmcpfeature). Supports HTTPS. - Hyper chunked (
hyper-chunked) — Hyper client with multi-chunked body (one per connection). Both HTTP/1 and HTTP/2. Supports HTTPS. - Hyper legacy (
hyper-legacy) — Legacy Hyper HTTP client (one per connection). Both HTTP/1 and HTTP/2. HTTP only (no HTTPS). - Hyper RT1 (
hyper-rt1) — Legacy Hyper HTTP client shared across a runtime. Both HTTP/1 and HTTP/2. HTTP only (no HTTPS). - Hyper H2 (
hyper-h2) — HTTP/2 client using Hyper with the h2 library (one per connection). Supports HTTPS. - Reqwest (
reqwest) — Popular Reqwest HTTP client (one per runtime). Supports HTTPS. - TokioUring (
tokio-uring) — HTTP client using tokio-uring for high-performance I/O (Linux only, requirestokio_uringfeature). HTTP only. - Monoio (
monoio) — HTTP client using monoio for high-performance I/O (Linux only, requiresmonoiofeature). HTTP only. - Compio (
compio) — HTTP client using compio for high-performance I/O (requirescompiofeature). HTTP only. - Help (
help) — Print available client types and exit.
-
<URI>— HTTP/HTTPS URI(s) for the request (e.g.,http://192.168.0.1:80orhttps://example.com). Required for most clients. HTTPS is supported withauto,hyper,hyper-chunked,hyper-h2,hyper-mcp, andreqwest. Forcing an incompatible client (e.g.tokio-uring,hyper-legacy) exits with an error. -
-t, --threads <NUMBER>(default:1) — Number of worker threads. -
-m, --multi-threaded <NUMBER>— Threads per Tokio runtime. If omitted, uses single-threaded executor. When specified,--threadsmust be an exact multiple of this value. -
-c, --concurrency <NUMBER>(default:1) — Concurrent connections or HTTP/2 streams. -
-d, --duration <SECONDS>— Test duration in seconds. -
-r, --requests <NUMBER>— Maximum requests per worker. If omitted, runs until duration elapses. -
-C, --client <TYPE>(default:auto) — Client type:auto,hyper,hyper-mcp,hyper-chunked,hyper-h2,hyper-legacy,hyper-rt1,reqwest,tokio-uring,monoio,compio, orhelp. -
--rpc <NUMBER>— Requests per connection. After every N requests the connection is closed (Connection: closeis sent on the last request) and a new one is opened. Use--rpc 1to measure Connections Per Second. By default, connections are reused indefinitely. -
--latency— Enable latency estimation using Gil Tene's coordinated omission correction algorithm. -
--host <HOST>— Override the HTTPHostheader (e.g. for virtual hosting). Defaults to the URI hostname/IP. -
--port <PORT>— Override the port to connect to. -
--sni <NAME>— Override the TLS SNI server name. Defaults to the URI hostname. Not available withreqwest. -
--absolute-uri— Send the HTTP/1 request-target in absolute-form (GET http://host/path) instead of origin-form (GET /path). Origin-form is the default. Not available withreqwest,hyper-legacy, orhyper-rt1. -
-k, --insecure— Skip TLS certificate verification. -
-v, --verbose— Enable verbose output. -
--metrics— Display Tokio runtime metrics at the end. -
--stats-csv— Print complete end-of-run statistics as a CSV row (no header). -
--stats-csv-header— Same as--stats-csv, but also print the CSV header.
-
-M, --method <METHOD>— HTTP method (e.g.,GET,POST,PUT,DELETE). If omitted, defaults toGETwhen no body is provided, orPOSTwhen a body is specified. Note:TRACEmethod cannot have a body. -
-H, --header <KEY:VALUE>— Add HTTP header (repeatable). -
-T, --trailer <KEY:VALUE>— Add HTTP trailer (repeatable). Only supported withhyper-chunkedorhyper-h2clients. -
-b, --body <BODY>— Request body content. Can be specified multiple times for multi-chunk encoding, but multi-chunk is only supported withhyper-chunkedclient. Use@pathto read the body from a file (streamed). -
--http2— Use HTTP/2 only. Not available withtokio-uring,monoio, orcompioclients.
Plumbrs supports benchmarking Model Context Protocol (MCP) servers. MCP is a protocol for communication between AI applications and tool servers, using JSON-RPC over HTTP.
When MCP mode is enabled, Plumbrs will:
- Perform the MCP handshake (initialize, initialized notification)
- Discover available tools via
tools/list - Benchmark the server by repeatedly calling the discovered tools
Two transport modes are supported:
-
--mcp— Enable MCP mode with Streamable HTTP transport (recommended). This is the newer transport where JSON-RPC requests and responses flow over standard HTTP POST requests. The server returns a session ID via theMcp-Session-Idheader. -
--mcp-sse— Enable MCP mode with legacy Server-Sent Events (SSE) transport (implies--mcp). This older transport uses a persistent SSE connection for receiving responses while sending requests via separate HTTP POST calls. -
--mcp-rand-string-len <NUMBER>— Fix the length of random strings generated fortools/callarguments. If omitted, a random length between 5 and 20 is used each time. -
--mcp-tool <NAME>(alias:--mcp-tools) — Filter MCP tools to invoke by name; can be repeated or comma-separated (e.g.--mcp-tool tool1,tool2). If not specified or empty, all tools returned by the server are invoked round-robin. Passing this option implies--mcp.
Both options are only available with auto or hyper-mcp client types.
Example — Benchmark an MCP server with Streamable HTTP:
plumbrs -c 10 -d 30 http://localhost:3001/mcp --mcp
Example — Benchmark an MCP server with SSE transport:
plumbrs -c 10 -d 30 http://localhost:3001/sse --mcp-sse
--http1-max-buf-size <NUMBER>— Maximum buffer size (default: ~400kb).--http1-read-buf-exact-size <NUMBER>— Exact read buffer size (unsets max-buf-size).--http1-writev <true|false>— Use vectored writes (default: auto).--http1-title-case-headers— Write header names as title case.--http1-preserve-header-case— Preserve original header case.--http1-max-headers <NUMBER>— Maximum number of headers (default: 100).--http1-allow-spaces-after-header-name-in-responses— Accept spaces after header names.--http1-allow-obsolete-multiline-headers-in-responses— Accept obsolete line folding.--http1-ignore-invalid-headers-in-responses— Silently ignore malformed headers.--http09-responses— Tolerate HTTP/0.9 responses.
--http2-adaptive-window <true|false>— Enable adaptive flow control. Not available withhyper-h2.--http2-initial-max-send-streams <NUMBER>— Initial max locally initiated streams. Not available withreqwest.--http2-max-concurrent-reset-streams <NUMBER>— Max concurrently reset streams. Not available withreqwest.--http2-initial-stream-window-size <NUMBER>— Initial stream-level flow control window.--http2-initial-connection-window-size <NUMBER>— Initial connection-level flow control window.--http2-max-frame-size <NUMBER>— Maximum frame size.--http2-max-header-list-size <NUMBER>— Maximum header list size.--http2-max-send-buffer-size <NUMBER>— Maximum send buffer size. Not available withreqwest.--http2-keep-alive-while-idle— Enable keep-alive while idle. Not available withhyper-h2.
--global-queue-interval <TICKS>— Global queue interval.--event-interval <TICKS>— Event interval.--max-io-events-per-tick <NUMBER>— Maximum I/O events per tick.--disable-lifo-slot— Disable LIFO slot heuristic (requirestokio_unstable).
The tokio-uring, monoio, and compio clients support HTTP/1 only and do not support multi-threaded runtimes (-m).
On Linux, the following options configure io_uring submission queues:
--uring-entries <NUMBER>(default:4096) — Size of the io_uring Submission Queue.--uring-sqpoll <MILLISECONDS>— Enable kernel-side submission polling with idle timeout in milliseconds.
By default the kernel picks an ephemeral source port for every connection.
On the server, Receive Side Scaling (RSS) hashes the 4-tuple
(src IP, dst IP, src port, dst port) to select the RX queue (and CPU) that
handles each connection. Sequential ephemeral ports do not hash uniformly, so
a benchmark can overload a few server queues while others stay idle.
Plumbrs can bind an explicit source port per connection, and — when the server RSS configuration is known — pick ports that spread evenly across the server RX queues:
--local-addr <IP>— Source IP to bind outgoing connections to.--local-port-range <START-END>— Source port range (e.g.40000-41000), assigned round-robin. Must hold at least as many ports as connections.--rss-key <HEX|@FILE>— RSS Toeplitz hash key. Dump it on the server withethtool --show-rxfh <iface>and pass--rss-key @rxfh.txt(or paste the hex inline).--rss-indir <N|LIST|@FILE>— RSS indirection table: a queue count (e.g.8), an explicit queue list (e.g.0,1,2,3), or a file withethtool -xoutput (--rss-indir @rxfh.txt; a--show-rxfhdump contains both the key and the table, so the same file can feed both options).
# on the server, dump the RSS configuration:
ethtool --show-rxfh eth0 > rxfh.txt
# on the load generator, spread 64 connections evenly over the server queues:
plumbrs -c 64 --rss-key @rxfh.txt --rss-indir @rxfh.txt http://server/
# ...optionally restricted to a known-free port range:
plumbrs -c 64 --local-port-range 40000-41000 \
--rss-key @rxfh.txt --rss-indir @rxfh.txt http://server/Notes:
- Source ports are supported by the direct-connect clients (
auto,hyper,hyper-chunked,hyper-h2,hyper-mcpwith themcpfeature, andcompiowith thecompiofeature). The other clients (hyper-legacy,hyper-rt1,reqwest,tokio-uring,monoio) reject--local-port-range/--rss-*;hyper-legacy,hyper-rt1andreqweststill honor--local-addr(source IP only). - If the server closes a connection, the reconnect uses the next port in the
assignment, so a task sticks to its server queue while avoiding
TIME_WAITcollisions (SO_REUSEADDRis set on bound sockets). - Source-port options require persistent connections (default
--rpc): with a finite--rpc Nthey are rejected, since per-connection bind work would be pure overhead and queue stickiness is impossible for single-request connections. - Balancing is computed for the first URI (source IP from
--local-addror auto-detected); with several URIs pointing at different hosts a warning is printed.
Basic GET request with 10 concurrent connections for 30 seconds:
plumbrs -c 10 -d 30 http://localhost:8080
POST request with headers and body:
plumbrs -t 4 -c 100 -M POST \
-H "Content-Type:application/json" \
-b '{"key":"value"}' http://localhost:8080/api
POST with body from file:
plumbrs -M POST -b @./payload.json http://localhost:8080/api
HTTP/2 with flow control tuning:
plumbrs -C hyper --http2 \
--http2-initial-stream-window-size 1048576 \
--http2-initial-connection-window-size 2097152 \
-c 100 -d 30 http://localhost:8080
Connections Per Second test:
plumbrs -C hyper --rpc 1 -c 10 -r 1000 http://localhost:8080
Latency-corrected benchmarking:
plumbrs --latency -c 100 -d 30 http://localhost:8080
The tokio-uring client delivers 382K RPS on a single thread and scales to +1.1M RPS with 4 threads. The hyper client also performs exceptionally (198K → 724K RPS), surpassing wrk. The reqwest client maintains competitive throughput (109K → 397K RPS).
The hyper-h2 client achieves 187K RPS on a single thread and 689K RPS with 4 threads. The standard hyper client with HTTP/2 follows closely (135K → 580K RPS). All Plumbrs HTTP/2 clients outperform rewrk in this benchmark.
Plumbrs uses mimalloc by default for improved memory allocation performance.
| Build command | Allocator |
|---|---|
cargo build --release |
mimalloc (default) |
cargo build --release --no-default-features |
system allocator |
To enable io_uring or completion-based clients, build Plumbrs with the corresponding feature:
# Linux only: io_uring with tokio-uring
cargo build --release --features tokio_uring
# Linux only: io_uring with monoio
cargo build --release --features monoio
# Cross-platform completion-based I/O with compio
cargo build --release --features compioTo enable MCP support, build Plumbrs with the mcp feature:
cargo build --release --features mcp
Some options require Tokio's unstable APIs:
RUSTFLAGS="--cfg tokio_unstable" cargo build --release


