http压力测试工具wrk

2016-11-19 16:56:36

前言
wrk是一款开源的压力测试工具,它没有Load Runner那么复杂,和apache的ab 一样简单上手,确比ab功能更加强大,足以应对开发过程中的性能验证了:

  • 集成了多线程设计和事件通知系统(epoll,kqueue)
  • 通过lua脚本进行扩展 eg. http请求的生产、响应处理,自定义报告等

下载安装

1
2
3
4
# git clone https://github.com/wg/wrk.git
# cd wrk/
# make -j8
# ./wrk -t12 -c100 -d10s http://www.baidu.com

要测试的网站当然是百度了,据说百度是局域网看网络通不通的首选哦,没有之一
我们启动 12 个线程,100 个并发,持续运行 10 秒。线程数一般设置为cpu核数的2-4倍,如果想看响应时间的分布情况可以加上–latency参数

[root@localhost wrk]# ./wrk -t12 -c100 -d10s http://www.baidu.com
Running 10s test @ http://www.baidu.com
12 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 367.77ms 417.94ms 1.97s 84.84%
Req/Sec 9.79 6.43 40.00 65.23%
760 requests in 10.12s, 11.42MB read
Socket errors: connect 0, read 0, write 0, timeout 41
Requests/sec: 75.13
Transfer/sec: 1.13MB

Latency: 可以理解为响应时间, 有平均值, 标准偏差, 最大值, 正负一个标准差占比
Requests/sec 就是最基本的指标:每秒处理的请求数(QPS)
Thread Stats 是线程执行情况,包括延迟、每秒处理个数,其中的 Avg 和 Max 很好理解,是平均值和最大值,Stdev 是标准差。
一般我们来说我们主要关注平均值和最大值. 标准差如果太大说明样本本身离散程度比较高. 有可能系统性能波动很大

测试场景-Post

1
2
3
4
5
6
# cat post.lua
wrk.method = "POST"
wrk.body = "foo=bar&baz=quux"
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"

# ./wrk -t12 -c100 -d30s -T30s --script=post.lua --latency http://www.baidu.com

复合场景-lua 实现访问多个 url.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
counter = 1

math.randomseed(os.time())
math.random(); math.random(); math.random()

function file_exists(file)
local f = io.open(file, "rb")
if f then f:close() end
return f ~= nil
end

function shuffle(paths)
local j, k
local n = #paths
for i = 1, n do
j, k = math.random(n), math.random(n)
paths[j], paths[k] = paths[k], paths[j]
end
return paths
end

function non_empty_lines_from(file)
if not file_exists(file) then return {} end
lines = {}
for line in io.lines(file) do
if not (line == '') then
lines[#lines + 1] = line
end
end
return shuffle(lines)
end

paths = non_empty_lines_from("paths.txt")

if #paths <= 0 then
print("multiplepaths: No paths found. You have to create a file paths.txt with one path per line")
os.exit()
end

print("multiplepaths: Found " .. #paths .. " paths")

request = function()
path = paths[counter]
counter = counter + 1
if counter > #paths then
counter = 1
end
return wrk.format(nil, path)
end

场景-cookie
我们需要模拟一些通过 cookie 传递数据的场景. wrk 并没有特殊支持, 可以通过 wrk.headers[“Cookie”]=”xxxxx”实现. 例子是取 Response的cookie作为后续请求的cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function getCookie(cookies, name)
local start = string.find(cookies, name .. "=")

if start == nil then
return nil
end

return string.sub(cookies, start + #name + 1, string.find(cookies, ";", start) - 1)
end

response = function(status, headers, body)
local token = getCookie(headers["Set-Cookie"], "token")

if token ~= nil then
wrk.headers["Cookie"] = "token=" .. token
end
end

通过源码可以看到 wrk 对象的源代码有如下属性

1
2
3
4
5
6
7
8
9
10
local wrk = {
scheme = "http",
host = "localhost",
port = nil,
method = "GET",
path = "/",
headers = {},
body = nil,
thread = nil,
}

schema, host, port, path 这些, 我们一般都是通过 wrk 命令行参数来指定,wrk 还提供了几个lua的hook函数:
setup //在目标 IP 地址已经解析完, 并且所有 thread 已经生成, 但是还没有开始时被调用. 每个线程执行一次这个函数.可以通过thread:get(name), thread:set(name, value)设置线程级别的变量
init //每次请求发送之前被调用
delay //返回一个数值, 在这次请求执行完以后延迟多长时间执行下一个请求. 可以对应 thinking time 的场景
request //函数可以每次请求之前修改本次请求的属性. 返回一个字符串. 这个函数要慎用, 会影响测试端性能
response //每次请求返回以后被调用. 可以根据响应内容做特殊处理, 比如遇到特殊响应停止执行测试, 或输出到控制台等等
done //在所有请求执行完以后调用, 一般用于自定义统计结果

已经迫不及待了吧,让我们看看源码给的例子吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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

ref
wrk 官网


您的鼓励是我写作最大的动力

俗话说,投资效率是最好的投资。 如果您感觉我的文章质量不错,读后收获很大,预计能为您提高 10% 的工作效率,不妨小额捐助我一下,让我有动力继续写出更多好文章。