図解 Locust 

<img src="https://qiita-image-store.s3.amazonaws.com/0/159680/e3f50a73-cf47-ff14-b3a7-77b3a2c99053.png" width="200">

## はじめに
[Locust](https://locust.io) の [Document](https://docs.locust.io/en/stable/api.html) は分かりやすいですが、
色々気になった点があったので **[ソースコード](https://github.com/locustio/locust/tree/master/locust)** を読んで図にしてみました。

## Locust の動き
![【 Locust の動き 】.png](https://qiita-image-store.s3.amazonaws.com/0/159680/6d2e8d36-9da1-e029-de14-d3f815d777c2.png)

## 補足 
### Locust を支えるもの
Locust は [Gevent](http://methane.github.io/gevent-tutorial-ja/) という並行処理をサポートしてくれるモジュールを使用している。
何個もの Locust は実際には単一の greenlet の中でスケジューリングされながら動いており、イベントベースで制御されている。

### Task 間をまたぐ振る舞いの制御はどうするのか？
SQLite や Redis を使って管理することになるかと。

### Summary に独自情報を追加したい
ソースコードを追っていくと、[locust/stats.py#L553](https://github.com/locustio/locust/blob/f0a5f893734faeddb83860b2985010facc910d7d/locust/stats.py#L553) の `global_stats` に流し込めばいいことがわかる。

```python
global_stats.log_request('foo', 'bar', 0, 0)
``` 

### ロックを取る
gevent の lock を使う。
例えば Locust の setup が、一度だけかつ全インスタンスの on_start の前に呼ばれるのも、これを使って実現されている。 => [locust/core.py#L136](https://github.com/locustio/locust/blob/master/locust/core.py#L136)

```python
import gevent

_lock = gevent.lock.Semaphore()

def do_samething():
 _lock.acquire()
 # なんか処理
 _lock.release()
```

### Taskset を nest するとどうなるか？
[stackoverflow: taskset-class-vs-function-task](https://stackoverflow.com/questions/46022068/locust-taskset-class-vs-function-task)。
ソースコードを見れば違いは明らかですが、[Document にも記述が確認できます](https://docs.locust.io/en/stable/api.html#locust.core.TaskSet)。

### Task を動的に決めたい
[schedule_task](https://docs.locust.io/en/stable/api.html#locust.core.TaskSet.schedule_task) を呼べばいい。

```python
class SampleTaskset(TaskSet):
    def on_start(self):
        if self.client.is_new:
            self.schedule_task(hoge, first=True)
        else:
            self.schedule_task(fuga, first=True)

   @task
   def hoge:
       print("hoge")

   @task
   def fuga:
       print("fuga")
```

### client 数の取得
`locust -c 6` なら 6匹。
 
```python
from locust import runners

runners.locust_runner.num_clients
```

### test は locust テスト と python 単体テストの2個書くのか？
この Issue が参考になります
[Feature request: "run through" each test once](https://github.com/locustio/locust/issues/98)

## おわりに
master-slaveによる分散型負荷試験 や seq_task による task の繋がり表現、 WebUI などなどについてはドキュメント参照。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/159680/ff4d4743-5e77-a153-9b2f-5077d160223c.png)


## 備考
図のソース。超雑。UMLとかいうの初めて書いた。

```text
@startuml
skinparam NoteFontSize 16
skinparam TitleFontSize 24
skinparam ArrowThickness 3
title 【 Locust の動き 】

rectangle bucket #LightGreen {
   agent Locust_A as Locust_A_1
   agent Locust_A as Locust_A_2
   agent Locust_A as Locust_A_3
   agent Locust_B as Locust_B_1
   agent Locust_B as Locust_B_2
   agent Locust_C as Locust_C_1
}
note right of bucket
Locust_A,B,C の weight が 3:2:1 で locust -c 6 した時の例。
bucket は locust_classes を重み付けしたものの名称。
end note

skinparam InterfaceFontSize 20
skinparam InterfaceFontColor #Red
interface spawn_locusts
bucket ---down-> spawn_locusts
note right of spawn_locusts
locust をたくさん spawn (発生)させる。
hatch_rate に基づき、locust を random で選んで生んでいく。
end note

agent Locust_A as Locust_A_3_spawned
agent Locust_C as Locust_C_1_spawned
agent Locust_B as Locust_B_2_spawned
agent Locust_A as Locust_A_1_spawned
agent Locust_A as Locust_A_2_spawned
agent Locust_B as Locust_B_1_spawned

spawn_locusts -[#green]down-> Locust_A_3_spawned: 1/hatch_rate 秒後。\nLocust の setup()
spawn_locusts --[#green]down-> Locust_C_1_spawned: 2/hatch_rate 秒後。\nLocust の setup()
spawn_locusts ---[#green]down-> Locust_B_2_spawned: 3/hatch_rate 秒後。\nLocust の setup()
spawn_locusts ----[#green]down-> Locust_A_1_spawned: 4/hatch_rate 秒後。
spawn_locusts -----[#green]down-> Locust_A_2_spawned: 5/hatch_rate 秒後。
spawn_locusts ------[#green]down-> Locust_B_1_spawned
note on link
☆☆☆
setup(), teardown() は、クラスにつき一度だけ実行される。
この2つのメソッドが並列に実行されることはない。重要
これは上手く図示できてないけど...
☆☆☆
end note

agent Locust_A_TaskSet as Locust_A_1_TaskSet
agent Locust_A_TaskSet as Locust_A_2_TaskSet
agent Locust_A_TaskSet as Locust_A_3_TaskSet
agent Locust_B_TaskSet as Locust_B_1_TaskSet
agent Locust_B_TaskSet as Locust_B_2_TaskSet
agent Locust_C_TaskSet as Locust_C_1_TaskSet
Locust_A_1_spawned -down-> Locust_A_1_TaskSet
Locust_A_2_spawned -down-> Locust_A_2_TaskSet
Locust_A_3_spawned -down-> Locust_A_3_TaskSet: TaskSet の setup()
Locust_B_1_spawned -down-> Locust_B_1_TaskSet
Locust_B_2_spawned -down-> Locust_B_2_TaskSet: TaskSet の setup()
Locust_C_1_spawned -down-> Locust_C_1_TaskSet: TaskSet の setup()

usecase on_start as on_start_Locust_A_1_TaskSet
usecase on_stop as on_stop_Locust_A_1_TaskSet
rectangle loop as task_A_1 {
   control tasks as task_A_1_control
}

usecase on_start as on_start_Locust_A_2_TaskSet
usecase on_stop as on_stop_Locust_A_2_TaskSet
rectangle loop as task_A_2 {
   control tasks as task_A_2_control
}

usecase on_start as on_start_Locust_A_3_TaskSet
usecase on_stop as on_stop_Locust_A_3_TaskSet
rectangle loop as task_A_3 {
   control tasks as task_A_3_control
}

usecase on_start as on_start_Locust_B_1_TaskSet
note right: 「さて、攻撃するぞ〜」(今回だと6並列の攻撃が展開されている)
usecase on_stop as on_stop_Locust_B_1_TaskSet
note right: 「疲れはてたよ...」
rectangle loop as task_B_1 {
   control tasks as task_B_1_control
}

usecase on_start as on_start_Locust_B_2_TaskSet
usecase on_stop as on_stop_Locust_B_2_TaskSet
rectangle loop as task_B_2 {
   control tasks as task_B_2_control
}

usecase on_start as on_start_Locust_C_1_TaskSet
usecase on_stop as on_stop_Locust_C_1_TaskSet
rectangle loop as task_C_1 {
   control tasks as task_C_1_control
}

Locust_A_1_TaskSet -down-> on_start_Locust_A_1_TaskSet
on_start_Locust_A_1_TaskSet -down-> task_A_1
task_A_1 -down-> on_stop_Locust_A_1_TaskSet

Locust_A_2_TaskSet -down-> on_start_Locust_A_2_TaskSet
on_start_Locust_A_2_TaskSet -down-> task_A_2
task_A_2 -down-> on_stop_Locust_A_2_TaskSet

Locust_A_3_TaskSet -down-> on_start_Locust_A_3_TaskSet
on_start_Locust_A_3_TaskSet -down-> task_A_3
task_A_3 -down-> on_stop_Locust_A_3_TaskSet

Locust_B_1_TaskSet -down-> on_start_Locust_B_1_TaskSet
on_start_Locust_B_1_TaskSet -down-> task_B_1
task_B_1 -down-> on_stop_Locust_B_1_TaskSet
note left of task_B_1
run-time 指定がなければ無限ループ。
Exception が起きても loop は継続する。
内部的な intterupt が来たら reschedule されて継続する。

request の結果を得たい時は locust の用意している hooks を使う。
request_success.fire() みたいに。
こうしないと task を実行しているスレッドを待機させてしまい、
次の task に移らないのでダメ。
end note

Locust_B_2_TaskSet -down-> on_start_Locust_B_2_TaskSet
on_start_Locust_B_2_TaskSet -down-> task_B_2
task_B_2 -down-> on_stop_Locust_B_2_TaskSet

Locust_C_1_TaskSet -down-> on_start_Locust_C_1_TaskSet
on_start_Locust_C_1_TaskSet -down-> task_C_1
task_C_1 -down-> on_stop_Locust_C_1_TaskSet

agent All_locusts_dead
on_stop_Locust_A_3_TaskSet ---------down-> All_locusts_dead: TaskSet, Locust の teardown()
on_stop_Locust_C_1_TaskSet --------down-> All_locusts_dead: TaskSet, Locust の teardown()
on_stop_Locust_B_2_TaskSet -------down-> All_locusts_dead: TaskSet, Locust の teardown()
on_stop_Locust_A_1_TaskSet ------down-> All_locusts_dead
on_stop_Locust_A_2_TaskSet -----down-> All_locusts_dead
on_stop_Locust_B_1_TaskSet ----down-> All_locusts_dead
@enduml
```

参考: [PlantUML Reference](http://s.plantuml.com/PlantUML_Language_Reference_Guide_JA.pdf)