(負荷試験ツール)Locustで基本的な使い方/よくある使い方

# 注意
この記事は古く、v1.0以降は記載した実装では動作しません
別の方がv1.0での実装を記事にされていますので、そちらを参照してください
https://qiita.com/sekikatsu/items/992e82671aa505c5a652

### Locustって何よ
- 負荷試験ツール
- 一度に大量のリクエストを発生させることができる
- Python
- 簡単に負荷試験の処理を定義できる

### 環境構築

公式で配布しているDockerImageを使ってdocker-compose.yamlを定義する
筆者の環境はMac OS Catalina(10.15.1)
ローカル環境で実行した

```
version: "3.4"

x-common: &common
  image: locustio/locust
  environment: &common-env
    TARGET_URL: http://example.com
    LOCUSTFILE_PATH: /tests/basic.py
  volumes:
    - tests/:/tests

services:
  locust-master:
    <<: *common
    ports:
      - 8089:8089
    environment:
      <<: *common-env
      LOCUST_MODE: master
  
  locust-slave:
    <<: *common
    environment:
      <<: *common-env
      LOCUST_MODE: slave
      LOCUST_MASTER_HOST: locust-master
```

軽く解説。

master: UI用のWebサーバを立てる。slaveのコントロールもします。
slave: 負荷のシナリオを実行する。大きな負荷をかけたい場合は下記のようなslaveを増やす。数値で指定したような負荷がかからない場合はslaveを増やすことで対応するとよいです。
docker-compose.yaml中の `locust-slave` を `locust-slave1`としてリネームしてコピペすれば増えます。

TARGET_URL: 負荷をかける対象のBase URL
LOCUSTFILE_PATH: LocustFileが存在するPath.docker上でのPATHなので注意
### ユースケース
- とあるアプリで、サービス開始時の瞬間的な負荷を計測したい
- APIは以下

```
GET /initalize: 初期化時のマスタデータ取得用
GET /item_list: 初期画面表示時のリスト表示用
POST /add_item: リストに項目を追加する用
```

### 実装

同じ階層にbasic.pyを定義

```python
from locust import HttpLocust, TaskSequence, seq_task, between
from locust.clients import HttpSession

def initialize(l):
    l.client.get("/initalize")

def item_list(l):
    l.client.get("/item_list")

def add_item(l):
    l.client.post("/add_item", {"item_name":"あああ"})

class ScenarioTask(TaskSequence):

    # ()内の数値＝実行順番
    @seq_task(1)
    def initialize(self):
        initialize(self)

    @seq_task(2)
    def item_list(self):
        item_list(self)
    
    @seq_task(2)
    def add_item(self):
        add_item(self)

class WebsiteUser(HttpLocust):
    task_set = ScenarioTask
  
    # タスク間の時間。1~3秒でランダム秒数で実施する
    wait_time = between(1.0, 3.0)
```
### フォルダ構成
```
performance-test-cli
├docke-compose.yaml
└tests
  └ basic.py
```

### いざ実行
docker-compose.yamlのある階層で

```
docker-compose up -d
```

ブラウザで `localhost:8089`を叩くと、以下のUIが表示されます。
以下２数値を指定して実行。
- Number of users to simulate: 最終的に到達するユーザ数(=クライアント数)
- Hatch rate: 1秒あたりに増加する秒間ユーザ数

この記事では
1ユーザあたり1秒に1回リクエストを投げるようにして、ユーザ数＝秒間リクエスト数となるように調整します。

<img width="1399" alt="スクリーンショット 2019-12-14 17.11.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55118/443656db-f945-e5ee-cc4d-a312b8347bc0.png">


上記のStart押せば開始。

### UIについて説明
- Chats タブ：　どのくらいの秒間リクエストでているのか等をグラフで見れます
- Failures: リクエスト失敗した場合は簡単な例外内容含めて出してくれます
- Exceptions: 例外のStackTrace
- Download Data: CSVとして結果のデータをダウンロードできます。
- Slaves: LocustのSlaveの状態を見れます。


### 注意点
`tests` 直下に `__pycache__` が出来上がる。
コンパイルしたものが格納されているようなので、実行前に消してやったほうが良いようだ。

### よくある使い方
#### Header付けて送りたい/request bodyつけたい
```Python
def initialize(l):
    l.client.get(url="/initalize",data={"item": 1},headers={"auth": "xxxxx"})
```

#### リクエストボディをjsonとして投げたい
以下のようにjsonとしてフォーマット

```python
def add_item(l):
    l.client.post(
        url="/add_item",
        headers={'content-type': 'application/json'},
        json={"item_name":"あああ"}
    )
```

#### wait_timeを1秒固定にしたい(1ユーザあたり1秒に1回リクエストを投げるようにして、ユーザ数＝秒間リクエスト数となるように調整したい)
 ```python
 wait_time constant(1)
 ```
#### １つのシナリオで複数のホストへ負荷をかけたい
`http://` などとして、個別にフルパスを記載すればOK

```python
    l.client.post("http://example.com/add_item", {"item_name":"あああ"})
```

#### 実行順は保証されなくていいから特定のリクエストだけ多く叩いてほしい
`@task(x)` の形式で記載する。
xは重み。数値が大きいほど実行頻度が高くなる

```Python
import task
...

    @task(2)
    def initialize(self):
        initialize(self)

    @task(1)
    def item_list(self):
        item_listz(self)
    
    @task(3)
    def add_item(self):
        add_item(self)
```

#### WebUIなんて使えねえ
pythonがインストールされている環境で以下を実行

```bash
locust -f my_locustfile.py --master
```

※docker-composeであれば、以下条件commandに記載すればよいはず。。(試していない)

- alpineなどの別のimageにpython/locustをインストール
- 今回とは別形式のdocker-compose.yamlを作成し、commandに上記コマンドを記載

また別の記事として書こうと思います。

参考。日本語訳ないのね。
https://docs.locust.io/en/stable/running-locust-distributed.html