網站架設

wrk:HTTP 網頁伺服器效能測試工具使用教學

介紹如何使用 wrk 對 HTTP 網頁伺服器進行負載與效能測試,並搭配 LuaJIT 指令稿客製化各項功能。

wrk 是一個 HTTP 標竿測試工具,其核心主要以 C 語言撰寫而成,可以透過多執行緒的方式產生大量的測試流量,亦可搭配 LuaJIT 指令稿擴充各種功能,例如請求產生、回應處理以及產生自訂報表。

安裝 wrk

若在一般的 Linux 或 macOS 系統中,可以在下載原始碼之後,執行 make 編譯:

# 下載 wrk 原始碼
git clone https://github.com/wg/wrk.git

# 編譯 wrk
cd wrk/
make

若在 Ubuntu Linux 中,可用 apt 安裝:

# 安裝 wrk 套件
sudo apt install wrk

基本效能測試

使用 wrk 進行 HTTP 伺服器的效能測試時,可以指定一些測試的參數,以下是最常用的幾個參數:

  • -t:使用的執行緒(threads)數量。
  • -c:維持連線的數量。
  • -d:測試時間。
  • -H:設定 HTTP 標頭。
  • -s:指定 Lua 指令稿。
  • --timeout:連線逾時時間。
  • --latency:顯示連線延遲分佈。

wrk 的參數中,需要指定數值的部分都可以使用標準的 SI 單位,例如 1k1M1G,而時間部分也要加上單位,例如 2s2m2h

例如使用 12 條執行緒,維持 400 條開啟的連線,持續測試 30 秒,則可執行:

# 基本效能測試
wrk -t12 -c400 -d30s http://127.0.0.1/
Running 30s test @ https://127.0.0.1/
  12 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.58s   304.06ms   2.00s    82.55%
    Req/Sec    40.75     54.17   303.00     85.30%
  6863 requests in 30.09s, 123.36MB read
  Socket errors: connect 0, read 0, write 0, timeout 753
Requests/sec:    228.07
Transfer/sec:      4.10MB

顯示延遲分佈

若要顯示連線延遲(latency)的分佈,可以加上 --latency 參數:

# 顯示連線延遲分佈
wrk -t12 -c400 -d30s --latency http://127.0.0.1/
Running 30s test @ https://127.0.0.1/
  12 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.61s   336.25ms   2.00s    83.32%
    Req/Sec    36.40     50.57   282.00     86.61%
  Latency Distribution
     50%    1.65s 
     75%    1.81s 
     90%    1.93s 
     99%    1.99s 
  6506 requests in 30.09s, 116.94MB read
  Socket errors: connect 0, read 0, write 0, timeout 1061
Requests/sec:    216.24
Transfer/sec:      3.89MB

指定 HTTP 標頭

若要自行加入指定的 HTTP 標頭,可以使用 -H 參數:

# 指定 HTTP 標頭
wrk -t12 -c400 -d30s -H"User-Agent: wrk" http://127.0.0.1/

POST 請求

wrk 預設是發送 GET 請求,若要發送 POST 請求,可以搭配 Lua 指令稿來設定,以下是一個用來發送 POST 請求的 Lua 指令稿:

-- 請求類型
wrk.method = "POST"

-- 請求內容
wrk.body = "foo=bar&baz=quux"

-- HTTP 標頭
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"

將上面這段指令稿儲存為 post.lua,並以下列指令執行測試:

# 使用 post.lua 指令稿進行測試
wrk -t12 -c400 -d30s -s counter.lua https://127.0.0.1/

傳送 JSON 資料

若要傳送 JSON 格式的資料,只要將 Content-Type 改為 JSON 類型,然後將 JSON 資料內容放進 wrk.body 中即可:

-- 請求類型
wrk.method = "POST"

-- JSON 資料內容
wrk.body = '{"test":"abcd"}'

-- 以 HTTP 標頭將 Content-Type 設定為 JSON
wrk.headers["Content-Type"] = "application/json"

將這段指令稿儲存為 json_test.lua,以 HTTPBin 測試:

# 傳送 JSON 資料
wrk -t12 -c400 -d30s -s json_test.lua https://httpbin.org/post

動態產生 HTTP 請求

wrk 可以搭配 Lua 指令稿自訂各種處理程序,例如以下指令稿可以依序送出不同編號的網址,同時將編號設定在 HTTP 標頭中:

-- 設定計數器初始值
counter = 0

-- 定義 HTTP 請求建立函數
request = function()
   path = "/" .. counter              -- 以計數器產生網址的路徑
   wrk.headers["X-Counter"] = counter -- 設定 HTTP 標頭
   counter = counter + 1              -- 遞增計數器
   return wrk.format(nil, path)       -- 產生並傳回 HTTP 請求
end

將上面這段指令稿儲存為 counter.lua,並以下列指令執行測試:

# 使用 counter.lua 指令稿進行測試
wrk -t12 -c400 -d30s -s counter.lua https://127.0.0.1/

wrk 以多執行緒的方式執行時,每一條執行緒中會有獨立的 counter 變數,所以這裡使用 12 條執行緒,程式就會同時發送 12 個 /0 路徑的網址、接著同時發送 12 個 /1 路徑的網址,以此類推。

解析 JSON 格式的 HTTP 回應資料

如果需要檢查 JSON 格式的 HTTP 回應資料,以確認執行有沒有成功,可以使用 json.lua 這個函式庫來處理 JSON 資料的解析,其使用方式很簡單,只要下載 json.lua 這個指令稿之後,將 json.lua 跟我們自己的指令稿放在一起,即可直接引入使用:

-- 引入 json.lua
json = require "json"

-- 請求類型
wrk.method = "POST"

-- JSON 資料內容
wrk.body = '{"test":"abcd"}'

-- 以 HTTP 標頭將 Content-Type 設定為 JSON
wrk.headers["Content-Type"] = "application/json"

-- 定義處理 HTTP 回應的 response 函數
function response(status, headers, body)
  result = json.decode(body) -- 解析 JSON 格式的 body 資料
  print(result["data"])      -- 輸出 JSON 資料中的 data 欄位
end

將這段指令稿儲存為 json_parse.lua,以 HTTPBin 測試:

# 解析 JSON 格式之 HTTP 回應資料
wrk -t12 -c400 -d30s -s json_parse.lua https://httpbin.org/post

分析與統計 HTTP 回應資料

若要分析與統計 HTTP 回應的資料,可以參考以下指令稿結構。在這段指令稿中,我們在每一個 HTTP 請求中放入一個 randNum 隨機變數,再從回應訊息中讀出這個變數,統計大於以及小於 0.5 的個數,這種測試程式碼架構可以很容易修改成各種應用情境,統計測試成功與失敗的次數。

-- 引入 json.lua
json = require "json"

-- 請求類型
wrk.method = "POST"

-- 以 HTTP 標頭將 Content-Type 設定為 JSON
wrk.headers["Content-Type"] = "application/json"
  
-- 儲存執行緒的變數
local threads = {}
  
-- 各執行緒設置函數
function setup(thread)
  table.insert(threads, thread)
end

-- 各執行緒初始化函數
function init(args)
  large = 0
  small = 0
end
    
-- 產生包含隨機資料的 HTTP 請求
function request()
  randNum = math.random()                     -- 產生隨機亂數
  wrk.body = json.encode({randNum = randNum}) -- 產生 JSON 格式請求資料
  return wrk.format(nil, nil)                 -- 產生並傳回 HTTP 請求
end

-- 定義處理 HTTP 回應的 response 函數
function response(status, headers, body)
  result = json.decode(body)       -- 解析 JSON 格式的 body 資料
  if result["json"]["randNum"] > 0.5
  then
    large = large + 1
  else
    small = small + 1
  end
end

-- 結束處理函數  
function done(summary, latency, requests)
  large_sum = 0
  small_sum = 0
  print("--------------- Result ---------------")
  for index, thread in ipairs(threads) do
    local large = thread:get("large")
    local small = thread:get("small")
    large_sum = large_sum + large
    small_sum = small_sum + small
  end
  print("large:", large_sum)
  print("small:", small_sum)
end

其中 setup() 函數會在每條執行緒執行前被呼叫一次,在這個函數中我們將各執行緒的 thread 變數放入 threads 中儲存起來,以利後續統計時使用。init() 會在每一條執行序開始執行時進行變數的初始化。request() 函數會在每一次發送 HTTP 請求時,產生一個 randNum 隨機亂數,並以 JSON 的格式放入 HTTP 請求中。response() 則是會對收到的每一條 HTTP 回應進行分析處理。最後統一由 done() 函數取得每一條執行緒的資料,統計出整體的測試報告。

將這段指令稿儲存為 random.lua,以 HTTPBin 測試:

# 分析與統計 HTTP 回應資料
wrk -s random.lua https://httpbin.org/post
Running 10s test @ https://httpbin.org/post
  2 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   256.94ms  120.11ms   1.08s    87.86%
    Req/Sec    19.91      8.51    40.00     48.12%
  370 requests in 10.02s, 226.21KB read
Requests/sec:     36.93
Transfer/sec:     22.58KB
--------------- Result ---------------
large:	200
small:	170

參考資料

Share
Published by
Office Guide

Recent Posts

Python 使用 PyAutoGUI 自動操作滑鼠與鍵盤

本篇介紹如何在 Python ...

9 個月 ago

Ubuntu Linux 以 WireGuard 架設 VPN 伺服器教學與範例

本篇介紹如何在 Ubuntu ...

9 個月 ago

Linux 網路設定 ip 指令用法教學與範例

本篇介紹如何在 Linux 系...

10 個月 ago

Windows 使用 TPM 虛擬智慧卡保護 SSH 金鑰教學與範例

本篇介紹如何在 Windows...

11 個月 ago

Linux 以 Shamir’s Secret Sharing 分割保存金鑰教學與範例

介紹如何在 Linux 中使用...

11 個月 ago

Linux 以 Cryptsetup、LUKS 加密 USB 隨身碟教學與範例

介紹如何在 Linux 系統中...

11 個月 ago