wrk 是一个类似 ab(apache bench)、jmeter 的压力测试工具,底层基于 epoll 和 kqueue 实现,能充分利用 cpu 资源,降低测试工具本身性能开销对测试结果准确性的影响。支持使用 lua 脚本自定义测试逻辑,使用上非常简单,但功能足够强大。
安装
用法
$ wrk -h
wrk: invalid option -- h
Usage: wrk
Options:
-c, --connections Connections to keep open
-d, --duration Duration of test
-t, --threads Number of threads to use
-s, --script Load Lua script file
-H, --header Add header to request
--latency Print latency statistics
--timeout Socket/request timeout
-v, --version Print version details
参数
说明
-c
与服务器保持的 http 连接数
-d
压测时间
-t
使用线程数
-s
自定义 lua 脚本路径
-H
自定义 http header 请求头,例如:"User-Agent: benchmark-wrk"
--latency
打印延迟统计数据
--timeout
http 超时时间
--version
打印版本信息
eg: wrk -t2 -c5 -d10s https://httpbin.org/get
这种情况只适用于每次请求都相同的情况
$ wrk -t2 -c5 -d10s https://httpbin.org/get
Running 10s test @ https://httpbin.org/get
2 threads and 5 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 251.97ms 50.38ms 510.96ms 94.52%
Req/Sec 7.60 2.40 10.00 75.23%
146 requests in 10.05s, 61.17KB read
Requests/sec: 14.52
Transfer/sec: 6.08KB
编写 lua 测试脚本
编写 lua 脚本可以实现复杂的测试场景,例如:需要登录认证的接口,查询不用 id 的数据(相同 id 服务端可能有缓存,达不到真实压测效果)
先看官方一个简单的自定义脚本
wrk.method = "POST"
wrk.body = "foo=bar&baz=quux"
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"
$ wrk -d3s -c2 -s scripts/post.lua https://httpbin.org/get
wrk 是一个内置的全局 table 类型变量,不需要定义可以直接使用,修改 wrk 变量的值,会对所有请求都生效。
wrk = {
scheme = "http",
host = "localhost",
port = nil,
method = "GET",
path = "/",
headers = {},
body = nil,
thread =
}
wrk 内置函数
wrk.format
function wrk.format(method, path, headers, body)
wrk.format returns a HTTP request string containing the passed parameters merged with values from the wrk table.
返回一个 http 请求字符串,参数会覆盖 wrk 全局配置,可以通过 format 可以构造出不同的 request
wrk.lookup
function wrk.lookup(host, service)
wrk.lookup returns a table containing all known addresses for the host and service pair. This corresponds to the POSIX getaddrinfo() function.
返回所有可用服务器的地址信息
wrk.connect
function wrk.connect(addr)
wrk.connect returns true if the address can be connected to, otherwise it returns false. The address must be one returned from wrk.lookup().
测试指定的服务器地址是否能正常连接
参考: local addrs = nil
function setup(thread)
if not addrs then
addrs = wrk.lookup(wrk.host, wrk.port or "http")
for i = #addrs, 1, -1 do
if not wrk.connect(addrs[i]) then
table.remove(addrs, i)
end
end
end
thread.addr = addrs[math.random(#addrs)]
end
function init(args)
local msg = "thread addr: %s"
print(msg:format(wrk.thread.addr))
end
生命周期回调函数
wrk 包括下面几个生命周期,在脚本中重新定义这些全局函数,可以修改 wrk 默认行为,实现个性化测试需求。
The following globals are optional, and if defined must be functions:
global setup -- called during thread setup
global init -- called when the thread is starting,
global delay -- called to get the request delay,
global request -- called to generate the HTTP request,
global response -- called with HTTP response data,
global done -- called with results of run
启动阶段
setup 每个线程初始化时执行一次
function setup(thread)
setup 方法会传入一个 thread 对象,可以修改或设置 thread 相关参数,也可以终止线程执行,这里一般做一些初始化的工作,例如读取配置文件,加载到内存(不要每次请求的时候读取一遍,这样对测试准确性影响很大) thread.addr - get or set the thread's server address,获取或设置服务器地址信息
thread:get(name) - get the value of a global in the thread's env,获取当前线程参数
thread:set(name, value) - set the value of a global in the thread's env,设置当前线程参数
thread:stop() - stop the thread,终止线程
执行阶段
init 每个线程开始启动时执行一次
function init(args)
args 是通过命令行传入的参数,通过 -- 指定
例如:wrk -d3s -c2 -s wrk.lua https://httpbin.org/get -- test 100 function init(args)
for i, v in ipairs(args) do
print(i,v)
end
end
-- 输出
-- 1test
-- 2100
delay 每次发送请求时,间隔时间(ms),每次请求执行一次
function delay()
返回值决定每次请求间隔
request 创建 request 时(发送 request 前)执行,每次请求执行一次
function request()
一般在这里会配合 wrk.format 方法,动态创建请求,这里不要执行耗时的代码,否则会影响测试结果准确性
response http 响应时执行,每次请求执行一次
function response(status, headers, body)
http 响应处理逻辑,参数对应 http 响应的 status, headers, body。
解析 header 和 body 的开销比较大,如果脚本没有定义 response 方法,wrk 将不会解析 header 和 body,这样测试结果会更加准确(解析响应数据是客户端负责的,不能算到服务器处理时间里面)
结束阶段
done 返回结果时执行,整个测试过程只执行一次,可以生成自定义测试报告,如果没有特别需求,一般不重写这个方法
function done(summary, latency, requests)
参数含义 latency.min -- minimum value seen
latency.max -- maximum value seen
latency.mean -- average value seen
latency.stdev -- standard deviation
latency:percentile(99.0) -- 99th percentile value
latency(i) -- raw value and count
summary = {
duration = N, -- run duration in microseconds
requests = N, -- total completed requests
bytes = N, -- total bytes received
errors = {
connect = N, -- total socket connection errors
read = N, -- total socket read errors
write = N, -- total socket write errors
status = N, -- total HTTP status codes > 399
timeout = N -- total request timeouts
}
}
官方示例
local counter = 1
local threads = {}
function setup(thread)
thread:set("id", counter)
table.insert(threads, thread)
counter = counter + 1
end
function init(args)
requests = 0
responses = 0
local msg = "thread %d created"
print(msg:format(id))
end
function request()
requests = requests + 1
return wrk.request()
end
function response(status, headers, body)
responses = responses + 1
end
function done(summary, latency, requests)
for index, thread in ipairs(threads) do
local id = thread:get("id")
local requests = thread:get("requests")
local responses = thread:get("responses")
local msg = "thread %d made %d requests and got %d responses"
print(msg:format(id, requests, responses))
end
end