1 引言
wrk的命令行參數只能發送類似於get、delete這種不在請求體中帶參數的http請求,如果要發送post這類請求,必須要寫Lua腳本。由於wrk具有內置的LuaJIT(用於Lua的即時編譯器),可以使用Lua腳本進行擴展。
也不必擔心沒學過Lua不會寫腳本,Lua的可讀性很強,類似於Python。通過下面簡單的示例,完全可以模仿出一個更高級的腳本,況且官方也寫了好幾個模板供我們使用。
2 腳本結構
在寫腳本之前,先研究一下腳本結構,該結構也反映了wrk的內部邏輯,有助於我們更好的理解和編寫腳本。如下圖:
圖示為wrk的執行流程,分為以下幾個階段:
1. 解析IP地址。
2. 設置線程。
3. 執行壓力測試,也被稱為運行階段。
4. 完成測試。
上圖只是一個單線程的圖示。如果是多線程,流程圖會變成這樣:
此外,running(運行階段)還可以分為三個步驟:init(初始化),request(請求)和response(響應)。
總結一下:wrk一次運行過程有1.resolve IP階段;2. setUp階段;3. running階段(分為init、request、reponse);4.done階段; 在上述的幾個階段中,都有對應的變量或方法提供調用。
3 不同階段的方法和變量
這個小節就是介紹一下各個階段對應的方法和一個wrk全局表,大致過一遍眼熟一下就行,配合后面的腳本示例看簡直效果拔群。
wrk全局表,存着一些變量供后面的方法調用:
wrk = { scheme = "http", host = "localhost", port = nil, method = "GET", path = "/", headers = {}, body = nil, thread = <userdata>, }
wrk.format方法:返回一個HTTP請求字符串,該字符串是傳遞的參數與wrk表中的值的合並。
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.
wrk.lookup方法:返回一個表,其中包含主機和服務對的所有已知地址。這對應於POSIX getaddrinfo()函數。
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方法:如果可以連接到請求地址,則返回true,否則返回false。該地址必須是wrk.lookup方法返回的地址。
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().
以下全局變量是可選的,並且如果定義必須是函數,也對應着上面流程圖中的各個階段,下面將一個個介紹:
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階段:
設置階段是ip地址已經被成功解析並且線程初始化完畢但是還未啟動的階段,用於將數據傳遞給線程。每個線程調用一次setup()並接收代表該線程的userdata對象。
只能通過get()/ set()方法設置或獲取線程運行時需要的全局變量,變量值可以是布爾值,nil,數字和字符串值或表的值。
thread:stop()只能在線程運階段調用。
function setup(thread) The setup phase begins after the target IP address has been resolved and all threads have been initialized but not yet started. setup() is called once for each thread and receives a userdata object representing the 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 Only boolean, nil, number, and string values or tables of the same may be transfered via get()/set() and thread:stop() can only be called while the thread is running.
Running階段:
運行階段從調用init()方法開始,之后循環調用request()和response()方法。
init()每個線程初始化時調用。可以接受命令行參數,但是為了和wrk的命令行參數做區分,參數名以“--”開頭。比如:
wrk -c3 -d1s -t2 -s /scripts/debug.lua http://$APP1_PRIVATE_IP:3000 -- debug true
delay()返回延遲發送下一個請求的毫秒數。
request()需要為每個請求返回HTTP對象。在此函數中,我們可以修改method,headers,path,和 body。可以使用wrk.format()輔助函數來生成請求對象。比如:
return wrk.format(method, path, headers, body)
response()在響應返回時調用。需要status(http響應狀態),headers(http響應頭),body(http響應體)三個參數,解析響應頭和響應體非常消耗計算機資源,因此,如果調用init()方法之后全局變量response是nil,wrk將不會解析響應頭和響應體。
Running function init(args) function delay() function request() function response(status, headers, body) The running phase begins with a single call to init(), followed by a call to request() and response() for each request cycle. The init() function receives any extra command line arguments for the script which must be separated from wrk arguments with "--". delay() returns the number of milliseconds to delay sending the next request. request() returns a string containing the HTTP request. Building a new request each time is expensive, when testing a high performance server one solution is to pre-generate all requests in init() and do a quick lookup in request(). response() is called with the HTTP response status, headers, and body. Parsing the headers and body is expensive, so if the response global is nil after the call to init() wrk will ignore the headers and body.
Done階段:
當所有請求都已經完成並且請求結果已經被統計時調用。
done()函數接收一個包含結果數據的表(表是lua內置數據類型),以及兩個統計對象:每個請求的延遲時間(以微秒為單位)、每個線程的請求速率(以每秒請求數為單位)。Done函數內,可以使用以下屬性:
function done(summary, latency, requests) The done() function receives a table containing result data, and two statistics objects representing the per-request latency and per-thread request rate. Duration and latency are microsecond values and rate is measured in requests per second.
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 第99%的值 latency[i] -- raw value and count 請求i的原始延遲數據 summary = { duration = N, -- run duration in microseconds 運行持續時間(以微秒為單位) requests = N, -- total completed requests 完成的請求總數 bytes = N, -- total bytes received 接受的總字節數 errors = { connect = N, -- total socket connection errors socket連接錯誤的總數目 read = N, -- total socket read errors socket讀取錯誤的總數目 write = N, -- total socket write errors socket寫入錯誤的總數目 status = N, -- total HTTP status codes > 399 HTTp狀態碼大於399的總數目 timeout = N -- total request timeouts 所有請求的超時時間總和 } }
4 Post請求示例
首先,在wrk1(博主本地虛擬機)上創建腳本文件夾和文件
[root@iQOO-Z1 pyy]# mkdir scripts [root@iQOO-Z1 pyy]# vi scripts/post.lua
添加以下內容到post.lua:
wrk.method = "POST" wrk.body = '{"username":"admin","password":"123456"}' wrk.headers["Content-Type"] = "application/json" response = function(status, headers, body) print(body) --調試用,正式測試時需要關閉,因為解析response非常消耗資源 end
這個腳本非常簡單,僅僅修改了全局wrk對象屬性和重載了response()方法,response()只是打印了一下響應體內容。使用帶有wrk的Lua腳本只需將文件路徑附加到-s參數后面即可:
# -v 數據卷掛載,將本地目錄與wrk容器內目錄共享; `pwd` 代表當前目錄 docker run --rm -v `pwd`/scripts:/scripts williamyeh/wrk -c1 -t1 -d1s -s /scripts/post.lua http://xxx.xx.xx.xx:port/url
運行結果如下,可以看見已經登錄成功並且返回了用戶token:
上面實現了post請求,但是還有提升效率的方法,比如每次測試都要輸入一遍命令未免有點繁瑣,所以可以考慮寫一個簡單的shell腳本來更快捷的進行測試。
創建shell文件並輸入命令代碼,如下:
[root@iQOO-Z1 pyy]# vi login_test.sh # content docker run --rm -v `pwd`/scripts:/scripts williamyeh/wrk -c$1 -t$2 -d$3 -s /scripts/post.lua http://xx.x.xx.xx:port/login [root@iQOO-Z1 pyy]# chmod +x login_test.sh [root@iQOO-Z1 pyy]# ./login_test.sh 1 1 1s
運行結果:
未完待續........