性能測試神器 wrk 使用教程

原文連接:https://blog.fengjx.com/wrk/

wrk 是一個類似 ab(apache bench)、jmeter 的壓力測試工具,底層基於 epoll 和 kqueue 實現,能充分利用 cpu 資源,降低測試工具本身性能開銷對測試結果準確性的影響。支持使用 lua 腳本自定義測試邏輯,使用上非常簡單,但功能足夠強大。

沒有了解過 lua 的同學,可以看下 lua 簡明教程 https://coolshell.cn/articles/10739.html

安裝

用法

$ 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

更多用法查看官方示例:https://github.com/wg/wrk/tree/master/scripts

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章