Skip to content

Commit

Permalink
Renamed hatch rate to spawn rate. Fixes #1405
Browse files Browse the repository at this point in the history
  • Loading branch information
cyberw committed Aug 18, 2020
1 parent 62fa37b commit eb15f2b
Show file tree
Hide file tree
Showing 24 changed files with 226 additions and 206 deletions.
9 changes: 9 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ Changelog Highlights

For full details of the Locust changelog, please see https://github.com/locustio/locust/blob/master/CHANGELOG.md

1.2
===

* Rename hatch rate to spawn rate (this may be a breaking change in some cases!)
* Ability to generate any custom load shape with LoadTestShape class
* Allow ramping down of users
* Ability to use save custom percentiles
* Bug fixes (excessive precision of metrics in losust csv stats, negative response time when system clock has changed, issue with non-string failure messages, some typos etc)

1.1.1
=====

Expand Down
2 changes: 1 addition & 1 deletion docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Example:
expect-workers = 5
host = http://target-system
users = 100
hatch-rate = 10
spawn-rate = 10
run-time = 10m
Expand Down
8 changes: 4 additions & 4 deletions docs/generating-custom-load-shape.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
Generating a custom load shape using a `LoadTestShape` class
=================================

Sometimes a completely custom shaped load test is required that cannot be achieved by simply setting or changing the user count and hatch rate. For example, generating a spike during a test or ramping up and down at custom times. In these cases using the `LoadTestShape` class can give complete control over the user count and hatch rate at all times.
Sometimes a completely custom shaped load test is required that cannot be achieved by simply setting or changing the user count and spawn rate. For example, generating a spike during a test or ramping up and down at custom times. In these cases using the `LoadTestShape` class can give complete control over the user count and spawn rate at all times.

How does a `LoadTestShape` class work?
---------------------------------------------

Define your class inheriting the `LoadTestShape` class in your locust file. If this type of class is found then it will be automatically used by Locust. Add a `tick()` method to return either a tuple containing the user count and hatch rate or `None` to stop the load test. Locust will call the `tick()` method every second and update the load test with new settings or stop the test.
Define your class inheriting the `LoadTestShape` class in your locust file. If this type of class is found then it will be automatically used by Locust. Add a `tick()` method to return either a tuple containing the user count and spawn rate or `None` to stop the load test. Locust will call the `tick()` method every second and update the load test with new settings or stop the test.

Examples
---------------------------------------------
Expand All @@ -25,14 +25,14 @@ Here is a simple example that will increase user count in blocks of 100 then sto
```python
class MyCustomShape(LoadTestShape):
time_limit = 600
hatch_rate = 20
spawn_rate = 20
def tick(self):
run_time = self.get_run_time()
if run_time < self.time_limit:
user_count = round(run_time, -2)
return (user_count, hatch_rate)
return (user_count, spawn_rate)
return None
```
6 changes: 3 additions & 3 deletions docs/running-locust-distributed.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ processor core** on the worker machines.

Otherwise - due to the current implementation -
you might end up with a distribution of the User classes that doesn't correspond to the
User classes' ``weight`` attribute. And if the hatch rate is lower than the number of worker
nodes, the hatching would occur in "bursts" where all worker node would hatch a single user and
then sleep for multiple seconds, hatch another user, sleep and repeat.
User classes' ``weight`` attribute. And if the spawn rate is lower than the number of worker
nodes, the spawning would occur in "bursts" where all worker node would spawn a single user and
then sleep for multiple seconds, spawn another user, sleep and repeat.


Example
Expand Down
4 changes: 2 additions & 2 deletions docs/running-locust-without-web-ui.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ like a CI server - by using the ``--headless`` flag together with ``-u`` and ``-
$ locust -f locust_files/my_locust_file.py --headless -u 1000 -r 100
``-u`` specifies the number of Users to spawn, and ``-r`` specifies the hatch rate
(number of users to spawn per second).
``-u`` specifies the number of Users to spawn, and ``-r`` specifies the spawn rate
(number of users to start per second).


Setting a time limit for the test
Expand Down
2 changes: 1 addition & 1 deletion docs/use-as-lib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The :py:class:`Environment <locust.env.Environment>` instance's
.. code-block:: python
env.create_local_runner()
env.runner.start(5000, hatch_rate=20)
env.runner.start(5000, spawn_rate=20)
env.runner.greenlet.join()
We could also use the :py:class:`Environment <locust.env.Environment>` instance's
Expand Down
18 changes: 9 additions & 9 deletions examples/custom_shape/stages.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,35 @@ class WebsiteUser(HttpUser):

class StagesShape(LoadTestShape):
"""
A simply load test shape class that has different user and hatch_rate at
A simply load test shape class that has different user and spawn_rate at
different stages.
Keyword arguments:
stages -- A list of dicts, each representing a stage with the following keys:
duration -- When this many seconds pass the test is advanced to the next stage
users -- Total user count
hatch_rate -- Hatch rate
spawn_rate -- Number of users to start/stop per second
stop -- A boolean that can stop that test at a specific stage
stop_at_end -- Can be set to stop once all stages have run.
"""

stages = [
{'duration': 60, 'users': 10, 'hatch_rate': 10},
{'duration': 100, 'users': 50, 'hatch_rate': 10},
{'duration': 180, 'users': 100, 'hatch_rate': 10},
{'duration': 220, 'users': 30, 'hatch_rate': 10},
{'duration': 230, 'users': 10, 'hatch_rate': 10},
{'duration': 240, 'users': 1, 'hatch_rate': 1},
{'duration': 60, 'users': 10, 'spawn_rate': 10},
{'duration': 100, 'users': 50, 'spawn_rate': 10},
{'duration': 180, 'users': 100, 'spawn_rate': 10},
{'duration': 220, 'users': 30, 'spawn_rate': 10},
{'duration': 230, 'users': 10, 'spawn_rate': 10},
{'duration': 240, 'users': 1, 'spawn_rate': 1},
]

def tick(self):
run_time = self.get_run_time()

for stage in self.stages:
if run_time < stage["duration"]:
tick_data = (stage["users"], stage["hatch_rate"])
tick_data = (stage["users"], stage["spawn_rate"])
return tick_data

return None
6 changes: 3 additions & 3 deletions examples/custom_shape/step_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ class StepLoadShape(LoadTestShape):
step_time -- Time between steps
step_load -- User increase amount at each step
hatch_rate -- Hatch rate to use at every step
spawn_rate -- Users to stop/start per second at every step
time_limit -- Time limit in seconds
"""

step_time = 30
step_load = 10
hatch_rate = 10
spawn_rate = 10
time_limit = 600

def tick(self):
Expand All @@ -40,4 +40,4 @@ def tick(self):
return None

current_step = math.floor(run_time / self.step_time) + 1
return (current_step * self.step_load, self.hatch_rate)
return (current_step * self.step_load, self.spawn_rate)
4 changes: 2 additions & 2 deletions examples/semaphore_wait.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

@events.init.add_listener
def _(environment, **kw):
@environment.events.hatch_complete.add_listener
def on_hatch_complete(**kw):
@environment.events.spawning_complete.add_listener
def on_spawning_complete(**kw):
all_users_spawned.release()

class UserTasks(TaskSet):
Expand Down
2 changes: 1 addition & 1 deletion examples/use_as_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def task_404(self):
gevent.spawn(stats_printer(env.stats))

# start the test
env.runner.start(1, hatch_rate=10)
env.runner.start(1, spawn_rate=10)

# in 60 seconds stop the runner
gevent.spawn_later(60, lambda: env.runner.quit())
Expand Down
14 changes: 10 additions & 4 deletions locust/argument_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,20 @@ def setup_parser_arguments(parser):
'-u', '--users',
type=int,
dest='num_users',
help="Number of concurrent Locust users. Only used together with --headless",
help="Number of concurrent Locust users. Primarily used together with --headless",
env_var="LOCUST_USERS",
)
parser.add_argument(
'-r', '--hatch-rate',
'-r', '--spawn-rate',
type=float,
help="The rate per second in which users are spawned. Only used together with --headless",
help="The rate per second in which users are spawned. Primarily used together with --headless",
env_var="LOCUST_SPAWN_RATE",
)
parser.add_argument(
'--hatch-rate',
env_var="LOCUST_HATCH_RATE",
action='store_true',
help=configargparse.SUPPRESS,
)
parser.add_argument(
'-t', '--run-time',
Expand Down Expand Up @@ -315,7 +321,7 @@ def setup_parser_arguments(parser):
stats_group.add_argument(
'--reset-stats',
action='store_true',
help="Reset statistics once hatching has been completed. Should be set on both master and workers when running in distributed mode",
help="Reset statistics once spawning has been completed. Should be set on both master and workers when running in distributed mode",
env_var="LOCUST_RESET_STATS",
)

Expand Down
4 changes: 2 additions & 2 deletions locust/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ class Events:
:param data: Data dict with the data from the worker node
"""

hatch_complete = EventHook
spawning_complete = EventHook
"""
Fired when all simulated users has been spawned.
Event arguments:
:param user_count: Number of users that was hatched
:param user_count: Number of users that were spawned
"""

quitting = EventHook
Expand Down
21 changes: 13 additions & 8 deletions locust/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,14 @@ def main():
options = parse_options()

if options.slave or options.expect_slaves:
sys.stderr.write("[DEPRECATED] Usage of slave has been deprecated, use --worker or --expect-workers\n")
sys.stderr.write("The --slave/--expect-slaves parameters have been renamed --worker/--expect-workers\n")
sys.exit(1)


if options.hatch_rate:
sys.stderr.write("The --hatch-rate parameter has been renamed --spawn-rate\n")
sys.exit(1)


# setup logging
if not options.skip_log_setup:
if options.loglevel.upper() in ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]:
Expand Down Expand Up @@ -186,8 +191,8 @@ def main():
# create locust Environment
environment = create_environment(user_classes, options, events=locust.events, shape_class=shape_class)

if shape_class and (options.num_users or options.hatch_rate or options.step_load):
logger.error("The specified locustfile contains a shape class but a conflicting argument was specified: users, hatch-rate or step-load")
if shape_class and (options.num_users or options.spawn_rate or options.step_load):
logger.error("The specified locustfile contains a shape class but a conflicting argument was specified: users, spawn-rate or step-load")
sys.exit(1)

if options.show_task_ratio:
Expand Down Expand Up @@ -301,16 +306,16 @@ def timelimit_stop():
# apply headless mode defaults
if options.num_users is None:
options.num_users = 1
if options.hatch_rate is None:
options.hatch_rate = 1
if options.spawn_rate is None:
options.spawn_rate = 1
if options.step_users is None:
options.step_users = 1

# start the test
if options.step_time:
runner.start_stepload(options.num_users, options.hatch_rate, options.step_users, options.step_time)
runner.start_stepload(options.num_users, options.spawn_rate, options.step_users, options.step_time)
else:
runner.start(options.num_users, options.hatch_rate)
runner.start(options.num_users, options.spawn_rate)

if options.run_time:
spawn_run_time_limit_greenlet()
Expand Down
Loading

0 comments on commit eb15f2b

Please sign in to comment.