Skip to content

Commit

Permalink
Use the new --processes feature in locust instead of using parallel t…
Browse files Browse the repository at this point in the history
…o launch workers.
  • Loading branch information
cyberw committed Nov 20, 2023
1 parent 7e74ec3 commit e357f6c
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 50 deletions.
43 changes: 21 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ On the loadgens:

```
pip install locust
apt install parallel # or yum install or whatever matches your system
# if you want to use locust-plugins:
# pip install locust-plugins
# as swarm automatically copies locust-plugins to loadgens every time, you can then uninstall it, leaving only its dependencies:
Expand All @@ -42,40 +41,40 @@ swarm -h
```

```
usage: swarm [-h] [-f LOCUSTFILE] --loadgen-list LOADGEN_LIST
[--processes-per-loadgen PROCESSES_PER_LOADGEN] [--selenium] [--playwright]
[--test-env TEST_ENV] [--loadgens LOADGENS] [-L LOGLEVEL] [--port PORT]
[--remote-master REMOTE_MASTER] [--extra-files EXTRA_FILES [FILES ...]] [--version]
usage: swarm [-h] [-f LOCUSTFILE] --loadgen-list LOADGEN_LIST [--loadgens LOADGENS] [--processes PROCESSES] [--selenium] [--playwright] [--test-env TEST_ENV] [--loglevel LOGLEVEL] [--port PORT] [--remote-master REMOTE_MASTER] [--extra-files EXTRA_FILES [EXTRA_FILES ...]] [--version]
A tool for running locust in a distributed fashion.
A tool for automating distributed locust runs using ssh.
optional arguments:
Example: swarm -f test.py --loadgen-list loadgen1.domain.com,loadgen2.domain.com --loadgens 2 --users 50
options:
-h, --help show this help message and exit
-f LOCUSTFILE, --locustfile LOCUSTFILE
[env var: LOCUST_LOCUSTFILE]
--loadgen-list LOADGEN_LIST
A comma-separated list of ssh servers on which to launch locust workers [env var: LOCUST_LOADGEN_LIST]
--processes-per-loadgen PROCESSES_PER_LOADGEN, -p PROCESSES_PER_LOADGEN
Number of locust worker processes to spawn on each load gen [env var: LOCUST_PROCESSES_PER_LOADGEN]
--selenium Start selenium server on load gens for use with locust-plugins's WebdriverUser [env var: LOCUST_SELENIUM]
--playwright Set LOCUST_PLAYWRIGHT env var for workers [env var: LOCUST_PLAYWRIGHT]
--test-env TEST_ENV Pass LOCUST_TEST_ENV to workers (in case your script needs it *before* argument parsing) [env var: LOCUST_TEST_ENV]
A comma-separated list of ssh servers on which to launch locust workers
--loadgens LOADGENS, -l LOADGENS
Number of servers to run locust workers on [env var: LOCUST_LOADGENS]
-L LOGLEVEL Use DEBUG for tracing issues with load gens etc
--port PORT [env var: LOCUST_PORT]
Number of servers to run locust workers on
--processes PROCESSES
This is passed on to locust unchanged and determines the number of worker processes per load generator.
--selenium Start selenium server on load gens for use with locust-plugins's WebdriverUser
--playwright Set LOCUST_PLAYWRIGHT env var for workers
--test-env TEST_ENV Pass LOCUST_TEST_ENV to workers (in case your script needs it *before* argument parsing)
--loglevel LOGLEVEL, -L LOGLEVEL
Use DEBUG for tracing issues with load gens etc
--port PORT
--remote-master REMOTE_MASTER
An ssh server to use as locust master (default is to run the master on the same machine as swarm). This is useful when rurnning swarm on your workstation if it might become disconnected [env var: LOCUST_REMOTE_MASTER]
An ssh server to use as locust master (default is to run the master locally). This is useful to prevent interrupting the load test if your workstation gets disconnected/goes to sleep.
--extra-files EXTRA_FILES [EXTRA_FILES ...]
A list of extra files or directories to upload. Space-separated, e.g. --extra-files testdata.csv common.py my-directory/ [env var: LOCUST_EXTRA_FILES]
A list of extra files or directories to upload. Space-separated, e.g. --extra-files testdata.csv *.py my-directory/
--version, -V Show program's version number and exit
Any parameters not listed here are forwarded to locust master unmodified, so go ahead and use things like -u, -r, --host, ... Swarm config can also be set using config file (~/.locust.conf, locust.conf, ~/.swarm.conf or swarm.conf). Example:
swarm --loadgen-list loadgen1.domain.com,loadgen2.domain.com -f test.py -u 10
Any parameters not listed here are forwarded to locust master unmodified, so go ahead and use things like --users, --host, --run-time, ...
If an arg is specified in more than one place, then commandline values override environment variables which override defaults.
Swarm config can also be set using config file (~/.locust.conf, locust.conf, ~/.swarm.conf or swarm.conf).
Parameters specified on command line override env vars, which in turn override config files.
```


## Example run

This assumes you have env vars like LOADGEN_LIST etc set. Just try running swarm and you'll get feedback on what is missing.
Expand Down
63 changes: 36 additions & 27 deletions locust_swarm/swarm.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,16 @@
"swarm.conf",
],
auto_env_var_prefix="LOCUST_",
description="A tool for running locust in a distributed fashion.",
epilog="Any parameters not listed here are forwarded to locust master unmodified, so go ahead and use things like -u, -r, --host, ... Swarm config can also be set using config file (~/.locust.conf, locust.conf, ~/.swarm.conf or swarm.conf).\nExample: swarm --loadgen-list loadgen1.domain.com,loadgen2.domain.com -f test.py -u 10",
formatter_class=configargparse.RawDescriptionHelpFormatter,
description="""A tool for automating distributed locust runs using ssh.
Example: swarm -f test.py --loadgen-list loadgen1.domain.com,loadgen2.domain.com --loadgens 2 --users 50""",
epilog="""Any parameters not listed here are forwarded to locust master unmodified, so go ahead and use things like --users, --host, --run-time, ...
Swarm config can also be set using config file (~/.locust.conf, locust.conf, ~/.swarm.conf or swarm.conf).
Parameters specified on command line override env vars, which in turn override config files.""",
add_config_file_help=False,
add_env_var_help=False,
)

parser.add_argument(
Expand All @@ -58,13 +65,27 @@
required=True,
help="A comma-separated list of ssh servers on which to launch locust workers",
)
parser.add_argument(
"--loadgens",
"-l",
type=int,
default=1,
help="Number of servers to run locust workers on",
)
parser.add_argument(
"--processes-per-loadgen",
"-p",
type=int,
default=0,
help=configargparse.SUPPRESS,
)
parser.add_argument(
"--processes",
type=int,
default=4,
help="Number of locust worker processes to spawn on each load gen",
help="This is passed on to locust unchanged and determines the number of worker processes per load generator.",
)

parser.add_argument(
"--selenium",
action="store_true",
Expand All @@ -85,13 +106,6 @@
help="Pass LOCUST_TEST_ENV to workers (in case your script needs it *before* argument parsing)",
env_var="LOCUST_TEST_ENV",
)
parser.add_argument(
"--loadgens",
"-l",
type=int,
default=1,
help="Number of servers to run locust workers on",
)
parser.add_argument(
"--loglevel",
"-L",
Expand All @@ -102,7 +116,7 @@
parser.add_argument(
"--remote-master",
type=str,
help="An ssh server to use as locust master (default is to run the master on the same machine as swarm). This is useful when rurnning swarm on your workstation if it might become disconnected",
help="An ssh server to use as locust master (default is to run the master locally). This is useful to prevent interrupting the load test if your workstation gets disconnected/goes to sleep.",
)
parser.add_argument(
"--exit-timeout",
Expand Down Expand Up @@ -314,12 +328,10 @@ def start_worker_process(server, port, locustfile_filename):
"'",
*extra_env,
*nohup,
"parallel",
"-j0",
"-N0",
"--ungroup",
"locust",
"--worker",
"--processes",
str(args.processes),
"--master-port",
str(port),
*master_parameters,
Expand All @@ -328,8 +340,6 @@ def start_worker_process(server, port, locustfile_filename):
"30",
"-f",
locustfile_filename,
":::",
"{1.." + str(args.processes_per_loadgen) + "}",
*ensure_remote_kill,
"'",
]
Expand Down Expand Up @@ -367,11 +377,13 @@ def main():

locustfile_filename = os.path.split(locustfile)[1]
port = int(args.port)
processes_per_loadgen = args.processes_per_loadgen
loadgens = args.loadgens
if loadgens < 1:
parser.error("loadgens parameter must be 1 or higher") # pylint: disable=not-callable
worker_process_count = processes_per_loadgen * loadgens
if args.processes_per_loadgen:
parser.error(
f"--processes-per-loadgen has been removed in favour of locusts native --processes parameter (you had it set to {args.processes_per_loadgen})"
)
if args.loadgens < 1:
parser.error("loadgens parameter must be 1 or higher")
worker_process_count = args.processes * args.loadgens
loadgen_list = args.loadgen_list.split(",")

try:
Expand Down Expand Up @@ -407,10 +419,10 @@ def get_available_servers_and_lock_them():
for server in loadgen_list:
if check_and_lock_server(server):
available_servers.append(server)
if len(available_servers) == loadgens:
if len(available_servers) == args.loadgens:
return available_servers
logging.info(
f"Only found {len(available_servers)} available servers, wanted {loadgens}. Will try again in {check_interval} seconds..."
f"Only found {len(available_servers)} available servers, wanted {args.loadgens}. Will try again in {check_interval} seconds..."
)
available_servers = []
time.sleep(check_interval)
Expand All @@ -425,9 +437,6 @@ def get_available_servers_and_lock_them():
start_time = datetime.now(timezone.utc)
atexit.register(cleanup, server_list)

# needed for compatibility with locust-plugins < 2.6.11. We can remove this at some point in the future
os.environ["LOCUST_RUN_ID"] = start_time.isoformat()

if args.remote_master:
logging.info("Some argument passing will not work with remote master (broken since 2.0)")
env_vars = ["PYTHONUNBUFFERED=1"]
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ def run(self):
requirement_list = [
"keyring==21.4.0",
"psutil",
"ConfigArgParse>=1.0",
"ConfigArgParse>=1.7",
"locust>=2.18.5.dev11",
]
# if locust-plugins IS installed, then require a version known to work with this version of swarm.
spec = importlib.util.find_spec("locust_plugins")
Expand Down

0 comments on commit e357f6c

Please sign in to comment.