Macとtsungで手軽に数千~数万req/sのHTTP負荷テスト

今までWebの負荷テストにJMeterを使っていましたが､秒間数百req/sとあまり高負荷をかけられなかったので､[こちらの記事](https://heartbeats.jp/hbblog/2016/10/tsung.html)を見てtsungを使用することに決めました｡
今回は単純に一つのMacクライアントから一つのWebサーバに負荷を掛けてみます｡
Macで動かそうとすると色々ハマったので備忘録も兼ねて｡

# 環境
MBP13インチ(Early 2015)
CPU: i7(3.1GHz 物理2コア)
メモリ: 8GB

```
$ tsung -v
Tsung version 1.6.0
```

# インストール
``` sh
$ brew install tsung # tsung本体
$ tsung -v
$ sudo cpan Template # Template-Toolkitのインストール
# インストール時の質問は全て[y]または[Enter]で答えれば大丈夫でした
```
[Load Testing Rails 5 Action Cable with Tsung](http://www.thegreatcodeadventure.com/load-testing-rails-5-action-cable-with-tsung/)を参考に[Template-Toolkit](http://template-toolkit.org/)をインストールしましたが､それだけでは集計用プログラムを走らせる時に､

```
Can't locate Template.pm in @INC (you may need to install the Template module) (@INC contains: /Library/Perl/5.18/darwin-thread-multi-2level /Library/Perl/5.18 /Network/Library/Perl/5.18/darwin-thread-multi-2level /Network/Library/Perl/5.18 /Library/Perl/Updates/5.18.2 /System/Library/Perl/5.18/darwin-thread-multi-2level /System/Library/Perl/5.18 /System/Library/Perl/Extras/5.18/darwin-thread-multi-2level /System/Library/Perl/Extras/5.18 .) at /usr/local/lib/tsung/bin/tsung_stats.pl line 570.
```

というエラーメッセージが表示され､上手く動きませんでした｡
brewでperlをインストールしていたので､集計用プログラムのperlのパスが違っていたのが原因でした｡
`$ which perl`で`/usr/bin/perl`と表示された場合は以下の修正は不要です｡

``` sh
$ chmod u+w /usr/local/lib/tsung/bin/tsung_stats.pl # 書込権限付与
```

``` diff
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -w
+#!/usr/local/bin/perl -w
```

``` sh
$ chmod u-w /usr/local/lib/tsung/bin/tsung_stats.pl # 書込権限削除
```

# 負荷テスト設定
負荷テストの設定はxmlファイルへ書き込みます｡

``` sh
$ git clone https://github.com/kurebio/mac-tsung.git
$ vi tsung.xml # テスト先サーバなどを指定
```

``` tsung.xml
<?xml version="1.0"?>
<!DOCTYPE tsung SYSTEM "/usr/local/Cellar/tsung/1.6.0/share/tsung/tsung-1.0.dtd">
<tsung loglevel="notice" version="1.0">

<clients>
<client host='localhost' maxusers="500" use_controller_vm="true"/>
</clients>

<servers>
<server host="localhost" port="8000" type="tcp"/>
</servers>

<load>
<arrivalphase phase="1" duration="60" unit="second">
<users maxnumber="500" arrivalrate="100" unit="second"/>
</arrivalphase>
</load>

<sessions>
<session probability="100" name="http-test" type="ts_http">
<for from="1" to="10000" incr="1" var="counter">
<request> <http url="/test.json" method="GET" version="1.1"/> </request>
</for>
</session>
</sessions>

</tsung>

```

## clients
``` xml
<clients>
<client host='localhost' maxusers="500" use_controller_vm="true"/>
</clients>
```
ローカルのMacのみをクライアントとして使用します｡
maxuser: このクライアントが走らせる最大ユーザー数
use_controller_vm: trueで最大オープンソケット数の問題を解決するためのErlang virtual machineを使用しません｡[参考](http://tsung.erlang-projects.org/user_manual/faq.html#can-t-start-distributed-clients-timeout-error)

## servers
``` xml
<servers>
<server host="localhost" port="8000" type="tcp"/>
</servers>
```
localhost:8000へ向けて負荷テストを行います｡

## load
``` xml
<load>
<arrivalphase phase="1" duration="60" unit="second">
<users maxnumber="500" arrivalrate="100" unit="second"/>
</arrivalphase>
</load>
```
arrivalphase: 最初のフェーズで(二つ目以降のフェーズはありませんが)duration秒間アクセスし続けます｡
unitにはsecond, minute または hourが入り､現バージョンでは50日弱程度まで設定出来ます｡
[6.4.3. Duration of the load test](http://tsung.erlang-projects.org/user_manual/conf-load.html#duration-of-the-load-test)


users: 同時アクセスを行う数(ユーザー)を､最大arrivalrateユーザー/unitずつ増やし､maxnumberまで増加させます｡
増加量は必ずしもarrivalrateの値になるのではなく､クライアントの負荷によっては増加量が減少します｡
最大数は`$ ulimit -n`の数に依存するので､必要に応じて[macOS Sierraでulimitを変更する方法](http://christina04.hatenablog.com/entry/2017/05/28/105601)などで調整しましょう｡


durationに達するか､ユーザーがmaxnumberまで達したらユーザーの増加が止まり､全ユーザーのリクエストが完了次第プログラムが終了します｡リクエストが終了するまで止まらないため､リクエスト実行時間=durationではありません｡

## sessions
``` xml
<sessions>
<session probability="100" name="http-test" type="ts_http">
<for from="1" to="10000" incr="1" var="counter">
<request> <http url="/test.json" method="GET" version="1.1"/> </request>
</for>
</session>
</sessions>
```
session probability: 100%の確率でこのセッションを使用します(複数セッションがある場合は合計が100になるよう設定します)｡
probabilityの変わりにweightを用いると､weight="1"とweight="2"を設定した2つのセッションに､それぞれ1:2の割合でアクセスを割り振る､ということもできます｡
[Tsungで負荷テストしよう(3) - リアルな負荷のためのTips](https://heartbeats.jp/hbblog/2016/10/tsung3.html)

for: この場合`for(int counter = 1; counter <= 10000, counter++)`のような意味です｡
counterはリクエストURLに使用出来ます｡
`<http url="/page?id=%%_counter%%"></http>`
その他のループや条件分岐は以下より｡
[6.7.5. Loops, If, Foreach](http://tsung.erlang-projects.org/user_manual/conf-advanced-features.html#loops-if-foreach)

request: 1人のユーザーがどのエンドポイントへリクエストするか指定します｡

上記内容を総合すると､ローカルサーバのJSONに10,000回アクセスしようとする最大500人のユーザーが毎秒100人くらい現れる､という内容になります｡


# 負荷テスト実行
``` sh
$ tsung -f ./tsung.xml -l log start # 負荷テスト実行
Starting Tsung
Log directory is: ~/tsung/log/[YYYYMMDD-HHMM]
[os_mon] cpu supervisor port (cpu_sup): Erlang has closed

# 最新のログを集計し､レポートをブラウザで開く 上記負荷テスト実行と繋げても良し
$ cd ./log/`ls -t log | head -n 1` && \
  /usr/local/lib/tsung/bin/tsung_stats.pl && \
  open report.html && \
  cd ../../
```

# 結果
##  Mac→同ホスト内nginxの静的ファイル

|Code|Highest Rate|Mean Rate|Total number|
|---|---|---|---|
|200|27133.1 / sec|13299.37 / sec|5000000|

自ホストへのリクエストですが､最大秒間27133req/sでリクエストを行えました!

## Mac→リモートホスト(ConohaVPS 32G/12v)内nginxの静的ファイル

|Code|Highest Rate|Mean Rate|Total number|
|---|---|---|---|
|200| 11300.4 / sec | 5896.29 / sec | 499997 |

負荷を考え､リクエスト数を1ユーザー当り1,000に減らしました｡
この数値はtsungやnginxの限界というより､自宅回線やConohaのネットワークの限界(100Mbps)のようです｡
リモート側のCPU使用率は10%くらいで､トラフィックは90Mbps前後でした｡
3回ほどエラーで失敗しているのは､回線の逼迫が原因かと思います｡

## Mac→リモートホスト(ConohaVPS 32G/12v)内Wordpress(初期状態, kusanagi使用, cache未使用)TOPページ

|Code|Highest Rate|Mean Rate|Total number|
|---|---|---|---|
|200| 857.8 / sec| 602.67 / sec |50000|

リクエスト数を1ユーザー当り100に減らし､ネットワークの負荷を下げるためテンプレートファイルの出力を3バイトにし､php-fpmのchildrenをmin100~max500に設定しました｡
リモート側のCPU使用率は60%弱くらいでした｡
この辺りは[KUSANAGI for AWS](https://kusanagi.tokyo/cloud/kusanagi-for-aws/)の太い回線で､本番用のプラグインも入れてテストしたいですね｡


## JMeter参考
|ケース|max(req/s)|mean(req/s)|
|---|---|---|
|Mac→ローカル|1055.4|456.3|
|Mac→リモート静的ファイル|606|346.9|
|Mac→リモートWordpress|610.9|358.5|

# まとめ
Macのノート用の低消費電力CPUでも数万req/sを叩き出せて満足です｡
設定次第でPOSTリクエスト､認証ページへのアクセス､DBサーバーへのクエリ､レスポンスのチェックなど､自由度の高いテストが出来るので､色々試してみたいです｡
今回はMaxを測るため瞬間的にリクエスト数を増やしましたが､実務で使う場合は1週間の間毎秒100人ずつアクセスさせ､たまにユーザー数が10倍になる､といった設定で走らせた方が妥当かもしれません｡
VPSなどに負荷を掛ける場合は他の利用者のことも考え､CPUやトラフィックの数値を見ながら大量に長時間リソースを消費しないよう行いましょう｡