locustio / locust

Write scalable load tests in plain Python 🚗💨
MIT License
24.64k stars 2.96k forks source link

Wrong tasks weight calculation over several TaskSet's #766

Closed mkusz closed 6 years ago

mkusz commented 6 years ago

Description of issue / feature request

Defining weight of tasks spread across more than one TaskSet lead to a wrong probability of task execution. Take a look at the following code:

from locust.core import HttpLocust, task, TaskSet

class LightSet(TaskSet):
    @task(1)
    def light_1(self):
        print('Light 1')

class HeavySet(TaskSet):
    @task(100)
    def heavy_1(self):
        print('Heavy 1')

class Light(HttpLocust):
    task_set = LightSet
    weight = 1
    host = 'localhost'

class Heavy(HttpLocust):
    task_set = HeavySet
    weight = 1
    host = 'localhost'

Actual and expected behavior

Using above code, my expectation is to execute heavy_1 over 100 times more likely than light_1. It's not happening. Instead, both task probability of execution is 1:1. When both tasks are in the same TaskSet it works as expected. When we add dummy task to Heavy like this:

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

Everything started to work correctly. It seems that weight calculation is always against task with lowes weight not one common value for all TaskSet's.

Environment settings (for bug reports)

Steps to reproduce (for bug reports)

Described above.

aldenpeterson-wf commented 6 years ago

How are you running locust?

If you specify multiple Locusts each is controlled independently so you'd see the expected behavior you are referring to because the timing would be unique per locust.

I think what you want is something like:

class LightAndHeavy(HttpLocust):
    task_set = [LightSet , HeavySet]
    weight = 1
    host = 'localhost'

which will combine the task sets into a single locust.

mkusz commented 6 years ago

@aldenpeterson-wf It won't work. First of all, we get: TypeError: 'list' object is not callable Reason for that is how task set is started (take a look at run() method in Locust object that is a parent for HttpLocust). Second thing is that sometimes we need to split tasks over various hosts that single system is build of (for example, an authentication server is separated from rest of the API). Using your approach I will be forced to switch host inside task set and I don't think is the way we should do it. The third thing is running tasks in distributed Locust configuration in which this kind of weight calculation will lead to wrong task execution probability. Mostly because of the third point, I think that all weights should be calculated against one common value not the task with the lowest weight.

aldenpeterson-wf commented 6 years ago

@aldenpeterson-wf It won't work

Oops. Needs to be:

class LightAndHeavy(HttpLocust):
    class AllTasks(TaskSet):
        task_set = [LightSet , HeavySet]
    task_set = AllTasks

Mostly because of the third point, I think that all weights should be calculated against one common value not the task with the lowest weight.

This isn't really the idea behind locust though. The paradigm Locust is designed with is to simulate users that have task sets associated with the users. What you are requesting is more of a "lots of users that all share a task set."

You could pretty easily accomplish what you are looking to do with a custom client, too.

mkusz commented 6 years ago

@aldenpeterson-wf Thanks for this code snippet (inside AllTasks task_set should be tasks). It's almost what I need. One thing that is not fixed by this tasks nesting is a problem with testing a system that is spread over various hosts. It can be achieved by switching client base_url but I don't think is a way it should be done. My idea was to implement TaskSet per host and run them all at the same time, but it will not work as intended because of the weight problem I have reported.

aldenpeterson-wf commented 6 years ago

@mkusz I'm assuming this is resolved - let me know if you need me to reopen this.