wrk Benchmark

Installation & Basic Usage

# Install wrk # macOS: brew install wrk # Ubuntu: sudo apt install wrk # Build from source: # git clone https://github.com/wg/wrk.git # cd wrk && make # Basic benchmark wrk -t2 -c10 -d30s http://localhost:3000/ # Flags: # -t number of threads (typically 2x CPU cores) # -c total number of connections # -d duration (s, m, h suffix: 30s, 2m, 1h) # -H extra request header wrk -t4 -c100 -d30s http://localhost:3000/api/users wrk -t8 -c200 -d1m -H "Authorization: Bearer token" https://api.example.com/ # Rule of thumb: connections per thread = c / t # -t4 -c100 means 25 connections per thread

Lua Scripts — Custom Requests

-- post.lua — POST JSON request wrk.method = "POST" wrk.headers["Content-Type"] = "application/json" wrk.body = '{"username":"testuser","password":"testpass"}' -- Run with script: -- wrk -t4 -c50 -d30s -s post.lua http://localhost:3000/api/login --- -- headers.lua — dynamic headers function request() local headers = {} headers["Authorization"] = "Bearer " .. os.getenv("API_TOKEN") headers["X-Request-ID"] = math.random(1, 100000) return wrk.format(nil, nil, headers, nil) end --- -- multiple_paths.lua — random endpoints paths = {"/api/users", "/api/products", "/api/orders"} request = function() path = paths[math.random(#paths)] return wrk.format("GET", path) end

Advanced Lua Scripts

-- auth_and_post.lua — login then use token local token = "" setup = function(thread) -- runs once per thread end init = function(args) local body = '{"username":"admin","password":"secret"}' local headers = {["Content-Type"] = "application/json"} local resp = wrk.format("POST", "/api/login", headers, body) -- Note: wrk doesn't support sequential init requests natively -- Use init to set per-thread state end request = function() return wrk.format("GET", "/api/protected", { ["Authorization"] = "Bearer " .. token, }, nil) end response = function(status, headers, body) if status ~= 200 then io.write("Error: " .. status .. "\n") end end done = function(summary, latency, requests) io.write("-----\n") for _, p in pairs({50, 75, 90, 99, 99.9}) do n = latency:percentile(p) io.write(string.format(" %gth percentile: %0.2f ms\n", p, n / 1000)) end end

Interpreting wrk Output

Running 30s test @ http://localhost:3000/ 4 threads and 100 connections Thread Stats Avg Stdev Max +/- Stdev Latency 12.34ms 4.56ms 234.78ms 89.23% Req/Sec 802.45 121.32 1.10k 66.00% Latency Distribution 50% 11.23ms ← P50 median 75% 13.45ms 90% 17.89ms ← P90 99% 34.56ms ← P99 96,294 requests in 30.10s, 21.20MB read ← total requests Socket errors: connect 0, read 0, write 0, timeout 12 Requests/sec: 3198.47 ← throughput (RPS) Transfer/sec: 720.63KB ← bandwidth # Key metrics: # Latency Avg — mean response time (affected by outliers) # Latency 99% — 99th percentile (worst 1% of responses) # Req/sec — throughput # Socket errors — connection problems (bad sign if > 0)

wrk2 — Constant Throughput Mode

# wrk2 adds rate limiting for more realistic load # brew install wrk2 # git clone https://github.com/giltene/wrk2 # Test at exactly 1000 RPS wrk2 -t4 -c100 -d30s -R 1000 http://localhost:3000/ # Test at 500 RPS to observe behavior under controlled load wrk2 -t2 -c50 -d60s -R 500 http://localhost:3000/api/search # wrk2 produces HDR histogram latencies # Better for measuring latency at specific load levels # Use wrk for finding max throughput # Use wrk2 for measuring latency at target RPS

Flag Quick Reference

FlagDescriptionExample
-tNumber of threads-t4
-cTotal connections-c100
-dDuration-d30s, -d2m
-sLua script file-s post.lua
-HHTTP header-H "Auth: Bearer x"
--latencyPrint latency stats--latency
--timeoutSocket timeout--timeout 5s
-R (wrk2)Target RPS-R1000