wrk——轻量级异步性能测试工具

猪小花1号2018-09-17 13:11

作者:牛小宝


良好的性能表现是作为高质量系统的必要条件,测试系统性能的软件有很多,商业工具如LoadRunner,开源工具有JMeter,Grinder,Gatling等,当需要进行一些简单快速的性能测试时这些工具的安装使用略嫌繁重。这里介绍下wrk,这是一款很简单轻量的HTTP异步性能测试工具,仅需一个命令行就可以进行性能测试。

相比LoadRunner,JMeter,Grinder这些工具,wrk除了轻量外还有一个最大的特点就是可以异步打压力,即可以用较少的线程模拟洪水压力。wrk是用C语言编写的,需要在Linux类系统上编译运行。wrk还支持使用LUA脚本模拟一些复杂的测试场景


安装wrk

wrk的安装很简单,到GitHub上下载wrk源码至Linux服务器上,编译即可,下载地址见https://github.com/wg/wrk 如果服务器上已有git可直接clone远程仓库至本地:

git clone https://github.com/wg/wrk.git
cd wrk 或 cd wrk-master
make

编译安装成功后会在wrk的文件夹内看到可执行wrk文件


wrk的简单使用

./wrk -t5 -c10 -d10s https://www.kaola.

命令解释:

-t:threads,指定多少个线程并发发请求;
-c:connections,指定模拟多少个连接,注意连接数不能小于并发数;
-d:duration,指测试持续的时长;

测试完成后会输出如下结果:

Running 10s test @ https://www.kaola.com
  5 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    35.89ms   62.05ms 423.12ms   82.95%
    Req/Sec   487.19    237.51     1.17k    70.77%
  23509 requests in 10.00s, 1.05GB read
Requests/sec:   2350.09
Transfer/sec:    107.49MB

命令解释:

Latency:为请求响应时间,结果中报错平均时间,方差(越大越不稳定),最大值
Req/Sec:每个线程每秒发送的请求数量
Requests/sec:每秒内可发送的总请求数量;
Transfer/sec:每秒内可传送的报文总长度


wrk脚本编写

对于复杂的场景如带参数的post请求,需要登录认证之类的场景,就需要编写lua脚本来发请求,wrk调用lua执行HTTP请求时分为3个阶段setup,running,done,每个wrk线程都是有独立的运行环境,如下图:

wrk的Lua API是有一个全局属性和一些全局方法构成。


全局属性:
wrk = {
    scheme  = "http",
    host    = "localhost",
    port    = nil,
    method  = "GET",
    path    = "/",
    headers = {},
    body    = nil,
    thread  = <userdata>,
  }

全局方法:
-- 生成整个request的string,这些string包含了wrk的全局属性
-- GET / HTTP/1.1
-- Host: tool.lu
function wrk.format(method, path, headers, body)

-- 获取域名的IP和端口,返回table,例如:返回 `{127.0.0.1:80}`
function wrk.lookup(host, service)

-- 判断addr是否能连接,例如:`127.0.0.1:80`,返回 truefalse
function wrk.connect(addr)

Setup阶段

setup是在线程创建之后,启动之前。

function setup(thread)

-- thread提供了1个属性,3个方法
-- thread.addr 设置请求需要打到的ip
-- thread:get(name) 获取线程全局变量
-- thread:set(name, value) 设置线程全局变量
-- thread:stop() 终止线程

Running阶段
function init(args)
-- 每个线程仅调用1次,args 用于获取命令行中传入的参数, 例如 --env=pre

function delay()
-- 每个线程调用多次,发送下一个请求之前的延迟, 单位为ms

function request()
-- 每个线程调用多次,返回http请求

function response(status, headers, body)
-- 每个线程调用多次,返回http响应

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
  }
}

post请求的发送

使用lua发送http请求的时候,其实就是给wrk的全局属性填充相应的值,对于一个http请求,我们要先确定请求方式,请求头部,请求体等信息,如下就是wrk给出的一个简单的post请求的lua脚本实例:

-- example HTTP POST script which demonstrates setting the
-- HTTP method, body, and adding a header

wrk.method = "POST"
wrk.body   = "foo=bar&baz=quux"
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"

然后使用如下命令执行即可:

./wrk -t1 -c1 -d1s -T1s --script=app_cart_add.lua --latency http://xxx

实际上,一般http请求都不会这么简单,比如需要模拟登陆,需要设置入参等,这时我们可以通过预设入参的数据文件来解决,必须下面这个场景,我们需要登录后模拟添加购物车的场景,登录信息提前预设到urs_id.txt和urs_token.txt两个数据文件中,lua脚本如下:

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

-- Read file lines into string array
function lines_from(file)
  if not file_exists(file) then return {} end
  lines = {}
  for line in io.lines(file) do 
    lines[#lines + 1] = line
  end
  return lines
end

local urs_id = 'urs_id.txt'
lines_urs_id = lines_from(urs_id)
local urs_token = 'urs_token.txt'
lines_urs_token = lines_from(urs_token)

request = function()
    -- Set random cookie
    local index = math.random(#lines_urs_id)
    wrk.headers["ursId"] = lines_urs_id[index]
    wrk.headers["ursToken"] = lines_urs_token[index] 
    wrk.headers["Content-Type"] = "application/json;charset=UTF-8"
    wrk.body="{\"skuId\":\"1596081-68a3e5516d7a7dc21fbe0e7ee13bfc1c\",\"isHuanGou\":0,\"goodsId\":\"1596081\",\"selected\":1,\"tempBuyAmount\":1,\"innerSource\":\"DETAIL\",\"goodsType\":0,\"activitySchemeId\":0}" 
    -- Return current request url formated by wrk api
    return wrk.format("POST", "/api/cart") 
end

response = function(status, headers, body)
    if status ~= 200 or string.find(body, "\"code\":0") == nil then
        -- Parse results
        print("error", body)
    else
        print("right", body)
    end
end

done = function(summary, latency, requests)
   print("welcome to wrk!")
end

在request方法中是直接给wrk.header,wrk.body进行复制的,也可以定义一个header字典,body字符串通过wrk.format(method, path, headers, body)赋值;
在response方法中对返回的body进行了脚本,如果http状态码不等于200,或者返回body不包含正确关键字就会打印出错误的消息体;
在done方法中打印了一串字符串,done方法是在整个脚本运行结束后最后只执行一次的,这里做个验证,真实使用的时候可以定义最后生成的报表内容。
运行命令执行脚本得到如下结果:

qatest@haitao-https1:~/wrk/scripts$ ../wrk -t1 -c1 -d1s -T1s --script=app_cart_add.lua --latency http://sp.kaola.com
Running 1s test @ http://sp.kaola.com
  1 threads and 1 connections
right    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":9}}
right    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":10}}
right    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":9}}
right    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":14}}
right    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":9}}
right    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":14}}
right    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":9}}
right    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":10}}
right    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":10}}
right    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":14}}
right    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":14}}
right    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":32}}
right    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":9}}
right    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":14}}
right    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":32}}
right    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":14}}
^Cright    {"code":0,"msg":"成功","body":{"cartGoodsTotalNum":14}}
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    40.65ms    6.28ms  52.49ms   64.71%
    Req/Sec    24.29      5.35    30.00     57.14%
  Latency Distribution
     50%   37.12ms
     75%   45.23ms
     90%   49.59ms
     99%   52.49ms
  17 requests in 700.32ms, 6.85KB read
Requests/sec:     24.27
Transfer/sec:      9.78KB
welcome to wrk!



网易云产品免费体验馆无套路试用,零成本体验云计算价值。  

本文来自网易实践者社区,经作者牛小宝授权发布