locustio / locust

Write scalable load tests in plain Python 🚗💨
https://locust.cloud
MIT License
25.06k stars 3k forks source link

Number of Users Dependent on Number of slaves ? #724

Closed abhimanyuPlivo closed 5 years ago

abhimanyuPlivo commented 6 years ago

Description of issue / feature request

I have 16 Slaves in my setup and I have created 30 different users (all with equal weight). When I try to hatch 30 Users I would expect 30 Users one of each kind (since the weights are equal). Instead, no users are hatched (all slaves are given 0 users to hatch as seen in logs). The minimum load am able to run is with 480 users (which is 16 * 30 ).

Environment settings (for bug reports)

ckiickA commented 6 years ago

I have run into this same issue. It's not just the number of users per slave, it's also depends on the number of classes and their weights.

            # create locusts depending on weight
            percent = locust.weight / float(weight_sum)
            num_locusts = int(round(amount * percent))
            bucket.extend([locust for x in xrange(0, num_locusts)]

That's from weight_locusts() in runners.py. Consider the case where amount=2, and you have 5 equally weighted classes. In that case num_locusts would always be 0, and you would never spawn any users. Worse, since it's deterministic, all of the slaves would act the same way, and nothing gets spawned across the whole set of instances.

This code needs to be re-written so that a) it uses integer math instead of rounding off floats, and b) it is randomized (the remainders at least) per the documentation.

Even when the amount of users is larger, you still lose some to rounding with the current code.

ckiickA commented 6 years ago

Not completely randomized, but one possible solution:

diff --git a/locust/runners.py b/locust/runners.py
index f397cd5..080dca2 100644
--- a/locust/runners.py
+++ b/locust/runners.py
@@ -68,6 +68,8 @@ class LocustRunner(object):
         """
         bucket = []
         weight_sum = sum((locust.weight for locust in self.locust_classes if locust.task_set))
+        threshold = random.random()
+        overage = 0.0
         for locust in self.locust_classes:
             if not locust.task_set:
                 warnings.warn("Notice: Found Locust class (%s) got no task_set. Skipping..." % locust.__name__)
@@ -79,8 +81,13 @@ class LocustRunner(object):
                 locust.stop_timeout = stop_timeout

             # create locusts depending on weight
-            percent = locust.weight / float(weight_sum)
-            num_locusts = int(round(amount * percent))
+            ratio = locust.weight / float(weight_sum)
+            raw = amount * ratio + overage
+            num_locusts = int(raw)
+            overage = raw - num_locusts
+            if overage > threshold:
+                num_locusts += 1
+                overage = overage - 1.0
             bucket.extend([locust for x in xrange(0, num_locusts)])
         return bucket
tortila commented 6 years ago

I also ran into this issue. The main reason behind this is the logic of distributing users between defined Locust classes and slaves in distributed mode:

This means that in the original example:

Applying randomisation suggested by @ckiickA would partially solve this issue, because in both cases the remaining users are assigned to some locust class anyway. If we however consider a case where defined Locust classes have different weights, this solution won't achieve the weight distribution as expected.

A more general solution to this would be to change the order of distributing users so that:

This would mean that in the original example:

The only disadvantage of this approach is that the API between master and slave would need to change, because it now needs to contain a list of Locust classes (which have to be serialized and deserialized). @heyman @cgoldberg @cgbystrom let me know what you think and if you'd be interested in a pull request for this (I have a proof of concept that works).

HieronymusLex commented 5 years ago

sounds like a valid issue, but one thing I'd say is that in most scenarios, it seems like having 16 slaves for 30 users (for example) is pretty inefficient. why not just run in standalone mode? or use less slaves?

But the issue could still arise if the work a user is doing is resource intensive and for whatever reason vertical scaling of slaves isn't possible (plus it does sound like the distribution logic isn't quite right)

heyman commented 5 years ago

With #1113 merged, one should no longer end up with 0 users hatched. However there's still an issue that one could end up with a distribution of Locust classes that doesn't correspond to the weight attributes, when simulating a number of users that are less than number of locust classes * number of slaves.

A note about this has been added to the documentation in d6d87b4b3cb29b9cec1190bed7586f8b87bb492b.

cyberw commented 5 years ago

partially fixed, the rest is documented as wontfix, I think :)

heyman commented 5 years ago

@tortila:

let me know what you think and if you'd be interested in a pull request for this (I have a proof of concept that works).

I think your suggested changes would make sense, and I'd be happy to review a PR!