From 97491e4f3c382a971fedd3bef5c61d860a6cbb69 Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Tue, 28 Apr 2020 18:41:53 +0200 Subject: [PATCH] * Explicitly set environment variable names for command line options. * Automatically generate documentation for all available environment variables. * Rename env variables LOCUST_MASTER, LOCUST_MASTER_PORT in order to make it less likely that they collide with env vars automatically added by Kubernetes. Also change the name of LOCUST_WORKER and LOCUST_MASTER_HOST for consistency. * Rename env variable CSVFILEBASE to LOCUST_CSV. * Remove --csv-base-name command line option. --- .gitignore | 1 + docs/changelog.rst | 15 +++++ docs/conf.py | 46 +++++++++++++-- docs/configuration.rst | 29 ++++++++++ docs/index.rst | 1 + docs/quickstart.rst | 24 +++----- locust/argument_parser.py | 117 +++++++++++++++++++++----------------- locust/main.py | 8 +-- 8 files changed, 164 insertions(+), 77 deletions(-) create mode 100644 docs/configuration.rst diff --git a/.gitignore b/.gitignore index 870b308182..4c1cd62663 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ locust.egg-info/** locustio.egg-info/** docs/_build/** docs/cli-help-output.txt +docs/env-options.rst mock.*.egg dist/** .idea/** diff --git a/docs/changelog.rst b/docs/changelog.rst index 12d899138c..ed11306c44 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -45,6 +45,20 @@ attribute instead: tasks = [MyTaskSet] +Environment variables changed +----------------------------- + +The following changes has been made to the configuration environment variables + +* ``LOCUST_MASTER`` has been renamed to ``LOCUST_MODE_MASTER`` (in order to make it less likely to get variable name collisions + when running Locust in Kubernetes/K8s which automatically adds environment variables depending on service/pod names). +* ``LOCUST_SLAVE`` has been renamed to ``LOCUST_MODE_WORKER``. +* ``LOCUST_MASTER_PORT`` has been renamed to ``LOCUST_MASTER_NODE_PORT``. +* ``LOCUST_MASTER_HOST`` has been renamed to ``LOCUST_MASTER_NODE_HOST``. +* ``CSVFILEBASE`` has been renamed to ``LOCUST_CSV``. + +See the :ref:`configuration` documentation for a full list of available :ref:`environment variables `. + Other breaking changes ---------------------- @@ -70,6 +84,7 @@ Other breaking changes * The official docker image no longer uses a shell script with a bunch of special environment variables to configure how how locust is started. Instead, the ``locust`` command is now set as ``ENTRYPOINT`` of the docker image. See :ref:`running-locust-docker` for more info. +* Command line option ``--csv-base-name`` has been removed, since it was just an alias for ``--csv``. Other fixes and improvements diff --git a/docs/conf.py b/docs/conf.py index 5e17eb39ba..38dab32863 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,14 +11,50 @@ import os import subprocess +from locust.argument_parser import get_empty_argument_parser, setup_parser_arguments # Run command `locust --help` and store output in cli-help-output.txt which is included in the docs -cli_help_output_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), "cli-help-output.txt") -print("Running `locust --help` command and storing output in %s" % cli_help_output_file) -help_output = subprocess.check_output(["locust", "--help"]).decode("utf-8") -with open(cli_help_output_file, "w") as f: - f.write(help_output) +def save_locust_help_output(): + cli_help_output_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), "cli-help-output.txt") + print("Running `locust --help` command and storing output in %s" % cli_help_output_file) + help_output = subprocess.check_output(["locust", "--help"]).decode("utf-8") + with open(cli_help_output_file, "w") as f: + f.write(help_output) + +save_locust_help_output() + +# Generate RST table with help/descriptions for all available environment variables +def save_locust_env_variables(): + env_options_output_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), "env-options.rst") + print("Generating RST table for Locust environment variables and storing in %s" % env_options_output_file) + parser = get_empty_argument_parser() + setup_parser_arguments(parser) + table_data = [] + for action in parser._actions: + if action.env_var: + table_data.append(( + action.env_var, + ", ".join(["``%s``" % c for c in action.option_strings]), + action.help, + )) + colsizes = [max(len(r[i]) for r in table_data) for i in range(len(table_data[0]))] + formatter = ' '.join('{:<%d}' % c for c in colsizes) + rows = [formatter.format(*row) for row in table_data] + edge = formatter.format(*['=' * c for c in colsizes]) + divider = formatter.format(*['-' * c for c in colsizes]) + headline = formatter.format(*["Name", "Command line option", "Description"]) + output = "\n".join([ + edge, + headline, + divider, + "\n".join(rows), + edge, + ]) + with open(env_options_output_file, "w") as f: + f.write(output) + +save_locust_env_variables() # The default replacements for |version| and |release|, also used in various diff --git a/docs/configuration.rst b/docs/configuration.rst new file mode 100644 index 0000000000..a876e34a3b --- /dev/null +++ b/docs/configuration.rst @@ -0,0 +1,29 @@ +.. _configuration: + +Configuration +============= + + +Command Line Options +----------------------------- + +The most straight forward way to Configure how Locust is run is through command line options. + +.. code-block:: console + + $ locust --help + +.. literalinclude:: cli-help-output.txt + :language: console + + + +.. _environment-variables: + +Environment variables +--------------------- + +Most of the configuration that can be set through command line arguments can also be set through +environment variables. Here's a table of all the available environment variables: + +.. include:: env-options.rst diff --git a/docs/index.rst b/docs/index.rst index 0e9e6e6b08..5c9310d730 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -39,6 +39,7 @@ Running your Locust tests .. toctree :: :maxdepth: 1 + configuration running-locust-distributed running-locust-docker running-locust-without-web-ui diff --git a/docs/quickstart.rst b/docs/quickstart.rst index c4a905e451..d79f26775d 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -144,22 +144,21 @@ host defaults to 127.0.0.1): $ locust -f locust_files/my_locust_file.py --worker --master-host=192.168.0.100 -Parameters can also be set in a `config file `_ (locust.conf or ~/.locust.conf) or in env vars, prefixed by LOCUST\_ +Parameters can also be set as :ref:`environment variables `, or in a +`config file `_ (``locust.conf`` or ``~/.locust.conf``). For example: (this will do the same thing as the previous command) +.. code-block:: console + + $ LOCUST_MASTER_NODE_HOST=192.168.0.100 locust + .. code-block:: # locust.conf in current directory locustfile locust_files/my_locust_file.py worker - -.. code-block:: console - - $ LOCUST_MASTER_HOST=192.168.0.100 locust - - .. note:: To see all available options type: ``locust --help`` @@ -175,12 +174,7 @@ greeted with something like this: .. image:: images/webui-splash-screenshot.png -Locust Command Line Interface -============================= - -.. code-block:: console - - $ locust --help +Locust Command Line Interface & Configuration +============================================= -.. literalinclude:: cli-help-output.txt - :language: console \ No newline at end of file +For a full list of available command line options see :ref:`configuration`. diff --git a/locust/argument_parser.py b/locust/argument_parser.py index 4c36396f11..61a8aa0f03 100644 --- a/locust/argument_parser.py +++ b/locust/argument_parser.py @@ -59,7 +59,6 @@ def find_locustfile(locustfile): def get_empty_argument_parser(add_help=True, default_config_files=DEFAULT_CONFIG_FILES): parser = configargparse.ArgumentParser( default_config_files=default_config_files, - auto_env_var_prefix="LOCUST_", add_env_var_help=False, add_config_file_help=False, add_help=add_help, @@ -74,7 +73,8 @@ def get_empty_argument_parser(add_help=True, default_config_files=DEFAULT_CONFIG parser.add_argument( '-f', '--locustfile', default='locustfile', - help="Python module file to import, e.g. '../other.py'. Default: locustfile" + help="Python module file to import, e.g. '../other.py'. Default: locustfile", + env_var="LOCUST_LOCUSTFILE", ) return parser @@ -125,68 +125,73 @@ def setup_parser_arguments(parser): parser._optionals.title = "Common options" parser.add_argument( '-H', '--host', - help="Host to load test in the following format: http://10.21.32.33" + help="Host to load test in the following format: http://10.21.32.33", + env_var="LOCUST_HOST", ) - # Number of Locust users parser.add_argument( '-u', '--users', type=int, dest='num_users', - help="Number of concurrent Locust users. Only used together with --headless" + help="Number of concurrent Locust users. Only used together with --headless", + env_var="LOCUST_USERS", ) - # User hatch rate parser.add_argument( '-r', '--hatch-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. Only used together with --headless", + env_var="LOCUST_HATCH_RATE", ) - # Time limit of the test run parser.add_argument( '-t', '--run-time', - help="Stop after the specified amount of time, e.g. (300s, 20m, 3h, 1h30m, etc.). Only used together with --headless" + help="Stop after the specified amount of time, e.g. (300s, 20m, 3h, 1h30m, etc.). Only used together with --headless", + env_var="LOCUST_RUN_TIME", ) - # List locust commands found in loaded locust files/source files parser.add_argument( '-l', '--list', action='store_true', dest='list_commands', - help="Show list of possible User classes and exit" + help="Show list of possible User classes and exit", ) web_ui_group = parser.add_argument_group("Web UI options") web_ui_group.add_argument( '--web-host', default="", - help="Host to bind the web interface to. Defaults to '*' (all interfaces)" + help="Host to bind the web interface to. Defaults to '*' (all interfaces)", + env_var="LOCUST_WEB_HOST", ) web_ui_group.add_argument( '--web-port', '-P', type=int, default=8089, - help="Port on which to run web host" + help="Port on which to run web host", + env_var="LOCUST_WEB_PORT", ) - # if we should print stats in the console web_ui_group.add_argument( '--headless', action='store_true', - help="Disable the web interface, and instead start the load test immediately. Requires -u and -t to be specified." + help="Disable the web interface, and instead start the load test immediately. Requires -u and -t to be specified.", + env_var="LOCUST_HEADLESS", ) web_ui_group.add_argument( '--web-auth', type=str, dest='web_auth', default=None, - help='Turn on Basic Auth for the web interface. Should be supplied in the following format: username:password' + help='Turn on Basic Auth for the web interface. Should be supplied in the following format: username:password', + env_var="LOCUST_WEB_AUTH", ) web_ui_group.add_argument( '--tls-cert', default="", - help="Optional path to TLS certificate to use to serve over HTTPS" + help="Optional path to TLS certificate to use to serve over HTTPS", + env_var="LOCUST_TLS_CERT", ) web_ui_group.add_argument( '--tls-key', default="", - help="Optional path to TLS private key to use to serve over HTTPS" + help="Optional path to TLS private key to use to serve over HTTPS", + env_var="LOCUST_TLS_KEY", ) master_group = parser.add_argument_group( @@ -197,29 +202,33 @@ def setup_parser_arguments(parser): master_group.add_argument( '--master', action='store_true', - help="Set locust to run in distributed mode with this process as master" + help="Set locust to run in distributed mode with this process as master", + env_var='LOCUST_MODE_MASTER', ) master_group.add_argument( '--master-bind-host', default="*", - help="Interfaces (hostname, ip) that locust master should bind to. Only used when running with --master. Defaults to * (all available interfaces)." + help="Interfaces (hostname, ip) that locust master should bind to. Only used when running with --master. Defaults to * (all available interfaces).", + env_var="LOCUST_MASTER_BIND_HOST", ) master_group.add_argument( '--master-bind-port', type=int, default=5557, - help="Port that locust master should bind to. Only used when running with --master. Defaults to 5557." + help="Port that locust master should bind to. Only used when running with --master. Defaults to 5557.", + env_var="LOCUST_MASTER_BIND_PORT", ) master_group.add_argument( '--expect-workers', type=int, default=1, - help="How many workers master should expect to connect before starting the test (only when --headless used)." + help="How many workers master should expect to connect before starting the test (only when --headless used).", + env_var="LOCUST_EXPECT_WORKERS", ) master_group.add_argument( '--expect-slaves', action='store_true', - help=configargparse.SUPPRESS + help=configargparse.SUPPRESS, ) worker_group = parser.add_argument_group( @@ -233,132 +242,133 @@ def setup_parser_arguments(parser): worker_group.add_argument( '--worker', action='store_true', - help="Set locust to run in distributed mode with this process as worker" + help="Set locust to run in distributed mode with this process as worker", + env_var="LOCUST_MODE_WORKER", ) worker_group.add_argument( '--slave', action='store_true', - help=configargparse.SUPPRESS + help=configargparse.SUPPRESS, ) # master host options worker_group.add_argument( '--master-host', default="127.0.0.1", - help="Host or IP address of locust master for distributed load testing. Only used when running with --worker. Defaults to 127.0.0.1." + help="Host or IP address of locust master for distributed load testing. Only used when running with --worker. Defaults to 127.0.0.1.", + env_var="LOCUST_MASTER_NODE_HOST", ) worker_group.add_argument( '--master-port', type=int, default=5557, - help="The port to connect to that is used by the locust master for distributed load testing. Only used when running with --worker. Defaults to 5557." + help="The port to connect to that is used by the locust master for distributed load testing. Only used when running with --worker. Defaults to 5557.", + env_var="LOCUST_MASTER_NODE_PORT", ) stats_group = parser.add_argument_group("Request statistics options") - # A file that contains the current request stats. stats_group.add_argument( - '--csv', '--csv-base-name', - dest='csvfilebase', - help="Store current request stats to files in CSV format.", + '--csv', + dest="csv_prefix", + help="Store current request stats to files in CSV format. Setting this option will generate three files: [CSV_PREFIX]_stats.csv, [CSV_PREFIX]_stats_history.csv and [CSV_PREFIX]_failures.csv", + env_var="LOCUST_CSV", ) - # Adds each stats entry at every iteration to the _stats_history.csv file. stats_group.add_argument( '--csv-full-history', action='store_true', default=False, dest='stats_history_enabled', help="Store each stats entry in CSV format to _stats_history.csv file", + env_var="LOCUST_CSV_FULL_HISTORY", ) - # if we should print stats in the console stats_group.add_argument( '--print-stats', action='store_true', - help="Print stats in the console" + help="Print stats in the console", + env_var="LOCUST_PRINT_STATS", ) - # only print summary stats stats_group.add_argument( '--only-summary', action='store_true', - help='Only print the summary stats' + help='Only print the summary stats', + env_var="LOCUST_ONLY_SUMMARY", ) 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", + env_var="LOCUST_RESET_STATS", ) log_group = parser.add_argument_group("Logging options") - # skip logging setup log_group.add_argument( '--skip-log-setup', action='store_true', dest='skip_log_setup', default=False, - help="Disable Locust's logging setup. Instead, the configuration is provided by the Locust test or Python defaults." + help="Disable Locust's logging setup. Instead, the configuration is provided by the Locust test or Python defaults.", + env_var="LOCUST_SKIP_LOG_SETUP", ) - # log level log_group.add_argument( '--loglevel', '-L', default='INFO', help="Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL. Default is INFO.", + env_var="LOCUST_LOGLEVEL", ) - # log file log_group.add_argument( '--logfile', help="Path to log file. If not set, log will go to stdout/stderr", + env_var="LOCUST_LOGFILE", ) step_load_group = parser.add_argument_group("Step load options") - # Enable Step Load mode step_load_group.add_argument( '--step-load', action='store_true', - help="Enable Step Load mode to monitor how performance metrics varies when user load increases. Requires --step-users and --step-time to be specified." + help="Enable Step Load mode to monitor how performance metrics varies when user load increases. Requires --step-users and --step-time to be specified.", + env_var="LOCUST_STEP_LOAD", ) - # Number of users to increase by Step step_load_group.add_argument( '--step-users', type=int, - help="User count to increase by step in Step Load mode. Only used together with --step-load" + help="User count to increase by step in Step Load mode. Only used together with --step-load", + env_var="LOCUST_STEP_USERS", ) step_load_group.add_argument( '--step-clients', action='store_true', help=configargparse.SUPPRESS ) - # Time limit of each step step_load_group.add_argument( '--step-time', - help="Step duration in Step Load mode, e.g. (300s, 20m, 3h, 1h30m, etc.). Only used together with --step-load" + help="Step duration in Step Load mode, e.g. (300s, 20m, 3h, 1h30m, etc.). Only used together with --step-load", + env_var="LOCUST_STEP_TIME", ) other_group = parser.add_argument_group("Other options") - # Display ratio table of all tasks other_group.add_argument( '--show-task-ratio', action='store_true', help="Print table of the User classes' task execution ratio" ) - # Display ratio table of all tasks in JSON format other_group.add_argument( '--show-task-ratio-json', action='store_true', help="Print json data of the User classes' task execution ratio" ) - # Version number (optparse gives you --version but we have to do it - # ourselves to get -V too. sigh) + # optparse gives you --version but we have to do it ourselves to get -V too other_group.add_argument( '--version', '-V', action='version', help="Show program's version number and exit", version='%(prog)s {}'.format(version), ) - # set the exit code to post on errors other_group.add_argument( '--exit-code-on-error', type=int, default=1, - help="Sets the process exit code to use when a test result contain any failure or error" + help="Sets the process exit code to use when a test result contain any failure or error", + env_var="LOCUST_EXIT_CODE_ON_ERROR", ) other_group.add_argument( '-s', '--stop-timeout', @@ -366,7 +376,8 @@ def setup_parser_arguments(parser): type=int, dest='stop_timeout', default=None, - help="Number of seconds to wait for a simulated user to complete any executing task before exiting. Default is to terminate immediately. This parameter only needs to be specified for the master process when running Locust distributed." + help="Number of seconds to wait for a simulated user to complete any executing task before exiting. Default is to terminate immediately. This parameter only needs to be specified for the master process when running Locust distributed.", + env_var="LOCUST_STOP_TIMEOUT", ) user_classes_group = parser.add_argument_group("User classes") diff --git a/locust/main.py b/locust/main.py index 9de55dcc3d..5e500cc7e9 100644 --- a/locust/main.py +++ b/locust/main.py @@ -283,8 +283,8 @@ def timelimit_stop(): stats_printer_greenlet = gevent.spawn(stats_printer(runner.stats)) stats_printer_greenlet.link_exception(greenlet_exception_handler) - if options.csvfilebase: - gevent.spawn(stats_writer, environment, options.csvfilebase, full_history=options.stats_history_enabled).link_exception(greenlet_exception_handler) + if options.csv_prefix: + gevent.spawn(stats_writer, environment, options.csv_prefix, full_history=options.stats_history_enabled).link_exception(greenlet_exception_handler) def shutdown(code=0): @@ -301,8 +301,8 @@ def shutdown(code=0): environment.events.quitting.fire(reverse=True) print_stats(runner.stats, current=False) print_percentile_stats(runner.stats) - if options.csvfilebase: - write_csv_files(environment, options.csvfilebase, full_history=options.stats_history_enabled) + if options.csv_prefix: + write_csv_files(environment, options.csv_prefix, full_history=options.stats_history_enabled) print_error_report(runner.stats) sys.exit(code)