wrk 介绍

在了解 wrk 之前,我对性能测试的了解一直局限在 ab(Apache Benchmark) 上,我印象中 ab 的功能比较简单,就是简单地压测给定的 API,然后看并发量是多少,这在一些全都是 get 的场景下倒是没什么问题,但是如果要涉及一些更复杂的测试就比较难搞了,所以我就了解了一下,然后看到别人安利了一下 wrk,所以就顺便记录一下关于 wrk 的东西。

功能概述

wrk 的基本功能其实也很 ab 很类似,可以简单地做一些 GET 的大量请求,然后算 QPS(Request Per Seconds) 和 RT(Response Time) 之类的,但是 wrk 不能直接执行 POST 请求(ab 可以)。所以,这里就需要进一步了解到 wrk 还有一个比较重要的功能:它支持通过 lua 脚本来做扩展,通过 lua 脚本,你可以做到:

这些都是 lua 脚本的扩展功能,在这方便,会比 ab 会好用很多。有意思的一件事情是,如果你去找 wrk,你还会发现一个 wrk2 的东西,它其实是 wrk 的修改版本,作者自己加入了一个所谓的 constant throughput 的功能,其实就是不测试 QPS 了,而是保持一定的 QPS,然后看 server 端的响应速度如何,表现怎么样,所以,一般情况下,我是不需要 wrk2 的。

基本功能演示

光说不练假把式,所以这里我只能演示一个简单的 GET 请求,其实就是看下 wrk 是如何执行这两个简单的操作的,至于 POST 请求,我需要在下一章节 扩展功能 中介绍:

扩展功能演示

lua 脚本执行逻辑

wrk 的 POST 请求不能直接通过命令行的参数执行,所以需要通过 lua 脚本的方式来运行,再看 lua 的编写和执行之前,先来了解一些 wrk 的 lua 的上下文概念:

lua 脚本框架

看完理论部分,解析来看下,每个阶段对应的 lua 框架是什么样的,也就是说,wrk 规定的 lua 脚本应该怎么写。

启动阶段

lua 线程启动之后,就会调用这个阶段的自定义函数,这个节点只有一个自定义函数:

  1. [[email protected].io]# cat setup.lua
  2. local counter = 1
  3. function setup(thread)
  4. thread:set("id", counter)
  5. table.insert(threads, thread)
  6. counter = counter + 1
  7. end

可以看到,在启动阶段,你可以修改 setup 函数的内容作为自定义的启动内容,里面你可以修改这个线程相关的信息,例如修改线程的变量,修改线程的 server 地址之类的,一般都不用修改,如果你想修改的话,这里有一些参数的函数可以供你使用:

函数(变量)名称 作用
thread.addr 变量:用于获取 / 修改服务器的地址
thread:get(name) 函数:用于获取线程的全局变量
thread:set(name, value) 函数:用于设置线程的全局变量,上面的例子演示了
thread:stop() 函数:关闭线程,后面的两个阶段也不用执行啦
运行阶段

运行阶段是 wrk 的核心部分,在这一部分,你可以有多个函数可以定义,分别是:

函数名称 作用
function init(args) lua 线程进入运行阶段时,会执行一遍,后续就不执行了,你可以在这个阶段做一些请求的初始化之类的
function delay() lua 线程每次发起请求之前都会调用一次这个
function request() lua 线程会通过这个函数的响应作为请求进行发送
function response(status, headers, body) lua 线程会将每次请求的响应作为参数调用这个函数

这里给一个示例每个阶段的代码是怎么写的:

  1. [[email protected].io]# cat running.lua
  2. function init(args)
  3. requests = 0
  4. responses = 0
  5. local msg = "thread %d created"
  6. print(msg:format(id))
  7. end
  8. function delay()
  9. return math.random(10, 50)
  10. end
  11. function request()
  12. requests = requests + 1
  13. return wrk.request()
  14. end
  15. function response(status, headers, body)
  16. responses = responses + 1
  17. end

这就是一个简单的示例了,在示例里面,这里每个请求都延迟了 10-50 毫秒,然后在请求之前会记录一下请求数,请求成功之后也会记录一下请求的响应数量。

结束阶段

在结束之后,lua 允许你定义你自己的处理结果,在这里,你可以自定义你的输出格式,如果你不喜欢默认的输出格式的话,这里也只有一个你需要自己定义的函数:

  1. [[email protected].io]# cat stop.lua
  2. function done(summary, latency, requests)
  3. for index, thread in ipairs(threads) do
  4. local id = thread:get("id")
  5. local requests = thread:get("requests")
  6. local responses = thread:get("responses")
  7. local msg = "thread %d made %d requests and got %d responses"
  8. print(msg:format(id, requests, responses))
  9. end
  10. end
特别的例子

如果你只想修改一下 request 就好了,其他都不想自定义,那么你可以直接在 lua 里面写 request 里面的函数,不用 return,也不用写函数定义,下面个 POST 的示例就是这种情况。

POST 请求

如果按照我们上一小节介绍的 lua 框架来说,我们可以在 request () 里面定义请求的格式,将请求方式定义为 POST 就可以了,但是,最后我也提了一下,wrk 考虑到了这种场景的常见性,所以讲这个 feature 简化成你只需要写一段脚本就可以了,不用在定义 function 和 return 了,例如这样就可以了:

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

这就是一个 POST 的示例了,然后通过 wrk 加载这个 lua 脚本运行:

  1. [[email protected].io]# wrk -c3 -d1s -t2 -s ./scripts/post.lua http://localhost:5000/api/v1/login

其他

lua 辅助函数

函数 解释
wrk.format(method, path, headers, body) 这个函数通常用于 request () 阶段返回 request 对象

done 的参数属性

done(summary, latency, requests) 里面记录了性能测试的很多数据,包含的字段如下:

对象 属性 解释
Property Description
—- —-
summary duration run duration in microseconds
summary requests total completed requests
summary bytes total bytes received
summary errors.connect total socket connection errors
summary errors.read total socket read errors
summary errors.write total socket write errors
summary errors.status total HTTP status codes > 399
summary errors.timeout total request timeouts
latency min minimum latency value reached during test
latency max maximum latency value reached during test
latency mean average latency value reached during test
latency stdev latency standard deviation
latency percentile(99.0) 99th percentile value
latency[i] raw latency data of request i ——

一个复杂的例子

  1. [[email protected].io]# cat pipeline.lua
  2. init = function(args)
  3. local r = {}
  4. r[1] = wrk.format(nil, "/?foo")
  5. r[2] = wrk.format(nil, "/?bar")
  6. r[3] = wrk.format(nil, "/?baz")
  7. req = table.concat(r)
  8. end
  9. request = function()
  10. return req
  11. end