Go
GAE
golang
GoogleCloudPlatform
performance

ちょっと良さげな負荷ツール vegeta をつかって分散負荷試験を実現してみる

motivation

  • Scalaのgatlingを使っていたけど、test_caseとして実装する必要があり、ローカル以外から仕組み的に実行するのがちょっと手間だった。(testをjarにするというのが難しい)
  • シンプルにインストールせずに実行できる負荷ツールはないものか
  • 負荷をかける側の限界でパフォーマンスを正しく測定できないこともあるので、クラスタ的に投げれたらなお良い

そんなフワーッとした雰囲気で調べてみたら面白そうなものがあった。
その名も Vegeta
名前がやばい。そう、みんな知ってるアレ。

github にもこんな画像がデカデカと

image.png

というわけで早速試してみましょう。
最初に結果を書くと、結構いい感触。おすすめしたい。

インストール方法

インストール方法は複数あります。
公式のドキュメントにあるのは2種類で、

Homebrewでインストールする(mac)

$ brew update && brew install vegeta

Sourceでインストールする

Mac以外でもインストールできますが、事前にgoをインストール/設定してある必要があります

$ go get -u github.com/tsenart/vegeta

の2点、その他に、

Sourceでインストールする

ソースコードをgit cloneしてある人はこちらの方法でもインストールできます

事前にgoの設定とdepのインストール/設定が必要です

$ mkdir -p $GOPATH/src/github.com/tsenant/
$ cd $GOPATH/src/github.com/tsnant/
$ git clone https://github.com/tsenart/vegeta.git
$ dep ensure
$ go install

インストールができたので、試しにぶっ放してみる

注)負荷ツールなので、自分が所有していない外部のサイトに対して実行するのは絶対にやめましょう!!(ダメ、ゼッタイ!)

今回は、自分でdeployしてあった、appengineに向けて実行してみます、
コマンドはこんな感じ
(当然のことながら、リクエスト先のドメインは適宜書き換えて利用してください。)

$ echo "GET https://{your-app-id}.appspot.com/" | vegeta attack -rate=10 -duration=5s | tee result.bin

こちらのコマンドを実行すると、 https://{your-app-id}.appspot.com に向けて 10 request/sec のレートで 5秒間 負荷をかけ続ける(この例の場合には / に対するGETリクエスト)

結果のファイルは result.bin という名前でファイルとしてコマンドを実行したディレクトリに作成される。

結果を見てみる

結果を見るためのコマンドは以下

text形式で表示する

$ vegeta -type=text report result.bin

(-type=text パラメータはデフォルト値のようなので、省略してもtext形式で表示されます)

こちらのコマンドを実行すると以下のようなレポートを見ることができる

Requests      [total, rate]            50, 10.20
Duration      [total, attack, wait]    4.910809485s, 4.90133s, 9.479485ms
Latencies     [mean, 50, 95, 99, max]  16.152ms, 9.778222ms, 17.040908ms, 211.802474ms, 211.802474ms
Bytes In      [total, mean]            1250, 25.00
Bytes Out     [total, mean]            0, 0.00
Success       [ratio]                  100.00%
Status Codes  [code:count]             200:50
Error Set:

もちろんたいしたリクエストではないので、100% 成功している。

appengine上のログだと2msecぐらいでレスポンスを返せているようなので、ちょっとRTT長めに見える。(自宅のネットワークが貧弱かも)

json形式で表示する

json形式で表示する場合には jq とセットで利用すると便利です(無くても問題ないです)

$ vegeta -type=json report result.bin | jq .

表示はこんな感じ

{
  "latencies": {
    "total": 807600022,
    "mean": 16152000,
    "50th": 9778222,
    "95th": 17040908,
    "99th": 211802474,
    "max": 211802474
  },
  "bytes_in": {
    "total": 1250,
    "mean": 25
  },
  "bytes_out": {
    "total": 0,
    "mean": 0
  },
  "earliest": "2018-08-31T10:24:20.814972+09:00",
  "latest": "2018-08-31T10:24:25.716302+09:00",
  "end": "2018-08-31T10:24:25.725781485+09:00",
  "duration": 4901330000,
  "wait": 9479485,
  "requests": 50,
  "rate": 10.20131270491887,
  "success": 1,
  "status_codes": {
    "200": 50
  },
  "errors": []
}

hist形式で表示する

ヒストグラム形式。今回のデータの場合以下のようなパラメータで確認してみます

$ cat result.bin | vegeta report -type='hist[0,5ms,10ms,15ms,20ms,100ms]'
Bucket           #   %       Histogram
[0s,     5ms]    0   0.00%
[5ms,    10ms]   30  60.00%  #############################################
[10ms,   15ms]   17  34.00%  #########################
[15ms,   20ms]   1   2.00%   #
[20ms,   100ms]  0   0.00%
[100ms,  +Inf]   2   4.00%   ###

まぁまぁ早いけど、極稀に結構遅いレスポンスがあるみたいですね。

グラフをplotしてhtmlを吐き出す

グラフをhtml形式で吐き出すこともできます。

$ cat result.bin | vegeta plot > result_plot.html

と実行すると result_plot.html が出力されます。

htmlなのでもちろんブラウザで開くことができます。

スクリーンショット 2018-08-31 14.42.09.png

このようなグラフを見ることができます。
グラフ上にカーソルを合わせることで、そのタイミングでのレイテンシなどが表示されるので便利です。

どうやら負荷を書け始めたタイミングでのレイテンシが大きくなっているようですね。(スピンアップかな?)

グラフはpng形式でのダウンロードもできます。あと、グラフ内の数字を入力することでグラフを平滑にすることもできます。

グラフの面白い機能としては、複数別々に実行した結果を重ね合わせて一つのグラフにすることもできるようです。

例えば、 100 req/sec, 200 req/sec, 500 req/sec と実行したファイルを重ね合わせたい場合には

$ vegeta plot results.100qps.bin results.200qps.bin results.500qps.bin > plot.html

のような感じです。

中締め

という感じで ab(apachebench) みたいな感じで簡単にコマンドで実行できるのは便利(やれることちょっと違うけど)

goのバイナリとしてインストールできるので、いろいろインストールしたくないという環境でも、バイナリを置くだけで良いので使い勝手良さそう

Appendix 1 -- macだけじゃなくて他の環境用のバイナリもほしいぃ!

windowsやlinux環境のバイナリもあると便利ですね。goなのでサクッとクロスコンパイルしてしまいましょう。

vegetaのプロジェクトを git clone し、main.go があるディレクトリで以下を実行します。

Linux用バイナリなら

$ GOOS=linux GOARCH=amd64 go build .

Windows用バイナリなら

$ GOOS=windows GOARCH=amd64 go build .

このコマンドでカレントディレクトリに吐かれたバイナリファイルを各環境のパスが通ったディレクトリに投げ込めばインストール完了。便利!

linuxバイナリはこの記事のあとの方で使います。

Appendix 2 -- 負荷テスト中の状態をリアルタイムで見たい!

大体のツールは負荷テストの終了後に結果ファイルからグラフを生成して、、みたいな感じのものが多いと思います

iTermを使っている場合、jaggrを使ってjplotと連携することでリアルタイムで負荷状況が見れます!

jplotをインストールしましょう

jplotはiTerm上でグラフをプロットできるようにするツールのようです。

$ brew install rs/tap/jplot

もしくは

$ go get -u github.com/rs/jplot

という感じでインストールができます。

jaggrをインストールしましょう

jaggrはjsonをリアルタイムで集計するためのツールのようです。主にjplotと併せて使うもののようです。

$ brew install rs/tap/jaggr

もしくは

$ go get -u github.com/rs/jaggr

という感じでインストールできます。

早速リアルタイム実行してみる

各コマンドを駆使するので若干コマンドがややこしい、

echo 'GET http://{your-app-id}.appspot.com' | \
    vegeta attack -rate 5000 -duration 10m | vegeta encode | \
    jaggr @count=rps \
          hist\[100,200,300,400,500\]:code \
          p25,p50,p95:latency \
          sum:bytes_in \
          sum:bytes_out | \
    jplot rps+code.hist.100+code.hist.200+code.hist.300+code.hist.400+code.hist.500 \
          latency.p95+latency.p50+latency.p25 \
          bytes_in.sum+bytes_out.sum

paint_test.gif

こんな感じで負荷をかけながらグラフが見れます。

負荷をかけながらアプリケーションのパラメータ変更や、サーバの構成変更(k8sのローリングアップデートなど)時のレイテンシの変化とか調べたいときに良さそう。

レイテンシのミリ秒の m が Mega に誤認されていそうな気はしますが、修正方法わかる方はそっとコメントくださいw

ちなみに

結構な負荷をappengineにかけたので、一瞬ですごい数のインスタンスが起動していました・・・(汗
スクリーンショット 2018-08-31 12.09.25.png

Appendix 3 -- 複数のパスに対してPOSTリクエストを送りたい

targetを利用すればできます

target.txt
POST http://{your-app-id}.appspot.com
Content-Type: application/json
@empty.json

POST http://{your-app-it}.appspot.com/test
Content-Type: application/json
@empty.json

1行目がリクエストを投げるURI.
2行目がヘッダ.
3行目がpostのbodyを定義したjsonファイルになります。
認証で Authorization: Bearer ヘッダが必要な場合などにはここに記述すればよいです。

このように記述すると複数の箇所にリクエストを送ることができます。POSTとGETを混ぜわせるということも可能です。

リクエストのbodyは empty.json に定義してあり、今回は以下のように中身なしのjsonを定義しています。(そもそもサーバ側でPOSTを実装していないので 404 が返る想定なので)

empty.json
{}

2つのファイルを配置したディレクトリで以下のコマンドを実行します

$ vegeta attack -rate=10 -duration=5s -targets=target.txt | vegeta report

細かいパラメータは説明しません、
追加されているのは -target= オプションで、そこに先程作成したtarget.txtを渡します。

先程までの例ではbinファイルを吐き出していましたが、とりあえず結果だけみたいので直接 vegeta report にパイプで渡します。

結果は

Requests      [total, rate]            50, 10.20
Duration      [total, attack, wait]    4.909292548s, 4.900122s, 9.170548ms
Latencies     [mean, 50, 95, 99, max]  9.579154ms, 9.050858ms, 12.695588ms, 17.721396ms, 17.721396ms
Bytes In      [total, mean]            900, 18.00
Bytes Out     [total, mean]            150, 3.00
Success       [ratio]                  0.00%
Status Codes  [code:count]             404:50
Error Set:
404 Not Found

という感じになり、Errorとして404が返ってきていることがわかります。

Appendix 3 -- Distributed attacks(分散アタック)をしたい!

これはほんとに欲しかった機能!

試してみましょう

事前設定

  • GCEにインスタンスを2台起動する
  • ssh-keyでssh接続できるようにしておく
  • vegetaコマンドを事前にサーバに送っておく(さっきbuildしたものを送れば良い、もちろんpathも通してネ)
  • pdsh をインストールしておく

pdshのインストール

macであればhomebrewでインストールができる

$ brew install pdsh

分散アタックしてみる

以下のコマンドで実行してみる

PDSH_RCMD_TYPE=ssh pdsh -b -w 'xxx.xxx.xxx.xxx,yyy.yyy.yyy.yyy' \
    'echo "GET http://{your-app-it}.appspot.com/" | vegeta attack -rate=100 -duration=10s > result.bin'

それほどリクエストを投げていないので、すぐに終わる。

結果を取得する

$ for machine in xxx.xxx.xxx.xxx yyy.yyy.yyy.yyy ; do
    scp $machine:~/result.bin $machine.bin &
  done

ファイルの転送をバックグラウンドに送っているので、ファイルはすぐにカレントディレクトリに現れません。

ps ax|grep scp などとしてみるとプロセスが複数動いてファイル転送していることがわかります。

結果を表示する

以下のように取得した複数の .bin ファイルを羅列することで結果が見れます
ファイルが転送されてくるまでゆっくり待ちましょう

vegeta report xxx.xxx.xxx.xxx.bin yyy.yyy.yyy.yyy.bin

表示のイメージはこんな感じ

Requests      [total, rate]            2000, 200.14
Duration      [total, attack, wait]    9.997967233s, 9.993000863s, 4.96637ms
Latencies     [mean, 50, 95, 99, max]  12.590457ms, 4.373789ms, 13.789848ms, 280.666678ms, 447.50811ms
Bytes In      [total, mean]            50000, 25.00
Bytes Out     [total, mean]            0, 0.00
Success       [ratio]                  100.00%
Status Codes  [code:count]             200:2000
Error Set:

もちろん分散データでもhist表示もできます

vegeta report -type='hist[0,5ms,10ms,15ms,20ms,100ms]' xxx.xxx.xxx.xxx.bin yyy.yyy.yyy.yyy.bin
Bucket           #     %       Histogram
[0s,     5ms]    1699  84.95%  ###############################################################
[5ms,    10ms]   194   9.70%   #######
[10ms,   15ms]   9     0.45%
[15ms,   20ms]   8     0.40%
[20ms,   100ms]  29    1.45%   #
[100ms,  +Inf]   61    3.05%   ##

htmlのplotに関してはエラーが出てうまくいきませんでした。

Appendix 4 -- ライブラリとして利用する

vegetaはライブラリとして利用できます
複雑な条件での負荷試験を行いたい場合にはこの使い方が良さそう。

もしくはテストケースまるまるバイナリとして配置したい場合に利用できそう。


package main

import (
  "fmt"
  "time"

  vegeta "github.com/tsenart/vegeta/lib"
)

func main() {
  rate := vegeta.Rate{Freq: 100, Per: time.Second}
  duration := 4 * time.Second
  targeter := vegeta.NewStaticTargeter(vegeta.Target{
    Method: "GET",
    URL:    "http://localhost:9100/",
  })
  attacker := vegeta.NewAttacker()

  var metrics vegeta.Metrics
  for res := range attacker.Attack(targeter, rate, duration, "Big Bang!") {
    metrics.Add(res)
  }
  metrics.Close()

  fmt.Printf("99th percentile: %s\n", metrics.Latencies.P99)
}

Appendix 5 -- 見せて貰おうか。appengineのスケール性能とやらを!

やろうと思いましたが、先程投げまくったリクエストでそこそこ課金されそうだったので、無茶はやめました・・・

まとめ

  • なんとなく(githubの画像的に)イロモノ感があったけど、使ってみたら機能的には良い感じ。
  • クローズドなネットワーク内でテストしたい、でもサーバに変なものインストールしたくないって時には便利そう
  • 結果のbinファイルさえあればあとはレポートが好きなように見れるのでそういう点も使いやすい(テスト結果の管理)
  • 分散負荷をかける環境を気軽に作れるのは便利

というわけで、最初書いたように割とおすすめなツールでした。

おしまい。