Skip to content

Commit

Permalink
Add doc strings
Browse files Browse the repository at this point in the history
  • Loading branch information
mboutet committed Nov 17, 2020
1 parent bc18b41 commit fc82425
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 7 deletions.
33 changes: 30 additions & 3 deletions locust/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,31 @@ def dispatch_users(
user_class_occurrences: Dict[str, int],
spawn_rate: float,
) -> Generator[Dict[str, Dict[str, int]], None, None]:
"""
Generator function that dispatches the users
in `user_class_occurrences` to the workers.
The currently running users is also taken into
account.
It waits an appropriate amount of time between each iteration
in order for the spawn rate to be respected, whether running in
local or distributed mode.
The spawn rate is only applicable when additional users are needed.
Hence, if `user_class_occurrences` contains less users than there are
currently running, this function won't wait and will only run for
one iteration. The logic for not stopping users at a rate of `spawn_rate`
is that stopping them is a blocking operation, especially when
having a `stop_timeout` and users with tasks running for a few seconds or
more. If we were to dispatch multiple spawn messages to have a ramp down,
we'd run into problems where the previous spawning would be killed
by the new message. See the call to `self.spawning_greenlet.kill()` in
`:py:meth:`locust.runners.LocalRunner.start` and `:py:meth:`locust.runners.WorkerRunner.worker`.
:param worker_nodes: List of worker nodes
:param user_class_occurrences: Desired number of users for each class
:param spawn_rate: The spawn rate
"""
initial_dispatched_users = {
worker_node.id: {
user_class: worker_node.user_class_occurrences.get(user_class, 0)
Expand Down Expand Up @@ -119,10 +144,8 @@ def dispatch_users(
gevent.sleep(max(0.0, wait_between_dispatch - delta))

# If we are here, it means we have an excess of users for one or more user classes.
# Hence, we need to dispatch a last set of users that will bring the desired users
# Hence, we need to dispatch a last set of users that will bring the users
# distribution to the desired one.
# TODO: Explain why we don't stop the users at "spawn_rate"
# and why we stop the excess users once at the end.
yield balanced_users


Expand Down Expand Up @@ -188,6 +211,10 @@ def balance_users_among_workers(
worker_nodes, # type: List[WorkerNode]
user_class_occurrences: Dict[str, int],
) -> Dict[str, Dict[str, int]]:
"""
Balance the users among the workers so that
each worker gets around the same number of users of each user class
"""
balanced_users = {
worker_node.id: {user_class: 0 for user_class in sorted(user_class_occurrences.keys())}
for worker_node in worker_nodes
Expand Down
11 changes: 9 additions & 2 deletions locust/distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,18 @@ def weight_users(
number_of_users: int,
) -> Dict[str, int]:
"""
Compute users to spawn
Compute the desired state of users using the weight of each user class.
If `number_of_users` is less than `len(user_classes)`, at most one user of each user class
is chosen. User classes with higher weight are chosen first.
If `number_of_users` is greater than or equal to `len(user_classes)`, at least one user of each
user class will be chosen. The greater `number_of_users` is, the better the actual distribution
of users will match the desired one (as dictated by the weight attributes).
:param user_classes: the list of user class
:param number_of_users: total number of users
:return: the new set of users to run
:return: the set of users to run
"""
assert number_of_users >= 0

Expand Down
4 changes: 2 additions & 2 deletions locust/test/test_runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ def my_task(self):
runner.spawning_greenlet.join()
delta = time.time() - ts
self.assertTrue(
0 <= delta <= 0.01, "Expected user count to increase to 10 instantaneously, instead it took %f" % delta
0 <= delta <= 0.05, "Expected user count to increase to 10 instantaneously, instead it took %f" % delta
)
self.assertTrue(
runner.user_count == 10, "User count has not decreased correctly to 2, it is : %i" % runner.user_count
Expand All @@ -398,7 +398,7 @@ def my_task(self):
runner.spawning_greenlet.join()
delta = time.time() - ts
self.assertTrue(
0 <= delta <= 0.01, "Expected user count to decrease to 2 instantaneously, instead it took %f" % delta
0 <= delta <= 0.05, "Expected user count to decrease to 2 instantaneously, instead it took %f" % delta
)
self.assertTrue(
runner.user_count == 2, "User count has not decreased correctly to 2, it is : %i" % runner.user_count
Expand Down

0 comments on commit fc82425

Please sign in to comment.