wrk 是一个比较先进的 HTTP 压力测试工具。wrk负载测试时可以运行在一个或者多核CPU,wrk结合了可伸缩的事件通知系统epoll和kqueue等多线程设计思想。目前wrk可以安装在Linux系统和Mac系统。 只有一个命令行, 就能做很多基本的 http 性能测试.
wrk 的开源的, 代码在 github 上. https://github.com/wg/wrk
首先要说的一点是: wrk 只能运行在 Unix 类的系统上. 比如 linux, mac, solaris 等. 也只能在这些系统上编译.
这 里不得不说一下, 为什么很多人说 mac 是最好的开发环境. 不是因为使用 mac 逼格有多高. 而是你可以同时得到 windows 和 linux 的好处. 多数 linux 下的开发工具都可以在 mac 上使用. 很多都是预编译好的, 有些只需要编译一下就能用.
wrk 的一个很好的特性就是能用很少的线程压出很大的并发量. 原因是它使用了一些操作系统特定的高性能 io 机制, 比如 select, epoll, kqueue 等. 其实它是复用了 redis 的 ae 异步事件驱动框架. 确切的说 ae 事件驱动框架并不是 redis 发明的, 它来至于 Tcl的解释器 jim, 这个小巧高效的框架, 因为被 redis 采用而更多的被大家所熟知.
要用 wrk, 首先要编译 wrk.
你的机器上需要已经安装了 git 和基本的c编译环境. wrk 本身是用 c 写的. 代码很少. 并且没有使用很多第三方库. 所以编译基本不会遇到什么问题.
1. git clone https://github.com/wg/wrk.git
2. cd wrk
3. make
就 ok了.
make 成功以后在目录下有一个 wrk 文件. 就是它了. 你可以把这个文件复制到其他目录, 比如 bin 目录. 或者就这个目录下执行.
如果编译过程中出现:
1. src/wrk.h:11:25: fatal error: openssl/ssl.h: No such file or directory
2. #include
是因为系统中没有安装openssl的库.
sudo apt-get install libssl-dev
或
sudo yum install openssl-devel
安装完成配置环境变量 /etc/profile
export PATH=$PATH:/letv/wrk
生效profile 文件
source /etc/profile
我们先来做一个简单的性能测试:
1.wrk -t2 -c10 -d30s -T30s --latency http://10.58.100.90/jsondatedemo.txt
等待完成本次测试
Running 30s test @ http://10.58.100.90/jsondatedemo.txt
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 209.38ms 300.23ms 1.99s 90.36%
Req/Sec 21.81 10.55 60.00 62.24%
Latency Distribution
50% 106.14ms
75% 188.97ms
90% 491.26ms
99% 1.56s
1218 requests in 30.04s, 1.76MB read
Socket errors: connect 0, read 540, write 0, timeout 30
Non-2xx or 3xx responses: 540
Requests/sec: 40.55
Transfer/sec: 60.02KB
------------------------------------------------------------------------------------------------
参数含义:
-t 线程模式 -c模拟并发数 -d 持续时间 m 分钟 s秒 -T 设置延时时间,默认1秒 --latency 显示百分之多少用户的响应时间
-------------------------------------------------------------------------------------------------
-c 一般线程数不宜过多. 核数的2到4倍足够了. 多了反而因为线程切换过多造成效率降低. 因为 wrk 不是使用每个连接一个线程的模型, 而是通过异步网络 io 提升并发量. 所以网络通信不会阻塞线程执行. 这也是 wrk 可以用很少的线程模拟大量网路连接的原因. 而现在很多性能工具并没有采用这种方式, 而是采用提高线程数来实现高并发. 所以并发量一旦设的很高, 测试机自身压力就很大. 测试效果反而下降.
-T wrk 默认超时时间是1秒. 这个有点短. 我一般设置为30秒. 这个看上去合理一点.
Thread Stats Avg Stdev Max +/- Stdev
Latency 209.38ms 300.23ms 1.99s 90.36%
Req/Sec 21.81 10.55 60.00 62.24%
Latency: 可以理解为响应时间, 有平均值, 标准偏差, 最大值, 正负一个标准差占比.
Req/Sec: 每个线程每秒钟的完成的请求数, 同样有平均值, 标准偏差, 最大值, 正负一个标准差占比.
一般我们来说我们主要关注平均值和最大值. 标准差如果太大说明样本本身离散程度比较高. 有可能系统性能波动很大.
--latency
Latency Distribution
50% 106.14ms
75% 188.97ms
90% 491.26ms
99% 1.56s
30秒钟总共完成请求数1218和读取数据量30.04s.
然后是错误统计, 上面的统计可以看到, 540个读错误, 30个超时.
然后是所以线程总共平均每秒钟完成40.55个请求. 每秒钟读取60.02兆数据量.
1218 requests in 30.04s, 1.76MB read
Socket errors: connect 0, read 540, write 0, timeout 30
Non-2xx or 3xx responses: 540
Requests/sec: 40.55
Transfer/sec: 60.02KB
wrk 支持lua语言脚本 ,在这个脚本里你可以修改 method, header, body, 可以对 response 做一下自定义的分析. 因为是 lua 脚本, 其实这给了你无限的可能. 但是这样一个强大的功能如果不谨慎使用, 会降低测试端的性能, 测试结果也受到影响.
一般修改method, header, body不会影响测试端性能, 但是操作 request, response 就要格外谨慎了.
我们通过一些测试场景在看看怎么使用 lua 脚本.
POST + header + body.
首先创建一个 post.lua 的文件:
1. wrk.method = "POST"
2. wrk.body = "devId=C80E7776704A"
3. wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"
就这三行就可以了, 当然 headers 可以加入任意多的内容.
然后执行:
1. wrk -t12 -c100 -d30s -T30s --script=post.lua --latency http://h5.itv.cp21.ott.cibntv.net/api/lottery/draw.json?
---------------------------------------------------------------------------------------------------------------------------------------------
下面没有验证过:
wrk 提供的几个 lua 的 hook 函数:
setup 函数
这个函数在目标 IP 地址已经解析完, 并且所有 thread 已经生成, 但是还没有开始时被调用. 每个线程执行一次这个函数.
可以通过thread:get(name), thread:set(name, value)设置线程级别的变量.
init 函数
每次请求发送之前被调用.
可以接受 wrk 命令行的额外参数. 通过 -- 指定.
delay函数
这个函数返回一个数值, 在这次请求执行完以后延迟多长时间执行下一个请求. 可以对应 thinking time 的场景.
request函数
通过这个函数可以每次请求之前修改本次请求的属性. 返回一个字符串. 这个函数要慎用, 会影响测试端性能.
response函数
每次请求返回以后被调用. 可以根据响应内容做特殊处理, 比如遇到特殊响应停止执行测试, 或输出到控制台等等.
1. function response(status, headers, body)
2. if status ~= 200 then
3. print(body)
4. wrk.thread:stop()
5. end
6. end
done函数
在所有请求执行完以后调用, 一般用于自定义统计结果.
1. done = function(summary, latency, requests)
2. io.write("------------------------------\n")
3. for _, p in pairs({ 50, 90, 99, 99.999 }) do
4. n = latency:percentile(p)
5. io.write(string.format("%g%%,%d\n", p, n))
6. end
7. end
下面是 wrk 源代码中给出的完整例子:
1. local counter = 1
2. local threads = {}
3.
4. function setup(thread)
5. thread:set("id", counter)
6. table.insert(threads, thread)
7. counter = counter + 1
8. end
9.
10. function init(args)
11. requests = 0
12. responses = 0
13.
14. local msg = "thread %d created"
15. print(msg:format(id))
16. end
17.
18. function request()
19. requests = requests + 1
20. return wrk.request()
21. end
22.
23. function response(status, headers, body)
24. responses = responses + 1
25. end
26.
27. function done(summary, latency, requests)
28. for index, thread in ipairs(threads) do
29. local id = thread:get("id")
30. local requests = thread:get("requests")
31. local responses = thread:get("responses")
32. local msg = "thread %d made %d requests and got %d responses"
33. print(msg:format(id, requests, responses))
34. end
35. end
测试复合场景时, 也可以通过 lua 实现访问多个 url.
例如这个复杂的 lua 脚本, 随机读取 paths.txt 文件中的 url 列表, 然后访问.:
1. counter = 1
2.
3. math.randomseed(os.time())
4. math.random(); math.random(); math.random()
5.
6. function file_exists(file)
7. local f = io.open(file, "rb")
8. if f then f:close() end
9. return f ~= nil
10. end
11.
12. function shuffle(paths)
13. local j, k
14. local n = #paths
15. for i = 1, n do
16. j, k = math.random(n), math.random(n)
17. paths[j], paths[k] = paths[k], paths[j]
18. end
19. return paths
20. end
21.
22. function non_empty_lines_from(file)
23. if not file_exists(file) then return {} end
24. lines = {}
25. for line in io.lines(file) do
26. if not (line == '') then
27. lines[#lines + 1] = line
28. end
29. end
30. return shuffle(lines)
31. end
32.
33. paths = non_empty_lines_from("paths.txt")
34.
35. if #paths <= 0 then
36. print("multiplepaths: No paths found. You have to create a file paths.txt with one path per line")
37. os.exit()
38. end
39.
40. print("multiplepaths: Found " .. #paths .. " paths")
41.
42. request = function()
43. path = paths[counter]
44. counter = counter + 1
45. if counter > #paths then
46. counter = 1
47. end
48. return wrk.format(nil, path)
49. end
关于 cookie
有些时候我们需要模拟一些通过 cookie 传递数据的场景. wrk 并没有特殊支持, 可以通过 wrk.headers["Cookie"]="xxxxx"实现.
下面是在网上找的一个例子, 取 Response的cookie作为后续请求的cookie
1. function getCookie(cookies, name)
2. local start = string.find(cookies, name .. "=")
3.
4. if start == nil then
5. return nil
6. end
7.
8. return string.sub(cookies, start + #name + 1, string.find(cookies, ";", start) - 1)
9. end
10.
11. response = function(status, headers, body)
12. local token = getCookie(headers["Set-Cookie"], "token")
13.
14. if token ~= nil then
15. wrk.headers["Cookie"] = "token=" .. token
16. end
17. end
wrk 本身的定位不是用来替换 loadrunner 这样的专业性能测试工具的. 其实有这些功能已经完全能应付平时开发过程中的一些性能验证了.
转:http://sanwen8.cn/p/1d4kAUJ.html
安装:224 /letv/wrk