JMeterでグラフィカルに変幻自在なアクセス負荷を設定する(後編)

# はじめに

前回はJMeterにThroughputShapingTimer(プラグイン)を組み込んで、
グラフィカルに負荷量を調整する方法を説明しました。
[JMeterでグラフィカルに変幻自在なアクセス負荷を設定する(前編)](https://qiita.com/Kenta-Sado/items/5a60e7dc4b43654181f9)
![sample_image](https://jmeter-plugins.org/img/wiki/throughput_shaping_timer1.png)
↑アクセス負荷量の制御イメージ　[公式ページ](https://jmeter-plugins.org/wiki/ThroughputShapingTimer/)より

簡単な使用法の紹介までで話が長くなってしまったので、今回は実践的な使い方を説明します。

# 前回のあらすじ

上司さんからWebサイトの性能試験を任されたA君。
「Twitterでバズった時のアクセス急上昇を試験したい」という上司からの無茶振りに対し、
ThroughputShapingTimerを使うことでA君はJMeterで期待に添う試験ができるようになりました。

しかしA君はまだ単一ページへの試験しか検証できていません。
Webサイトのアクセス実績に基づき、より複雑な試験シナリオの作成に奮闘するのであった。。。

# Throughput Shaping Timer を使ってみる（応用編）

小ネタはこのくらいにしておき、早速試験パターンとJMeterの試験シナリオ作成を説明していきます。

この記事では以下３つの試験パターンをホップ・ステップ・ジャンプの流れでご紹介します。

- １つの画面遷移パターンをシリアルに実行する
- １つの画面遷移パターンをパラレルに実行する
- 複数の画面遷移パターンをパラレルに実行する

## 環境情報

- Mac(Catalina)
- Apache httpd server(2.4.x)
- Java8(Oracle java 8u202)
- JMeter(5.3)

## 環境復旧

Apacheが停止している場合は前回と起動手順が異なるので、備忘かねて残します。

### 疎通確認

JMeterからリクエストを送信することでも確認できますが、ここではCURLコマンドで確認します。

<pre>
$ curl http://localhost:8080/index.html
curl: (7) Failed to connect to localhost port 8080: Connection refused
</pre>

`Failed`となった場合、前回使用したApacheに通信できていないことがわかります。

### コンテナの状態確認

<pre>
$ docker ps -a
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS                  PORTS               NAMES
e2dc7c3714ee        httpd:2.4           "httpd-foreground"   6 days ago          Exited (0) 5 days ago                       my-apache-app
</pre>

`Status`が`Exited`になっているので、コンテナが停止状態（正常）であることがわかります。

万が一`Up`になっている場合はハング状態なので、一度コンテナを停止させます。

### コンテナの停止（参考）

<pre>
$ docker stop e2dc7c3714ee
</pre>

コンテナIDを指定して停止します。
コマンド実行後に改めてコンテナ状態を確認し、`Exited`になっていることを確認します。
（ダメだったらPC再起動ですね）

### コンテナの起動

<pre>
$ docker start e2dc7c3714ee
e2dc7c3714ee
</pre>

<pre>
$ docker ps -a             
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                                           NAMES
e2dc7c3714ee        httpd:2.4           "httpd-foreground"   6 days ago          Up 3 seconds        0.0.0.0:8080->80/tcp, 0.0.0.0:32772->8080/tcp   my-apache-app
</pre>

<pre>
$ curl http://localhost:8080/index.html
hello
</pre>

これで準備完了です。

## １つの画面遷移パターンをシリアルに実行する

Amazonさんを例に挙げると、ほとんどの人が以下の流れでサイトを利用しますね

1. トップページを表示する
2. 商品を選択する
3. 購入する

本パターンでは、ひとりのユーザが同じページ遷移を延々と繰り返す前提で負荷量調整をする方法を説明します。

### 状況設定

- ユーザはひとり
- ページ01 -> ページ02 -> ページ03 の順に画面遷移する
- 普段は15RPS(RequestPerSecond)だが、瞬間的に75RPSまで上昇する

### アクセスの流れをJMeterで表現する

前回作成したスレッドグループを流用します
今回は試験用にコンテンツを用意していないので、同一のHTMLにクエリ文字を変えてアクセスすることで、３種類のURLへアクセスしていることにします。

*ページ01*
![スクリーンショット 2020-10-18 22.08.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/b2c9e4ae-9ec2-5816-a0b3-43edaf5762b4.png)

*ページ02*
![スクリーンショット 2020-10-18 22.14.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/edebf606-7196-cdfc-f029-068ce0aabd53.png)

*ページ03*
![スクリーンショット 2020-10-18 22.15.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/b5df9747-6642-d178-3b81-5e44fa3f2668.png)

### ThroughputShapingTimerを設定する

![スクリーンショット 2020-10-18 22.17.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/507aaa7e-2699-3e77-31e3-ff5cc110f1c9.png)

負荷量を変更しています。

### スレッドグループを設定する

![スクリーンショット 2020-10-18 22.43.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/da5c85cb-e563-60ad-277f-f0b3a0ef2fef.png)

スレッド数（並列度）を１に設定します。

### JMeterを実行する

![スクリーンショット 2020-10-18 22.22.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/283b89a0-a172-85b1-2da8-b65f9668d1b5.png)

３つのURLに大体均等にアクセスできましたね。
（完全に均等でないですが、ThroughputShapingTimerちゃんはお茶目なので許してあげてください）

### Apacheログ解析

<pre>
$ docker logs e2dc7c3714ee | tail -1094 | awk '{print $4}' | sed 's_\[18/Oct/2020:__' | uniq -c
〜〜〜
  16 13:20:00
  14 13:20:01
  16 13:20:02
  14 13:20:03
  16 13:20:04
  14 13:20:05
  15 13:20:06
  26 13:20:07
  38 13:20:08
  50 13:20:09
  63 13:20:10
  75 13:20:11
  63 13:20:12
  51 13:20:13
  39 13:20:14
  27 13:20:15
  16 13:20:16
  14 13:20:17
  15 13:20:18
  14 13:20:19
〜〜〜
</pre>

15RPSから75RPSまで上昇し、15RPSまで戻りましたね。

※これから何度か試験するので、今回のアクセス総数である1094行のログを確認対象にしています
※sedコマンドの区切り文字はスラッシュ以外も指定できるよ！とのアドバイスをいただき見やすくなりました

### Apacheログ解析（シリアルであることを確認）

<pre>
$ docker logs e2dc7c3714ee | tail -10 | awk '{print $4, $7}'
[18/Oct/2020:13:20:36 /index.html?test02
[18/Oct/2020:13:20:36 /index.html?test03
[18/Oct/2020:13:20:36 /index.html?test01
[18/Oct/2020:13:20:36 /index.html?test02
[18/Oct/2020:13:20:36 /index.html?test03
[18/Oct/2020:13:20:36 /index.html?test01
[18/Oct/2020:13:20:36 /index.html?test02
[18/Oct/2020:13:20:36 /index.html?test03
[18/Oct/2020:13:20:36 /index.html?test01
[18/Oct/2020:13:20:36 /index.html?test02
</pre>

綺麗にページ01 -> ページ02 -> ページ03 の順でアクセスが来ていることがわかりますね。

## １つの画面遷移パターンをパラレルに実行する

前回はひとりのユーザが同じページ遷移を延々と繰り返す前提で負荷量調整しましたが、実際のWebサイトではあり得ませんよね。

３人のユーザが同時に前回同様の画面遷移をする前提で負荷量調整をする方法を説明します。

### 状況設定

- ユーザは３人
- ページ01 -> ページ02 -> ページ03 の順に画面遷移する
- 普段は15RPS(RequestPerSecond)だが、瞬間的に75RPSまで上昇する

### スレッドグループを設定する

![スクリーンショット 2020-10-18 23.41.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/8249bbcd-4a80-93f1-b274-de0c0c2b10b8.png)

スレッド数（並列度）を３に設定します。

※他の設定は前回同様です

### JMeterを実行する

![スクリーンショット 2020-10-18 23.45.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/0633106d-eba0-f024-4884-a179abe06742.png)

今回も３つのURLに大体均等にアクセスできましたね。

### Apacheログ解析

<pre>
$ docker logs e2dc7c3714ee | tail -1082 | awk '{print $4}' | sed 's_\[18/Oct/2020:__' | uniq -c
〜〜〜
  14 14:44:50
  15 14:44:51
  14 14:44:52
  15 14:44:53
  15 14:44:54
  14 14:44:55
  16 14:44:56
  14 14:44:57
  16 14:44:58
  13 14:44:59
  28 14:45:00
  38 14:45:01
  50 14:45:02
  65 14:45:03
  75 14:45:04
  63 14:45:05
  51 14:45:06
  37 14:45:07
  28 14:45:08
  15 14:45:09
  15 14:45:10
  15 14:45:11
  15 14:45:12
  15 14:45:13
〜〜〜
</pre>

15RPSから75RPSまで上昇し、15RPSまで戻りましたね。

### Apacheログ解析（パラレルであることを確認）

<pre>
$ docker logs e2dc7c3714ee | tail -10 | awk '{print $4, $7}'
[18/Oct/2020:14:45:29 /index.html?test03
[18/Oct/2020:14:45:29 /index.html?test02
[18/Oct/2020:14:45:29 /index.html?test01
[18/Oct/2020:14:45:29 /index.html?test02
[18/Oct/2020:14:45:29 /index.html?test03
[18/Oct/2020:14:45:29 /index.html?test02
[18/Oct/2020:14:45:29 /index.html?test03
[18/Oct/2020:14:45:29 /index.html?test01
[18/Oct/2020:14:45:29 /index.html?test03
[18/Oct/2020:14:45:29 /index.html?test01
</pre>

前回に比べてアクセス順が ページ01 -> ページ02 -> ページ03 の順ではなくなりましたね。
３人のユーザが同時にアクセスして順序が前後したことがわかります。

## 複数の画面遷移パターンをパラレルに実行する

前回までは以下のパターンを試験してみました。（パターン１）
1. トップページを表示する
2. 商品を選択する
3. 購入する

今回は以下の画面遷移を別のスレッドグループとして追加します。（パターン２）
1. トップページを表示する
2. 商品を選択する
3. 他のおすすめ商品を選択する

今回は
（パターン１）３人のユーザが同時に画面遷移をする
（パターン２）５人のユーザが同時に画面遷移をする
前提で負荷量調整をする方法を説明します。

### 状況設定

（パターン１）※前回同様

- ユーザは３人
- ページ01 -> ページ02 -> ページ03 の順に画面遷移する
- 普段は15RPS(RequestPerSecond)だが、瞬間的に75RPSまで上昇する

（パターン２）

- ユーザは５人
- ページ11 -> ページ12 -> ページ13 の順に画面遷移する
- 50RPS(RequestPerSecond)が継続的に発生する

### アクセスの流れをJMeterで表現する

前回作成したスレッドグループを流用します。

以下の写真のように、スレッドグループを複製して配置します。
統計レポートはスレッドグループと同じ階層に配置してください。

![スクリーンショット 2020-10-19 2.53.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/1f52cd32-5e8e-59ac-a165-9c413c6bcf36.png)

複製した設定を以下のように設定変更します。

スレッドグループ：
　スレッド数：`５`
HTTP リクエスト(test11)：
　パス：`/index.html?test11`
HTTP リクエスト(test12)：
　パス：`/index.html?test12`
HTTP リクエスト(test13)：
　パス：`/index.html?test13`

### ThroughputShapingTimerを設定する

![スクリーンショット 2020-10-19 23.54.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/e2651291-de5d-8e5b-5e85-a6d5646bc43b.png)

50RPSで均一にします。
※JMeterはいきなりフルパワーを発揮できないので、指定負荷量に到達するまで徐々に駆け上がるようにしましょう

### JMeterを実行する

![スクリーンショット 2020-10-20 0.13.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/9a19198e-552b-e2b2-c8b5-56a44aed6072.png)

スレッドグループ１と２それぞれで均一の負荷量が保てましたね。

### JMeterレポートを作成する

今回は試験内容が複雑なので、Apacheログの解析では期待通り動いているかの確認が困難です。
JMeterの標準機能でレポート出力し、結果を確認しましょう。

1. JMeterレポートのグラフ描写間隔を調整する
![スクリーンショット 2020-10-20 1.47.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/e8059d6f-079f-040b-84d0-341ed5187f58.png)
いきなり通常は実施しない手順ですが。。。
レポートはデフォルトでは１分単位でグラフを描写します。
今回は試験時間がとても短いので、グラフの描写間隔も短くします。<br>
`<JMeterを展開したディレクトリ>/apache-jmeter-5.3/bin/user.properties`を開きます。
76行目あたりに`jmeter.reportgenerator.overall_granularity`というグラフの描写間隔の設定値があるので、コメントアウトを外して、60000ミリ秒から1000ミリ秒に変更します。

1. レポート生成メニューを開く
![スクリーンショット 2020-10-20 1.28.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/8bdd1cec-92ba-c915-72a8-828708910e0e.png)
メニューバーから`Tools`->`Generate HTML report`を選択します。

1. レポート生成に必要な情報を入力する
![スクリーンショット 2020-10-20 1.31.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/2ce6c85e-45a2-4e46-d794-66f4483298bc.png)
Result file (csv or jtl)：統計レポートで指定した出力ファイル名
user.properties file：bin配下にあります（先ほど書き換えたファイル）
Output directory：どこでもOKです

1. レポートを生成する
![スクリーンショット 2020-10-20 1.32.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/0c5f5f0c-3dc9-c36e-a423-c33ec5e68a0a.png)
下部のGenerate reportをクリックしてチェックマークがつけば生成完了です。

### JMeterレポートを確認する

1. 生成されたレポートをブラウザで開く
![スクリーンショット 2020-10-20 1.52.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/1b3d071a-219e-86f0-e9c3-7ef1d6671f19.png)
先ほどOutput directoryに指定した場所にレポートが生成されているので、`index.html`を開きます。

1. 目的のグラフに遷移する
![スクリーンショット 2020-10-20 1.53.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/feebf817-6682-4d18-64d5-eef3873ad9ee.png)
`Throughput`を選択します。

1. 目的のグラフに遷移する
![スクリーンショット 2020-10-20 1.55.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/911dc333-280b-7b9e-7d9f-7850a4b4bda2.png)
`Transactions Per Seconds`を選択します。

1. 美しいグラフに感動する
![スクリーンショット 2020-10-20 1.56.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784816/b8f5e432-1da8-5449-7f8d-e37a18de7f2a.png)
`ThroughputShapingTimer`で設定した通りの波形が見事に描けましたね！！<br>
**おめでとうございます、これであなたもJMeterマスターです！**

# 後日談

A君「ついにJMterの設定が完了しました！」
上司「ありがとう、今回の性能試験はA君に一任するよ。頑張って！」
A君「はい！」

見事試験対象のWebアプリケーションを性能測定する準備ができたA君。
しかしここからが本当の地獄の始まりでした。
まだまだ経験が浅いA君は、これから立ちふさがるであろう数々の問題をまだ予想できていないのでした。。。

続編は予定しておりませんが、、、あえて次回タイトルをつけるのであれば
**次回：「性能未達も、本番障害も、あるんだよ」**

性能試験は問題発見の手段でしかありません。
A君の試験計画を上司さんがより妥当なものへと導き、A君が大小問わず様々な問題に対して関係者を巻き込んでいくことが、ハッピーエンドへの近道ですね。

A君はさておき、この記事でより豊かなJMeterライフを送れる方が一人でも増えたら嬉しいです。

# Appendix

記事を書いていく中で「こういう点でつまずくかもしれない。。。」と思ったポイントをQA形式でまとめます。
他にも気になることがありましたらコメントください。

### Q1 設定した負荷量に到達しない

A君「設定は正しいのですが、想定の負荷量に達しません。。。設定値を若干下回ります。。。」
上司「仕様？だよ」

今回の検証では少量の負荷しかかけていないので問題になりませんでしたが、数百RPSを超えたあたりから実測値が設定を若干下回ります。

OSSなのでそこは大目に見て、下回ることを前提に若干大目の負荷量を設定しましょう。

### Q2 設定した負荷に全然到達しません。。。

A君「本番相当の負荷量を設定したのですが、実測値が大幅に下回ります。。。JMeterもエラーを吐いています。。。」
上司「エラーを見ないとなんとも言えないが、JMeterが限界に達しているかもしれないね」

JMeterはJavaで動くので、かなりリソースを消費します。
まずはJMeterに割り当てるメモリを増やしてみてください。

参考：
[JMeterでOutOfMemoryが発生した場合の対応方法](https://qiita.com/akiynkmr/items/e7e7aba567cc6cf8f8d9)

メモリに余裕はあるが想定の負荷量に到達しない場合は、OSの接続上限数に達している可能性があります。
ファイルディスクリプタの上限数を引き上げてみましょう。
（よくわからない場合は基盤担当の人にやってもらいましょう！）

参考：
[reboot/shutdown後も設定を維持したい場合はulimit -nではなく/etc/security/limits.confを編集する(「Too many open files」「ファイルを開きすぎています」エラー対策)](https://qiita.com/neko_the_shadow/items/841bf59c4f80588baad7)

### Q3 どう頑張っても期待の負荷量が出ません。。。

A君「いろいろ設定を見直したのですが、本番相当の負荷量が出ません。。。」
上司「JMeterサーバの限界かもしれないね。。。」

大規模システムは複数のサーバで処理を分散するように、１台のJMeterで出せる負荷量には限界があります。（一概には言えませんが。。。）
以下いずれかの方法でJMeterの処理を分散させましょう。

例：１０台のJMeterに処理を分散する場合

- ThroughputShapingTimerの設定値を1/10にして10台のJMeterに配置して実行する
- 上記に加えてJMeterをMaster-Slave構成にする

参考：
[AWSでJMeterのMaster/Slave環境を構築（CentOS 7）](https://qiita.com/marumaru/items/1c79b9fef5bbeb1adb42)
FireWallとKeyStoreがミソですね

Master-Slave構成にすることで実行ログがMasterに集約されるので、レポート作成が楽になるのがメリットですね。
しかしサーバインスタンスが1台増えるので、コスト面がデメリットです。

# さいごに

長くなってしまいましたが、今回の記事はこれで完結です。

記事を書くって結構大変だな、ということを実感しました。

今までQiita等々には何度も助けられていますが、なんとなく流し読みするばかりでした。
これからはもう少し噛み締めながら記事を拝見させていただこうと思います。

今回は性能試験に関する記事を掲載しましたが、本業はJavaフレームワークのカスタマイズをしています。
（SpringFrameworkとか）
Javaフレームワークは業務に関わる内容が多く記事にしにくいので、
これを機に今まで手をつけてこなかった分野に挑戦するのもありかなと思っています。

GitHubPagesとGitHubActionsでWebページでも作ってみようかなと思うのですが、
基盤->Javaフレームワークときて画面は一切触ったことないので、失踪しないよう頑張ります。

# 参考
[ThrouputShapingTimer](https://jmeter-plugins.org/wiki/ThroughputShapingTimer/)
[JMeter generating-dashboard](https://jmeter.apache.org/usermanual/generating-dashboard.html)