ベンチマークツール(wrk, vegeta)で負荷テスト

ベンチマークツールとして人気のある wrkvegeta の動作確認をします。macにそれぞれのツールをインストールして、簡単な負荷テストをしてみます。

前準備

動作確認用の負荷試験サーバーを作成

expressを利用して、動作確認用の負荷試験サーバーを作成します。必要なパッケージをインストールします。

npm init
npm install --save express multer

app.js を作成して、以下処理を記述します。

const express = require('express')
const app = express()
const bodyParser = require('body-parser')
const multer = require('multer')
const upload = multer({ dest: 'uploads/' })

app.use(bodyParser.urlencoded({ extended: true }))

const log = (req) => {
  console.log(
    `url: ${req.url} method: ${req.method} second: ${new Date().getSeconds()}`
  )
}
const sleep = (second) => new Promise(resolve => setTimeout(resolve, second * 1000))

app.get('/', (req, res) => {
  log(req)
  return res.json({ message: 'hello world' })
})

app.get('/sleep', async (req, res) => {
  log(req)
  await sleep(1)
  return res.json({ message: 'hello world' })
})

app.post('/', (req, res) => {
  log(req)
  return res.json(req.body)
})

app.post('/upload', upload.single('file'), (req, res) => {
  log(req)
  return res.send(req.file.originalname)
})

app.listen(5000, () => console.log('Example app listening on port 5000!'))

expressサーバーを起動します。

$ node app.js 
Example app listening on port 5000!

curlで動作確認

GET /

curl localhost:5000 \
-w "\n%{time_total}\n"
$ curl localhost:5000 \
> -w "\n%{time_total}\n"
{"message":"hello world"}
0.004826

GET /sleep

curl localhost:5000/sleep \
-w "\n%{time_total}\n"
$ curl localhost:5000/sleep \
> -w "\n%{time_total}\n"
{"message":"hello world"}
1.006174

POST /

curl localhost:5000 \
-XPOST \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'message=hello world' \
-w "\n%{time_total}\n"
$ curl localhost:5000 \
> -XPOST \
> -H 'Content-Type: application/x-www-form-urlencoded' \
> -d 'message=hello world' \
> -w "\n%{time_total}\n"
{"message":"hello world"}
0.005452

POST /upload

10KBのファイル10MBのファイル を用意します。

$ ls -lh | grep dummy
-rw-r--r--  1 xxx  xxx    10K Jul 26 10:17 dummy10KB.file
-rw-r--r--  1 xxx  xxx    10M Jul 26 10:16 dummy10MB.file
curl localhost:5000/upload \
-F "file=@./dummy10MB.file" \
-w "\n%{time_total}\n"
$ curl localhost:5000/upload \
> -F "file=@./dummy10KB.file" \
> -w "\n%{time_total}\n"
dummy10KB.file
0.006415
$ curl localhost:5000/upload \
> -F "file=@./dummy10MB.file" \
> -w "\n%{time_total}\n"
dummy10MB.file
0.037135

wrkで負荷テスト

wrkは、C言語で書かれたベンチマークツールです。

Macにインストール

macにwrkをインストールします。

$ git clone https://github.com/wg/wrk.git
$ cd wrk
$ make -j4

実行(GET)

コマンド例

./wrk -t 1 \
-c 2 \
-d 10s \
--latency \
"http://localhost:5000"

1thread2つのhttp connection を確立して、10秒間リクエストを送ります。

実行結果

$ ./wrk -t 1 \
> -c 2 \
> -d 10s \
> --latency \
> "http://localhost:5000"
Running 10s test @ http://localhost:5000
  1 threads and 2 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   151.68us  150.08us   8.57ms   96.01%
    Req/Sec    13.91k     2.16k   15.87k    92.08%
  Latency Distribution
     50%  124.00us
     75%  144.00us
     90%  209.00us
     99%  487.00us
  139728 requests in 10.10s, 31.58MB read
Requests/sec:  13834.21
Transfer/sec:      3.13MB

レイテンシ[Latency] スループット(rps [Req/Sec]) など確認できます。

10.10s 間で合計 139728 requests を送っています。

--latencyオプション を設定しているため 75% 144.00us といったパーセンタイル表示が追加されます。

パーセンタイル
e.g.) 総リクエストが100リクエストであるケースで80パーセンタイルが指すデータ
→ データを小さい順に並べて、80番目の位置にあるデータ

結果の違いを確認するために、今度は http://localhost:5000/sleep のパスで動作確認します。

$ ./wrk -t 1 \
> -c 2 \
> -d 10s \
> --latency \
> "http://localhost:5000/sleep"
Running 10s test @ http://localhost:5000/sleep
  1 threads and 2 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.00s     1.96ms   1.01s    55.00%
    Req/Sec     1.10      0.32     2.00     90.00%
  Latency Distribution
     50%    1.00s 
     75%    1.00s 
     90%    1.01s 
     99%    1.01s 
  20 requests in 10.08s, 4.63KB read
Requests/sec:      1.98
Transfer/sec:     470.43B

10.08s 間で合計 20 requests を送っています。

実行(POST)

luaスクリプトを用意します。post.lua というファイルを作成して以下処理を記述します。

wrk.method = "POST"
wrk.body   = "message=hello world"
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"

コマンド例

-sオプション で先ほど作成した post.lua を指定しています。

./wrk -t 1 \
-c 2 \
-d 10s \
-s ./post.lua \
--latency \
"http://localhost:5000"

実行結果

$ ./wrk -t 1 \
> -c 2 \
> -d 10s \
> -s ./post.lua \
> --latency \
> "http://localhost:5000"
Running 10s test @ http://localhost:5000
  1 threads and 2 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   200.31us  377.34us  16.56ms   99.13%
    Req/Sec    10.83k     1.24k   11.88k    92.08%
  Latency Distribution
     50%  162.00us
     75%  181.00us
     90%  256.00us
     99%  549.00us
  108892 requests in 10.10s, 24.61MB read
Requests/sec:  10781.80
Transfer/sec:      2.44MB

実行(ファイルアップロード)

luaスクリプトを用意します。post_file_upload.lua というファイルを作成して以下処理を記述します。

function read_txt_file(path)
    local file, errorMessage = io.open(path, "r")
    if not file then 
        error("Could not read the file:" .. errorMessage .. "\n")
    end

    local content = file:read "*all"
    file:close()
    return content
end

local Boundary = "----WebKitFormBoundaryePkpFF7tjBAqx29L"
local BodyBoundary = "--" .. Boundary
local LastBoundary = "--" .. Boundary .. "--"

local CRLF = "\r\n"

local FileBody = read_txt_file("dummy10MB.file")
local Filename = "dummy10MB.file"

local ContentDisposition = "Content-Disposition: form-data; name=\"file\"; filename=\"" .. Filename .. "\""

wrk.method = "POST"
wrk.headers["Content-Type"] = "multipart/form-data; boundary=" .. Boundary
wrk.body = BodyBoundary .. CRLF .. ContentDisposition .. CRLF .. CRLF .. FileBody .. CRLF .. LastBoundary

How to test upload 3MB file? · Issue #274 · wg/wrk を参考にしています。

また、dummy10MB.filepost_file_upload.lua が存在する同一階層のフォルダにコピーしています。

コマンド例

-sオプション で先ほど作成した post_file_upload.lua を指定しています。

./wrk -t 1 \
-c 2 \
-d 10s \
-s ./post_file_upload.lua \
--latency \
"http://localhost:5000/upload"

実行結果

$ ./wrk -t 1 \
> -c 2 \
> -d 10s \
> -s ./post_file_upload.lua \
> --latency \
> "http://localhost:5000/upload"
Running 10s test @ http://localhost:5000/upload
  1 threads and 2 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    22.95ms    3.39ms  56.30ms   89.58%
    Req/Sec    87.17     10.77   101.00     74.00%
  Latency Distribution
     50%   21.89ms
     75%   24.48ms
     90%   26.32ms
     99%   36.07ms
  871 requests in 10.01s, 185.43KB read
Requests/sec:     87.05
Transfer/sec:     18.53KB

vegetaで負荷テスト

vegetaは、Goで書かれたベンチマークツールです。

Macにインストール

macにvegetaをインストールします。

$ brew update && brew install vegeta

インストールできました。

$ vegeta -version
Version: 12.8.3
Commit: 
Runtime: go1.14 darwin/amd64
Date: 2020-03-25T14:41:40Z

実行(GET)

vegeta -h で使い方を確認したところ、以下examplesが表示されていたので、こちらの動作確認をしてみます。

examples:
  echo "GET http://localhost/" | vegeta attack -duration=5s | tee results.bin | vegeta report
  vegeta report -type=json results.bin > metrics.json
  cat results.bin | vegeta plot > plot.html
  cat results.bin | vegeta report -type="hist[0,100ms,200ms,300ms]"

examplesでは、vegetaの3つのコマンド( attack report plot )を利用しています。

  • attackコマンド
    • リクエストが実行され負荷をかけます。
  • reportコマンド
    • results.binの内容をもとに text形式(デフォルト) json形式(-type=json) hist形式(-type="hist[0,100ms,200ms,300ms]") に整形して出力します。
  • plotコマンド
    • results.binの内容をもとに、グラフが書かれた html形式 の内容を出力します。

attackコマンド実行

http://localhost:5000 にリクエストしてみます。

$ echo "GET http://localhost:5000" | vegeta attack -duration=5s | tee results.bin | vegeta report
Requests      [total, rate, throughput]         250, 50.21, 50.21
Duration      [total, attack, wait]             4.979s, 4.979s, 641.457µs
Latencies     [min, mean, 50, 90, 95, 99, max]  214.128µs, 664.866µs, 615.345µs, 938.15µs, 966.637µs, 1.082ms, 8.451ms
Bytes In      [total, mean]                     6250, 25.00
Bytes Out     [total, mean]                     0, 0.00
Success       [ratio]                           100.00%
Status Codes  [code:count]                      200:250  
Error Set:
$
$ ls -l
total 264
-rw-r--r--  1 xxx  xxx  70078 Jul 26 11:41 results.bin

4.979s 間で合計 250 requests を送っています。rps50.21 です。

reportコマンド実行

reportコマンド を利用して results.bin を整形してみます。

$ vegeta report -type=json results.bin > metrics.json
$ 
$ ls -l
total 272
-rw-r--r--  1 xxx  xxx    499 Jul 26 11:45 metrics.json
-rw-r--r--  1 xxx  xxx  70078 Jul 26 11:41 results.bin
$ cat results.bin | vegeta report -type="hist[0,100ms,200ms,300ms]"
Bucket           #    %        Histogram
[0s,     100ms]  250  100.00%  ###########################################################################
[100ms,  200ms]  0    0.00%    
[200ms,  300ms]  0    0.00%    
[300ms,  +Inf]   0    0.00%    

plotコマンド実行

plotコマンド を利用して results.bin からグラフを生成してみます。

$ cat results.bin | vegeta plot > plot.html
$
$ ls -l
total 864
-rw-r--r--  1 xxx  xxx     499 Jul 26 11:45 metrics.json
-rw-r--r--  1 xxx  xxx  300232 Jul 26 11:46 plot.html
-rw-r--r--  1 xxx  xxx   70078 Jul 26 11:41 results.bin
742-benchmark-tool_vegeta_plot.png

ブラウザでの確認結果です。

参考リンク