図解 Locust


はじめに

LocustDocument は分かりやすいですが、
色々気になった点があったので ソースコード を読んで図にしてみました。

Locust の動き

補足

Locust を支えるもの

Locust は Gevent という並行処理をサポートしてくれるモジュールを使用している。
何個もの Locust は実際には単一の greenlet の中でスケジューリングされながら動いており、イベントベースで制御されている。

Task 間をまたぐ振る舞いの制御はどうするのか?

SQLite や Redis を使って管理することになるかと。

Summary に独自情報を追加したい

ソースコードを追っていくと、locust/stats.py#L553global_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

参考: PlantUML Reference