Stress Test with Locust written in Python

この記事は [Brainpad Advent Calender 2019](https://qiita.com/advent-calendar/2019/brainpad) 2日目の記事になります

こんにちは@nissy0409240です
BrainPadでエンジニアをしています
2019年もあと一ヶ月を切りましたが
皆様いかがお過ごしでしょうか

今年はプライベートで東京ドームへ行くことが出来た
感動の一年でありました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/101d87d7-7d16-0034-41cd-91e8505f1163.png)
東京ドームと言えば野球
野球と言えばピッチャーとバッターです
そんな前置きとは無関係に本エントリーでは
Pythonでシナリオを書ける負荷試験ツールLocustの基本的な機能を紹介させて頂きます

# Locustとは

LocustとはGUIのインターフェースもついているPython製の負荷試験ツールです
他の負荷試験ツールと比較して一番大きい特徴はPythonでシナリオを書けることです
言葉遊びなようでもありますがPythonでシナリオを書かないといけないとも言えます

公式ドキュメント等の各種リンクはこちらになります

* [公式サイト](https://locust.io/)
* [ドキュメント](https://docs.locust.io/en/stable/)
* [ソースコード](https://github.com/locustio/locust/)

ちなみにLocustはバッタやイナゴという意味の英語です

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/694c1dd7-cd8d-cbb7-93ac-ff4dda8355fd.png)
イラストはトノサマバッタです

# インストール方法

早速インストールしていこうと思います
インストールは下記コマンドを実行するだけです
※Macのローカル(Python3系)で動かすことを前提に進めます

```shell
$ brew install libev
$ python3 -m pip install locustio
```
実行後に`$ locust --help`コマンドを実行し、ヘルプが表示されればインストール成功です

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/9ecbd27a-fc65-c21e-bc5f-f0a7f1ac889e.png)
イラストはイナゴです

# シナリオを作成

シナリオはPythonスクリプトに記述します。
記述するファイル名自体に制約は有りません。
ですが、起動時にファイル名の指定をしないと`locustfile.py`というファイル名のものが実行されるので
ファイル名の制約が無いのなら`locustfile.py`というファイル名にすることをお薦めします。

以下がサンプルです

```python
from locust import HttpLocust, TaskSet, task, between


class WebsiteTasks(TaskSet):
    @task
    def index(self):
        self.client.get('/')
        

class WebsiteUser(HttpLocust):
    task_set = WebsiteTasks
    wait_time = between(5, 15)

```

ちなみに受けるAPIサーバーはこんな感じです

```python
from wsgiref.simple_server import make_server

import json


def api(environ, start_response):
    status = '200 OK'
    headers = [
        ('Content-type', 'application/json; charset=utf-8'),
        ('Access-Control-Allow-Origin', '*'),
    ]
    start_response(status, headers)

    return [json.dumps({'message':'hoge'}).encode("utf-8")]

with make_server('', 3000, api) as httpd:
    print("Serving on port 3000...")
    httpd.serve_forever()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/c28d5820-4516-7653-a9ec-f7484529cf92.png)
イラストはショウリョウバッタです

# 起動

下記コマンドにて起動します

```shell
$ locust
[2019-12-01 00:56:13,884] locust.main: Starting web monitor at *:8089
[2019-12-01 00:56:13,885] locust.main: Starting Locust 0.9.0
```

上の例では何もオプションを着けて実行しておりませんが

-fオプションで実行したいシナリオが書かれたファイルを
-Hオプションで叩きたいエンドポイントを指定できます

デフォルトでLocustのプロセスは8089ポートで起動しますが
-Pオプションで起動時のポートを変更することが可能です

ちゃんと指定すると
このように起動します

```shell
$ locust -f locustfile.py -H http://localhost:3000 -P 8089
[2019-12-02 03:38:42,717] locust.main: Starting web monitor at *:8089
[2019-12-02 03:38:42,718] locust.main: Starting Locust 0.13.2
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/5b044d70-ae06-192b-b7e5-50029ac88e8c.png)
イラストはイナゴの佃煮です

# テスト実行

起動後にブラウザから`http://localhost:8089`にアクセスすると
以下のようなGUIにアクセス出来ます

<img width="1438" alt="スクリーンショット 2019-12-02 3.39.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/5ef9f471-ab89-50ed-96db-907be1bba8cd.png">
先程何気なく指定した叩きたいエンドポイントがデフォルトで設定されています

<img width="1437" alt="スクリーンショット 2019-12-02 3.08.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/a19fdc5f-1033-43df-efe4-9b571ae3e008.png">
※起動時にHostを指定しない場合、Hostは空欄になります

空欄になっている設定項目があるので、以下の値を入力します
"Number of users to simulate" に100
"Hatch rate" に 1

それぞれの意味は以下の通りです

```shell
Number of users to simulate:何クライアント作成するか(最大で秒間何リクエストを送るかと同義)
Hatch rate:クライアントの作成スピード(毎秒ごと)
Host:接続先エンドポイント
```

今回は`http://localhost:3000`に対して1秒ごとに1クライアントづつ増やしながらリクエストを送信する
この時、最大でもクライアント数は100まで増やすこととする
という条件で実行します

<img width="1433" alt="スクリーンショット 2019-12-02 23.36.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/e1727862-6bfc-76cd-fe95-fc52445f074f.png">

入力後「Start Swarming」ボタンを押すとリクエストが実行されます

<img width="1435" alt="スクリーンショット 2019-12-02 21.40.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/63014987-639e-f561-d370-f2424b907133.png">

実行を終了させたい場合はブラウザの右上のSTOPボタンを押しましょう
押した後はSTATUSがSTOPPEDになり、終了したことが確認出来ます

<img width="1433" alt="スクリーンショット 2019-12-02 21.42.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/d6a724b9-b6b8-2754-6571-866d5a55acf6.png">

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/5f020214-6846-dc8c-1137-20b6bab2726e.png)
イラストはバッタやイナゴの大群です

# メトリクス

Chartsタブでメトリクスを見ることも出来ます
<img width="1436" alt="スクリーンショット 2019-12-02 22.00.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/c780a5ab-2e65-6be6-2b4e-dd127acc34a0.png">
<img width="1430" alt="スクリーンショット 2019-12-02 22.01.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/0ac20729-98e0-bf53-a27b-935db4c4005f.png">
こちらは1秒ごとに1クライアントづつ増やし、最大100クライアントまで増えたらそのまま100クライアントでリクエストし続けた時のメトリクスです

続いて、リクエストに失敗した時はどのようになるか見ていきましょう
今回はAPIサーバーのプロセスを途中で停止してみようと思います

<img width="1429" alt="スクリーンショット 2019-12-02 22.44.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/68c4e1e9-9295-6abc-1d4d-9d8c9cd94c83.png">
<img width="1432" alt="スクリーンショット 2019-12-02 22.43.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/53f8703b-170b-c41e-c483-b857d813c93a.png">
<img width="1431" alt="スクリーンショット 2019-12-02 22.44.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/048b0042-252b-3fc0-c36c-1f89c6bb6b3d.png">
<img width="1427" alt="スクリーンショット 2019-12-02 22.44.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/e6aa5783-4982-820b-0338-f95ac3ff57ed.png">
各タブにてこのように確認出来ます
失敗したリクエストの増え方やリクエスト全体に対する失敗の割合
Failuresタブではどのような理由で失敗したかも確認出来ます

また、Downloads Dataタブからは結果をダウンロードすることも可能です
<img width="1431" alt="スクリーンショット 2019-12-02 23.23.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/b0639cb8-2a39-6986-04a9-fb1e8f2b2bb9.png">

ダウンロードしたファイルの中身はこのようになっています
<img width="1182" alt="スクリーンショット 2019-12-02 23.24.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/1f8d1457-74c8-b99c-b15a-df856d28ace5.png">

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/5793dcb6-3cac-404c-b61f-f78b5a4022df.png)
イラストはバッタのキャラクターです

# 結び

以上、基本的な機能のみではございますがLocustの紹介をさせて頂きました
バッターのイラストとともにお別れです
最後までお付き合い頂きありがとうございました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100333/94637914-7e47-c1cf-dd88-bbaf84f00fb3.png)


## 参考
https://qiita.com/yamionp/items/17ffcc465272ad83c490
https://inside.pixiv.blog/east/5407
https://blog.htmlhifive.com/2015/08/14/web-server-load-testing-tools-2-locust/
https://docs.locust.io/en/stable/installation.html
https://docs.locust.io/en/stable/quickstart.html
https://co3k.org/blog/load-test-by-locust