原文連接:https://blog.fengjx.com/wrk/
wrk 是一個類似 ab(apache bench)、jmeter 的壓力測試工具,底層基於 epoll 和 kqueue 實現,能充分利用 cpu 資源,降低測試工具本身性能開銷對測試結果準確性的影響。支持使用 lua 腳本自定義測試邏輯,使用上非常簡單,但功能足夠強大。
沒有了解過 lua 的同學,可以看下 lua 簡明教程 https://coolshell.cn/articles/10739.html
安裝
- linux https://github.com/wg/wrk/wiki/Installing-wrk-on-Linux
- macOS https://github.com/wg/wrk/wiki/Installing-wrk-on-Mac-OS-X
- windows( Windows Subsystem for Linux ) https://github.com/wg/wrk/wiki/Installing-wrk-on-Windows-10
用法
$ wrk -h
wrk: invalid option -- h
Usage: wrk <options> <url>
Options:
-c, --connections <N> Connections to keep open
-d, --duration <T> Duration of test
-t, --threads <N> Number of threads to use
-s, --script <S> Load Lua script file
-H, --header <H> Add header to request
--latency Print latency statistics
--timeout <T> 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 測試腳本
官方文檔:https://github.com/wg/wrk/blob/master/SCRIPTING
編寫 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 = <userdata>
}
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 -- 輸出 -- 1 test -- 2 100
-
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 } }
官方示例
https://github.com/wg/wrk/blob/master/scripts/setup.lua
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