これは、なにをしたくて書いたもの?
Vegetaという、割と個性的な負荷テストツールがあり、コマンドラインで簡単に使えそうなので試してみようかということで。
GitHub - tsenart/vegeta: HTTP load testing tool and library. It's over 9000!
Apache Benchの代わりに使えたらどうでしょう?くらいの感覚で、見てみました。
Vegeta
Vegetaというのは、HTTPの負荷テストツールです。
Vegeta is a versatile HTTP load testing tool built out of a need to drill HTTP services with a constant request rate.
GitHub - tsenart/vegeta: HTTP load testing tool and library. It's over 9000!
名前がまさしく…ですが、リポジトリに載せられている画像もそのまんまですね。
Go言語で作成されており、CLIとしても使えますし、ライブラリとしても利用することができます。
It can be used both as a command line utility and a library.
レポートは、テキスト、JSON、ヒストグラムやグラフで見れたりするようです。
テスト対象は、デフォルトは標準入力で与えるようですが、ファイルで用意して複数のターゲットに対して負荷をかけることも
できるようです。
分散実行は直接はサポートしていなさそうですが、SSHを使って複数のサーバーでVegetaを実行し、結果のレポートを
統合することで分散実行を実現するようです。
途中経過をリアルタイムに見るためには、他のツールの助けが必要な模様。
とまあ、前置きはこれくらいにして、実際に使ってみましょう。
環境
環境は、Ubuntu Linux 18.04 LTSで行いました。
$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 18.04.2 LTS Release: 18.04 Codename: bionic
インストール
CLIとしては、バイナリをダウンロードしてきて展開すれば使うことができます。
$ wget https://github.com/tsenart/vegeta/releases/download/cli%2Fv12.3.0/vegeta-12.3.0-linux-amd64.tar.gz $ tar xf vegeta-12.3.0-linux-amd64.tar.gz
バージョン、12.3.0…。
$ ./vegeta -version Version: 12.3.0 Commit: 7bf09f4fab4d852141935796455c17459d212098 Runtime: go1.12 linux/amd64 Date: 2019-04-14T13:29:49Z"
使い方は、ヘルプを見るとよいでしょう。
$ ./vegeta -h
最後の方に、コマンドの実行例が出力されます。
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]"
あとは、マニュアルを見ながらオプションの意味を確認しつつ…ですね。
テスト対象サーバー
Vegetaは負荷テストツールなので、負荷をかける対象がいないと始まりません。
ここは、簡単にNode.js+Expressで用意しました。
$ node -v v10.15.3 $ npm init $ npm i express
利用したExpressのバージョンは、こちら。
"dependencies": { "express": "^4.16.4" },
作成したアプリケーション。3つ、エンドポイントを作成しました。
※GET、POSTのパスに冗長感ありますが、今回は明確にパスを分けました
server.js
const express = require('express'); const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.urlencoded({ extended: true })); app.get('/', (req, res) => res.send('Hello!!')); app.get('/echo/get', (req, res) => res.send(req.query.message)); app.post('/echo/post', (req, res) => res.send(req.body.message)); app.listen(3000, () => console.log(`[${new Date()}] Server startup`));
package.jsonのscriptsに登録して
"scripts": { "start": "node server.js", "test": "echo \"Error: no test specified\" && exit 1" },
起動。
$ npm start > target-server@1.0.0 start /path/to/target-server > node server.js [Sun Apr 28 2019 21:05:04 GMT+0900 (GMT+09:00)] Server startup
確認。
$ curl localhost:3000 Hello!! $ curl localhost:3000/echo/get?message=hello hello $ curl -XPOST -H 'Content-Type: application/x-www-form-urlencoded' localhost:3000/echo/post -d 'message=hello' hello
これで、準備完了です。
単一のURLに対して負荷をかけてみる
それでは、Vegetaを使って対象のサーバーに負荷をかけてみましょう。
以下のコマンドで、「http://localhost:3000」にHTTP GETで、5秒間負荷をかけて結果を表示します。レポートはデフォルトで
標準出力に出力されるので、それをさらに「vegeta report」で整形します。
$ echo 'GET http://localhost:3000' | ./vegeta attack -duration=5s | ./vegeta report
結果。
Requests [total, rate] 250, 50.20 Duration [total, attack, wait] 4.981730015s, 4.980160389s, 1.569626ms Latencies [mean, 50, 95, 99, max] 1.096255ms, 1.092932ms, 1.595155ms, 2.017918ms, 3.177145ms Bytes In [total, mean] 1750, 7.00 Bytes Out [total, mean] 0, 0.00 Success [ratio] 100.00% Status Codes [code:count] 200:250 Error Set:
結果の読み方は、レポートのフォーマットが「text」のところに書いてあります。
- Requests
- total … 全実行回数
- rate … 秒間の実行回数
- Duration
- total … 負荷をかけるのに要した時間(attack+wait)
- attack … 全リクエストを実行するのに要した時間(total - wait)
- wait … レスポンスを待っている時間
- Latencies … それぞれ、平均、50%、95%、99%パーセンタイル、最大値
- Bytes In/Bytes Out … リクエスト/レスポンスの送受信のバイト数
- Success … リクエストの成功率(なお、200と400がエラーとしてカウントされない)
- Status Codes … ステータスコードのヒストグラム(0は、失敗)
- Error Set … 失敗したリクエストとその内容
最初の例が、秒間50リクエストとなかなか激しいので、ちょっと下げてみましょう。「-rate」で指定することができます。
秒あたり、3にしてみましょう。
$ echo 'GET http://localhost:3000' | ./vegeta attack -duration=5s -rate=3 | ./vegeta report Requests [total, rate] 15, 3.21 Duration [total, attack, wait] 4.668077623s, 4.666875044s, 1.202579ms Latencies [mean, 50, 95, 99, max] 1.061737ms, 935.134µs, 2.220665ms, 2.486884ms, 2.486884ms Bytes In [total, mean] 105, 7.00 Bytes Out [total, mean] 0, 0.00 Success [ratio] 100.00% Status Codes [code:count] 200:15 Error Set:
エラーの場合も見てみましょう。試しに、Not FoundになるURLで試してみます。
$ echo 'GET http://localhost:3000/notfound' | ./vegeta attack -duration=5s -rate=3 | ./vegeta report Requests [total, rate] 15, 3.21 Duration [total, attack, wait] 4.667400657s, 4.666715559s, 685.098µs Latencies [mean, 50, 95, 99, max] 1.38848ms, 963.456µs, 4.354423ms, 4.627119ms, 4.627119ms Bytes In [total, mean] 2205, 147.00 Bytes Out [total, mean] 0, 0.00 Success [ratio] 0.00% Status Codes [code:count] 404:15 Error Set: 404 Not Found
次は、POSTしてみましょう。
HTTPボディの内容は、以下のようなテキストファイルを用意。
body.txt
message=test
URLの前に、今度は「POST」を指定して負荷を書けます。作成したHTTPボディ用のテキストファイルは、「-body」オプションで
指定します。あと、「-header」でHTTPヘッダーも追加しました。
$ echo 'POST http://localhost:3000/echo/post' | ./vegeta attack -duration=5s -body=body.txt -header='Content-Type: application/x-www-form-urlencoded' | ./vegeta report
結果。
Requests [total, rate] 250, 50.20 Duration [total, attack, wait] 4.981419329s, 4.980088019s, 1.33131ms Latencies [mean, 50, 95, 99, max] 1.399184ms, 1.358172ms, 1.962928ms, 3.146063ms, 5.236047ms Bytes In [total, mean] 1250, 5.00 Bytes Out [total, mean] 3250, 13.00 Success [ratio] 100.00% Status Codes [code:count] 200:250 Error Set:
「-body」オプションはあくまでファイルを指定するので、ファイルを用意するのは面倒と思うかもしれません。
そういう時は、JSONフォーマットを使うとよいかもしれません。こうすると、HTTPボディやヘッダーの内容を
$ jq -ncM '{method: "POST", url: "http://localhost:3000/echo/post", body: "message=test" | @base64, header: {"Content-Type": ["application/x-www-form-urlencoded"]}}' | ./vegeta attack -format=json -duration=5s | ./vegeta report
結果。
Requests [total, rate] 250, 50.20 Duration [total, attack, wait] 4.981231371s, 4.980126573s, 1.104798ms Latencies [mean, 50, 95, 99, max] 1.328564ms, 1.192952ms, 1.933122ms, 2.922471ms, 23.264626ms Bytes In [total, mean] 1000, 4.00 Bytes Out [total, mean] 3000, 12.00 Success [ratio] 100.00% Status Codes [code:count] 200:250 Error Set:
結果をグラフで見る
ここまでテキスト形式で結果を見てきましたが、「vegeta plot」を使うことでグラフ形式でも見ることができます。
$ echo 'GET http://localhost:3000' | ./vegeta attack -duration=5s | ./vegeta plot > report.html
結果のHTMLを表示すると、こんな感じになります。
この例では5秒間負荷をかけていますが、その推移をグラフ表示した感じですね。
Requests [total, rate] 250, 50.20 Duration [total, attack, wait] 4.981240384s, 4.980150818s, 1.089566ms Latencies [mean, 50, 95, 99, max] 942.97µs, 880.2µs, 1.525036ms, 2.03296ms, 2.314226ms Bytes In [total, mean] 1750, 7.00 Bytes Out [total, mean] 0, 0.00 Success [ratio] 100.00% Status Codes [code:count] 200:250 Error Set:
複数のURLへ負荷をかける
複数のURLに対して負荷をかける場合は、以下のフォーマットに従って対象のURLを列挙します。
test-scenario.txt
GET http://localhost:3000 GET http://localhost:3000/echo/get?message=test POST http://localhost:3000/echo/post Content-Type: application/x-www-form-urlencoded @body.txt
HTTPヘッダも指定することができ、HTTPボディはファイルで指定します。
このファイルを、「-targets」オプションで指定します。
$ ./vegeta attack -targets=test-scenario.txt -duration=5s | ./vegeta report
結果。
Requests [total, rate] 250, 50.20 Duration [total, attack, wait] 4.980628214s, 4.980055554s, 572.66µs Latencies [mean, 50, 95, 99, max] 1.208511ms, 1.067828ms, 1.793813ms, 2.42757ms, 16.116178ms Bytes In [total, mean] 1335, 5.34 Bytes Out [total, mean] 1079, 4.32 Success [ratio] 100.00% Status Codes [code:count] 200:250 Error Set:
…個別のURLに対する結果はわからない感じ?
結果をグラフにしてみましょう。
$ ./vegeta attack -targets=test-scenario.txt -duration=5s | ./vegeta plot > report.html
こちらでも、わからないようで…。
まあ、おおまかな使い方はわかったので、良しとしましょう。