Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle task weights #2741

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Prev Previous commit
Next Next commit
refactor: remove DefaultTaskSet class, User now stores a TaskSet
TaskSet has a boolean field noting if it is being used as DefaultTaskSet or not
Check of empty tasks attribute is performed at __init__ and not on every call to gen_next_task
  • Loading branch information
bakhtos committed May 27, 2024
commit 6889806630ed46ef720a261b57be9b001360bf05
70 changes: 28 additions & 42 deletions locust/user/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,10 +278,21 @@ def __init__(self, parent: User) -> None:

if isinstance(parent, TaskSet):
self._user = parent.user
self._isdefaulttaskset = False
else:
self._user = parent
self._isdefaulttaskset = True

self._parent = parent
s = self.user if isinstance(self._parent, TaskSet) else self
if not s.tasks:
if getattr(s, "task", None):
extra_message = ", but you have set a 'task' attribute - maybe you meant to set 'tasks'?"
else:
extra_message = "."
raise Exception(
f"No tasks defined on {s.__class__.__name__}{extra_message} Use the @task decorator or set the 'tasks' attribute"
)

# if this class doesn't have a min_wait, max_wait or wait_function defined, copy it from Locust
if not self.min_wait:
Expand Down Expand Up @@ -369,16 +380,23 @@ def execute_next_task(self):
self.execute_task(self._task_queue.popleft())

def execute_task(self, task):
# check if the function is a method bound to the current locust, and if so, don't pass self as first argument
if hasattr(task, "__self__") and task.__self__ == self:
# task is a bound method on self
task()
elif hasattr(task, "tasks") and issubclass(task, TaskSet):
# Check if this is a nested TaskSet or User's internal
if not self._isdefaulttaskset: # Nested case
s = self
else: # We are in User
# check if the function is a method bound to the current locust, and if so, don't pass self as first argument
if hasattr(task, "__self__") and task.__self__ == self:
# task is a bound method on self
task()
return
s = self.user

if hasattr(task, "tasks") and issubclass(task, TaskSet):
# task is another (nested) TaskSet class
task(self).run()
task(s).run()
else:
# task is a function
task(self)
task(s)

def schedule_task(self, task_callable, first=False):
"""
Expand All @@ -393,15 +411,9 @@ def schedule_task(self, task_callable, first=False):
self._task_queue.append(task_callable)

def get_next_task(self):
if not self.tasks:
if getattr(self, "task", None):
extra_message = ", but you have set a 'task' attribute - maybe you meant to set 'tasks'?"
else:
extra_message = "."
raise Exception(
f"No tasks defined on {self.__class__.__name__}{extra_message} use the @task decorator or set the 'tasks' attribute of the TaskSet"
)
return random.choices(self.task_list, cum_weights=self.task_cum_weights)
return random.choices(self.user.task_list, cum_weights=self.user.task_cum_weights) if self._isdefaulttaskset\
else random.choices(self.task_list, cum_weights=self.task_cum_weights)


def wait_time(self):
"""
Expand Down Expand Up @@ -458,29 +470,3 @@ def client(self):
Shortcut to the client :py:attr:`client <locust.User.client>` attribute of this TaskSet's :py:class:`User <locust.User>`
"""
return self.user.client


class DefaultTaskSet(TaskSet):
"""
Default root TaskSet that executes tasks in User.tasks.
It executes tasks declared directly on the Locust with the user instance as the task argument.
"""

def get_next_task(self):
if not self.user.tasks:
if getattr(self.user, "task", None):
extra_message = ", but you have set a 'task' attribute on your class - maybe you meant to set 'tasks'?"
else:
extra_message = "."
raise Exception(
f"No tasks defined on {self.user.__class__.__name__}{extra_message} Use the @task decorator or set the 'tasks' attribute of the User (or mark it as abstract = True if you only intend to subclass it)"
)
return random.choices(self.user.task_list, cum_weights=self.user.task_cum_weights)

def execute_task(self, task):
if hasattr(task, "tasks") and issubclass(task, TaskSet):
# task is (nested) TaskSet class
task(self.user).run()
else:
# task is a function
task(self.user)
3 changes: 1 addition & 2 deletions locust/user/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
LOCUST_STATE_RUNNING,
LOCUST_STATE_STOPPING,
LOCUST_STATE_WAITING,
DefaultTaskSet,
TaskSet,
get_tasks_from_base_classes,
)
Expand Down Expand Up @@ -145,7 +144,7 @@ def on_stop(self):
@final
def run(self):
self._state = LOCUST_STATE_RUNNING
self._taskset_instance = DefaultTaskSet(self)
self._taskset_instance = TaskSet(self)
try:
# run the TaskSet on_start method, if it has one
try:
Expand Down