wrk -s: Scripting Requests with Lua
wrk -s script.lua loads a Lua script that can set the method, headers, and body per request via wrk.format() and summarize results in a done() hook.
A plain URL benchmark only exercises GETs. To load-test a POST endpoint or rotate paths, wrk embeds LuaJIT: define request() to shape each call and done() to emit a machine-readable summary CI can gate on.
What it does
wrk exposes a Lua API. The request() function returns the bytes for each request (usually via wrk.format(method, path, headers, body)), response() sees each reply, and done(summary, latency, requests) runs once at the end so you can print custom metrics and exit codes are still up to your outer script.
Common usage
-- post.lua
wrk.method = "POST"
wrk.body = '{"user":"ci"}'
wrk.headers["Content-Type"] = "application/json"
function done(summary, latency, requests)
io.write(string.format("p99_ms=%.2f\n", latency:percentile(99) / 1000))
io.write(string.format("errors=%d\n", summary.errors.status))
endOptions
| Symbol | What it does |
|---|---|
| wrk.method / wrk.body | Set a static method and body for every request |
| wrk.headers[name] | Set a static request header |
| request() | Return raw request bytes, usually via wrk.format() |
| response(status, headers, body) | Called for each response |
| done(summary, latency, requests) | Final hook; latency:percentile(N) gives p-values |
In CI
Print a parseable line from done() (like p99_ms=...) and have the workflow step read it and exit 1 when it exceeds budget. Note wrk latency values are microseconds; divide by 1000 for milliseconds.
Common errors in CI
wrk: <script>.lua: No such file or directory means a wrong relative path; wrk resolves it from the working directory, not the workflow root. attempt to call a nil value (field 'format') means a typo like wrk.Format; the API is case-sensitive. A script that does heavy work in request() throttles throughput, so keep per-request Lua minimal.