はじめに
Locust の Document は分かりやすいですが、
色々気になった点があったので ソースコード を読んで図にしてみました。
Locust の動き
補足
Locust を支えるもの
Locust は Gevent という並行処理をサポートしてくれるモジュールを使用している。
何個もの Locust は実際には単一の greenlet の中でスケジューリングされながら動いており、イベントベースで制御されている。
Task 間をまたぐ振る舞いの制御はどうするのか?
SQLite や Redis を使って管理することになるかと。
Summary に独自情報を追加したい
ソースコードを追っていくと、locust/stats.py#L553 の global_stats
に流し込めばいいことがわかる。
global_stats.log_request('foo', 'bar', 0, 0)
ロックを取る
gevent の lock を使う。
例えば Locust の setup が、一度だけかつ全インスタンスの on_start の前に呼ばれるのも、これを使って実現されている。 => locust/core.py#L136
import gevent
_lock = gevent.lock.Semaphore()
def do_samething():
_lock.acquire()
# なんか処理
_lock.release()
Taskset を nest するとどうなるか?
stackoverflow: taskset-class-vs-function-task。
ソースコードを見れば違いは明らかですが、Document にも記述が確認できます。
Task を動的に決めたい
schedule_task を呼べばいい。
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匹。
from locust import runners
runners.locust_runner.num_clients
test は locust テスト と python 単体テストの2個書くのか?
この Issue が参考になります
Feature request: "run through" each test once
おわりに
master-slaveによる分散型負荷試験 や seq_task による task の繋がり表現、 WebUI などなどについてはドキュメント参照。
備考
図のソース。超雑。UMLとかいうの初めて書いた。
@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