Closed abhimanyuPlivo closed 5 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.
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
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:
MasterLocustRunner
divides the number of users evenly between all slaves:
https://github.com/locustio/locust/blob/ba01b0be5dff6cdad7a133a14d490b8d2010868d/locust/runners.py#L278SlaveLocustRunner
) assigns their portion of users to available Locust classes, according to their weights. It uses an imperfect rounding mechanism:
https://github.com/locustio/locust/blob/ba01b0be5dff6cdad7a133a14d490b8d2010868d/locust/runners.py#L82-L84
which, as pointed out by @ckiickA, doesn't work well for small number of users (amount
) compared to number of Locust classes (actually to the sum of their weights). This snippet above contains the answer, why distributing users depends not only on the number of slaves, as pointed out in the original issue by @abhimanyuPlivo, but also on the sum of weights of all Locust classes. This means that in the original example:
num_locusts = int(round(1 * 1.0/30))
and num_locusts = int(round(2 * 1.0/30))
num_locusts
is 0, no locusts are being hatched. 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:
MasterLocustRunner
would create locusts according to the weights of defined Locust classes and the number of users (basically the current logic of SlaveLocustRunner
), and then distribute the generated locusts between all available slaves. For this, data sent between master and slave would have to contain this information (additional field needed),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).
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)
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.
partially fixed, the rest is documented as wontfix, I think :)
@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!
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)