Назначьте наборы задач саранчи программно

#python #performance-testing #locust

Вопрос:

Я пытаюсь сделать что-то, где я назначаю задачи саранчи динамически на основе значения хоста, передаваемого через пользовательский интерфейс. В этом примере, если хост передается как «привет», тест должен запустить задачу «Привет», иначе он должен запустить задачу «Мир».

 from locust import HttpUser, TaskSet, task, events

class RandomTask1(TaskSet):
  @task(1)
  def soemthing(self):
    print("Hello!")

class RandomTask2(TaskSet):
  @task(1)
  def soemthing(self):
    print("World!")

class LoadTestUser(HttpUser):
  def on_start(self):
    host_config = self.host
    if host_config == "hello":
      tasks = {RandomTask1:1}
    else:
      tasks = {RandomTask2:1}
 

Приведенный ниже пример не работает, и я получаю следующую ошибку

 Exception: No tasks defined on LoadTestUser. use the @task decorator or set the tasks property of the User (or mark it as abstract = True if you only intend to subclass it)
 

Есть идеи, как я могу добиться чего-то подобного? Я упростил это для примера, но для всех целей и задач давайте предположим, что экземпляр locust уже запущен и его нельзя остановить или перезапустить, а задачи необходимо назначать динамически.введите описание изображения здесь

Редактировать:

Пытался сделать это:

 class LoadTestUser(HttpUser):
    def on_start(self):
        if self.host == "hello":
            self.tasks = {HelloTask: 1}
        else:
            self.tasks = {WorldTask: 1}

    @task
    def nothing(self):
        pass

class HelloTask(TaskSet):
    @task
    def something(self):
        print("Hello")

class WorldTask(TaskSet):
    @task
    def something(self):
        print("World")
 

Теперь я вижу следующую ошибку:

 Traceback (most recent call last):
  File "/Users/user/project/venv/lib/python3.8/site-packages/locust/user/task.py", line 285, in run
    self.schedule_task(self.get_next_task())
  File "/Users/user/project/venv/lib/python3.8/site-packages/locust/user/task.py", line 420, in get_next_task
    return random.choice(self.user.tasks)
  File "/Users/user/opt/anaconda3/lib/python3.8/random.py", line 291, in choice
    return seq[i]
KeyError: 0
 

Ответ №1:

Создайте одну задачу и поместите в нее логику для того, что вы хотите, чтобы она выполнялась.

 class LoadTestUser(HttpUser):

    def something1(self):
        print("Hello!")

    def something2(self):
        print("World!")

    @task
    def task_logic(self):
        if self.host == "hello":
            self.something1()
        else:
            self.something2()
 

Однако вы можете просто устранить ошибку, которую вы получаете напрямую. Вам необходимо определить задачу в классе, даже если вы собираетесь перезаписать или изменить задачи с помощью своих наборов задач. В документации есть пример, но просто добавьте задачу, pass чтобы она ничего не делала, тогда ваши переопределения должны сработать.

 class LoadTestUser(HttpUser):

    def on_start(self):
        host_config = self.host
        if host_config == "hello":
            self.tasks = {RandomTask1:1}
        else:
            self.tasks = {RandomTask2:1}

    @task
    def nothing(self):
        pass
 

ИЗМЕНИТЬ:
Это должно сработать, но похоже, что в текущей версии Locust может быть ошибка, из-за которой она принимает словарь только для задач при первом запуске Locust, а затем принимает список только после этого. Пока это не исправлено, пример в другом ответе работает.

Комментарии:

1. С этим будет трудно справиться, потому что у меня много задач и много хостов, с которыми нужно разобраться. У каждого типа хоста должен быть свой собственный набор задач, и я хочу динамически загружать этот набор задач.

2. Без дополнительной информации я не думаю, что пойму. Это работает, даже если у вас много задач и хостов, с которыми нужно работать. Основываясь на приведенном вами примере, это, по сути, то же самое, но более простое, и вам не нужно беспокоиться о сложности. Но может быть и другой способ. Я могу отредактировать свой ответ, чтобы добавить еще один вариант.

3. Вы правы, что это будет работать со многими задачами и хостами, но что вы предлагаете, если я хочу, чтобы вес задач был разным для разных хостов?

4. Для взвешивания можно использовать свою собственную логику. Как вы это сделаете, будет зависеть от того, чего именно вы хотите, но часто это не так сложно, как кажется на первый взгляд. Обычно это просто еще одно «если» с выбором случайного числа. Если вам нужно соотношение 10:1 между 2 заданиями, если случайное значение от 0 до 10 равно 0, выполните задание 1, в противном случае выполните задание 10.

5. Я попробовал то, что вы предлагаете во втором фрагменте кода, и похоже, что переопределения не происходит. Я думаю, что набор задач должен быть назначен при загрузке класса, а не тогда, когда бегун является стартером. Кроме того, для первого фрагмента рассмотрим следующий сценарий: если у меня есть 2 задачи: задача 1 и задача 2. Для хоста 1 я хочу вес 1:1, а если это хост2, я хочу, чтобы вес задачи составлял 1:5. Как бы я это сделал?

Ответ №2:

 import sys
from locust import HttpUser, TaskSet, task, events

class LoadTestUser(HttpUser):
    def locust_class(self, name):
        module = sys.modules[__name__]
        return getattr(module, f"{name.capitalize()}Task")

    def get_weighted_tasks(self, task_list):
        new_tasks = []
        for item in task_list:
            if "locust_task_weight" in dir(item):
                for i in range(item.locust_task_weight):
                    new_tasks.append(item)
        return new_tasks

    def get_locust_tasks(self, cls):
        tasks = []
        for maybe_task in cls.__dict__.values():
            if hasattr(maybe_task, "locust_task_weight"):
                tasks.append(maybe_task)
        return tasks

    def on_start(self):
        task_cls = self.locust_class(self.host)
        task_list = self.get_locust_tasks(task_cls)
        self.tasks = self.get_weighted_tasks(task_list)

    @task(1)
    def nothing(self):
        pass


class HelloTask(TaskSet):
    @task(1)
    def something(self):
        print("Hello")

    @task(100)
    def something_else(self):
        print("hello")


class WorldTask(TaskSet):
    @task(1)
    def something(self):
        print("World")

    @task(10)
    def something_else(self):
        print("world")