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

Possibility to set the exact number of users to spawn (instead weight) #1939

Closed
EzR1d3r opened this issue Nov 17, 2021 · 4 comments
Closed

Comments

@EzR1d3r
Copy link
Contributor

EzR1d3r commented Nov 17, 2021

Is your feature request related to a problem? Please describe.

I needed a special query that would be executed once every 30 seconds, regardless of the total number of users created. The only solution I came up with is to create one instance of a user with such a request. To do this, it was necessary to count his weight according to the formula

@events.test_start.add_listener
def on_test_start(environment: Environment, **kwargs) -> None:
    SingleUserUser.weight = 1
    CommonUser.weight = environment.runner.target_user_count - 1

This is not very convenient not very obvious.

Describe the solution you'd like

Just a field (like weight) "count" or "fix_count", which describes the exactly count of user instances. I guess weight parameter in that case have to be ignored.

Describe alternatives you've considered

Additional context

If you find it a useful figure I can try to do

@marcinh
Copy link
Contributor

marcinh commented Nov 19, 2021

Hi

I was thinking about the same feature - I talked a little bit with @mboutet about it and discussed how to implement it and we came to the following design:

class User(object, metaclass=UserMeta):
    ...
    fixed_user_count= None
    """Static number indicating how many user class statically spawn"""

and then modify algorithm of spawning in dispacher with following rules:

  • if total number of users is lower than fixed total - we will spawn only lower number of fixed users with warning (If there are more than one user classes with this attribute I consider to spawn them according to the ratio)
  • fixed users are always ramped up first - the remaining difference would be divided by weights as so far.
  • if workers are missing - redistribution is required
def _user_gen(self) -> Generator[str, None, None]:
        # Create fixed number users based on their ratio
        total_fixed_users = sum(u.fixed_user_count for u in self._user_classes if u.fixed_user_count)
        normalized_weights = [
            (user_class.__name__, round(user_class.fixed_user_count / total_fixed_users))
            for user_class in self._user_classes if user_class.fixed_user_count
        ]

        gen = smooth(normalized_weights)
        user_classes_count = defaultdict(lambda: 0)
        desired_fixed_user_classes_count = {u.__name__: u.fixed_user_count for u in self._user_classes if u.fixed_user_count}
        while any(user_classes_count[u.__name__] < u.fixed_user_count for u in self._user_classes if u.fixed_user_count):
            user_class_name = gen()
            if user_classes_count[user_class_name] == desired_fixed_user_classes_count[user_class_name]:
                continue
            user_classes_count[user_class_name] += 1
            yield user_class_name

        # All users with fixed user count have been created, now we create the weighted users.
        # Normalize the weights so that the smallest weight will be equal to "target_min_weight".
        # The value "2" was experimentally determined because it gave a better distribution especially
        # when dealing with weights which are close to each others, e.g. 1.5, 2, 2.4, etc.
        target_min_weight = 2
        min_weight = min(u.weight for u in self._user_classes if not u.fixed_user_count)
        normalized_weights = [
            (user_class.__name__, round(target_min_weight * user_class.weight / min_weight))
            for user_class in self._user_classes
            if not user_class.fixed_user_count
        ]
        gen = smooth(normalized_weights)
        # Instead of calling `gen()` for each user, we cycle through a generator of fixed-length
        # `generation_length_to_get_proper_distribution`. Doing so greatly improves performance because
        # we only ever need to call `gen()` a relatively small number of times. The length of this generator
        # is chosen as the sum of the normalized weights. So, for users A, B, C of weights 2, 5, 6, the length is
        # 2 + 5 + 6 = 13 which would yield the distribution `CBACBCBCBCABC` that gets repeated over and over
        # until the target user count is reached.
        generation_length_to_get_proper_distribution = sum(
            normalized_weight[1] for normalized_weight in normalized_weights
        )
        yield from itertools.cycle(gen() for _ in range(generation_length_to_get_proper_distribution))

Code is not checked - I didn't have time to do it yet, but if somebody is available to do it - go ahead.

Cheeers,
Marcin

@cyberw
Copy link
Collaborator

cyberw commented Nov 19, 2021

Looks like a reasonable approach. If someone makes a PR with a few appropriate tests I’ll be happy to review.

@EzR1d3r
Copy link
Contributor Author

EzR1d3r commented Dec 20, 2021

Looks like a reasonable approach. If someone makes a PR with a few appropriate tests I’ll be happy to review.

After diving into code I realized what my previous comment was pretty senseless. I have deleted it. )) After experiments, I also realized that in the current implementation of user spawning, trying to generate them more evenly, they still have to give large weights, although I achieved some success and even wrote part of the tests, my implementation's behavior close to generating fixed users at first. So I back to the @marcinh realization, with some modification to resolve case with restore fixed users count after ramp-down and ramp-up.

@EzR1d3r
Copy link
Contributor Author

EzR1d3r commented Dec 25, 2021

#1964

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants