diff --git a/docs/changelog.rst b/docs/changelog.rst index f7191b0c05..38d70bfd09 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -10,6 +10,7 @@ For full details of the Locust changelog, please see https://github.com/locustio Breaking changes ---------------- +* The option for running Locust without the Web UI has been renamed from ``--no-web`` to ``--headless``. * Removed ``Locust.setup``, ``Locust.teardown``, ``TaskSet.setup`` and ``TaskSet.teardown`` hooks. If you want to run code at the start or end of a test, you should instead use the :py:attr:`test_start ` and :py:attr:`test_stop ` events: diff --git a/docs/retrieving-stats.rst b/docs/retrieving-stats.rst index 8b935625a1..ce3e09ee2a 100644 --- a/docs/retrieving-stats.rst +++ b/docs/retrieving-stats.rst @@ -7,11 +7,11 @@ You may wish to consume your Locust results via a CSV file. In this case, there First, when running Locust with the web UI, you can retrieve CSV files under the Download Data tab. Secondly, you can run Locust with a flag which will periodically save two CSV files. This is particularly useful -if you plan on running Locust in an automated way with the ``--no-web`` flag: +if you plan on running Locust in an automated way with the ``--headless`` flag: .. code-block:: console - $ locust -f examples/basic.py --csv=example --no-web -t10m + $ locust -f examples/basic.py --csv=example --headless -t10m The files will be named ``example_response_times.csv`` and ``example_stats.csv`` (when using ``--csv=example``) and mirror Locust's built in stat pages. diff --git a/docs/running-locust-distributed.rst b/docs/running-locust-distributed.rst index 85b7dc6fd1..7caac89e2a 100644 --- a/docs/running-locust-distributed.rst +++ b/docs/running-locust-distributed.rst @@ -84,7 +84,7 @@ listen to. Defaults to 5557. ``--expect-workers=X`` ---------------------- -Used when starting the master node with ``--no-web``. The master node will then wait until X worker +Used when starting the master node with ``--headless``. The master node will then wait until X worker nodes has connected before the test is started. diff --git a/docs/running-locust-docker.rst b/docs/running-locust-docker.rst index d0f0069ec3..b40ad9b52c 100644 --- a/docs/running-locust-docker.rst +++ b/docs/running-locust-docker.rst @@ -56,7 +56,7 @@ To run in standalone mode without the web UI, you can use the ``LOCUST_OPTS`` en .. code-block:: console - docker run --volume $PWD/dir/of/locustfile:/mnt/locust -e LOCUSTFILE_PATH=/mnt/locust/locustfile.py -e TARGET_URL=https://abc.com -e LOCUST_OPTS="--clients=10 --no-web --run-time=600" locustio/locust + docker run --volume $PWD/dir/of/locustfile:/mnt/locust -e LOCUSTFILE_PATH=/mnt/locust/locustfile.py -e TARGET_URL=https://abc.com -e LOCUST_OPTS="--clients=10 --headless --run-time=600" locustio/locust If you are Kubernetes user, you can use the `Helm chart `_ to scale and run locust. diff --git a/docs/running-locust-in-step-load-mode.rst b/docs/running-locust-in-step-load-mode.rst index beac676543..30510de340 100644 --- a/docs/running-locust-in-step-load-mode.rst +++ b/docs/running-locust-in-step-load-mode.rst @@ -39,7 +39,7 @@ If you want to run Locust in step load mode without the web UI, you can do that .. code-block:: console - $ locust -f --no-web -c 1000 -r 100 --run-time 1h30m --step-load --step-clients 300 --step-time 20m + $ locust -f --headless -c 1000 -r 100 --run-time 1h30m --step-load --step-clients 300 --step-time 20m Locust will swarm the clients by step and shutdown once the time is up. diff --git a/docs/running-locust-without-web-ui.rst b/docs/running-locust-without-web-ui.rst index 8e8ad86278..084c602991 100644 --- a/docs/running-locust-without-web-ui.rst +++ b/docs/running-locust-without-web-ui.rst @@ -5,11 +5,11 @@ Running Locust without the web UI ================================= You can run locust without the web UI - for example if you want to run it in some automated flow, -like a CI server - by using the ``--no-web`` flag together with ``-c`` and ``-r``: +like a CI server - by using the ``--headless`` flag together with ``-c`` and ``-r``: .. code-block:: console - $ locust -f locust_files/my_locust_file.py --no-web -c 1000 -r 100 + $ locust -f locust_files/my_locust_file.py --headless -c 1000 -r 100 ``-c`` specifies the number of Locust users to spawn, and ``-r`` specifies the hatch rate (number of users to spawn per second). @@ -22,7 +22,7 @@ If you want to specify the run time for a test, you can do that with ``--run-tim .. code-block:: console - $ locust -f --no-web -c 1000 -r 100 --run-time 1h30m + $ locust -f --headless -c 1000 -r 100 --run-time 1h30m Locust will shutdown once the time is up. @@ -33,7 +33,7 @@ By default, locust will stop your tasks immediately. If you want to allow your t .. code-block:: console - $ locust -f --no-web -c 1000 -r 100 --run-time 1h30m --stop-timeout 99 + $ locust -f --headless -c 1000 -r 100 --run-time 1h30m --stop-timeout 99 .. _running-locust-distributed-without-web-ui: diff --git a/locust/argument_parser.py b/locust/argument_parser.py index ec36c3118e..231c0c8815 100644 --- a/locust/argument_parser.py +++ b/locust/argument_parser.py @@ -1,5 +1,7 @@ +import argparse import os import sys +import textwrap import configargparse @@ -59,7 +61,15 @@ def get_empty_argument_parser(add_help=True, default_config_files=DEFAULT_CONFIG 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, + formatter_class=argparse.RawDescriptionHelpFormatter, + usage=argparse.SUPPRESS, + description=textwrap.dedent(""" + Usage: locust [OPTIONS] [LocustClass ...] + + """), + #epilog="", ) parser.add_argument( '-f', '--locustfile', @@ -82,7 +92,7 @@ def parse_locustfile_option(args=None): default=False, ) parser.add_argument( - '-V', '--version', + '--version', '-V', action='store_true', default=False, ) @@ -112,205 +122,226 @@ def setup_parser_arguments(parser): Takes a configargparse.ArgumentParser as argument and calls it's add_argument for each of the supported arguments """ + parser._optionals.title = "Common options" parser.add_argument( '-H', '--host', help="Host to load test in the following format: http://10.21.32.33" ) + # Number of Locust users parser.add_argument( + '-c', '--clients', + type=int, + dest='num_clients', + default=1, + help="Number of concurrent Locust users. Only used together with --headless" + ) + # User hatch rate + parser.add_argument( + '-r', '--hatch-rate', + type=float, + default=1, + help="The rate per second in which clients are spawned. Only used together with --headless" + ) + # 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" + ) + # 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 locust 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)" ) - parser.add_argument( - '-P', '--web-port', + web_ui_group.add_argument( + '--web-port', '-P', type=int, default=8089, help="Port on which to run web host" ) - # A file that contains the current request stats. - parser.add_argument( - '--csv', '--csv-base-name', - dest='csvfilebase', - help="Store current request stats to files in CSV format.", - ) - # Adds each stats entry at every iteration to the _stats_history.csv file. - parser.add_argument( - '--csv-full-history', + # if we should print stats in the console + web_ui_group.add_argument( + '--headless', action='store_true', - default=False, - dest='stats_history_enabled', - help="Store each stats entry in CSV format to _stats_history.csv file", + help="Disable the web interface, and instead start the load test immediately. Requires -c and -t to be specified." + ) + + master_group = parser.add_argument_group( + "Master options", + "Options for running a Locust Master node when running Locust distributed. A Master node need Worker nodes that connect to it before it can run load tests.", ) # if locust should be run in distributed mode as master - parser.add_argument( + master_group.add_argument( '--master', action='store_true', help="Set locust to run in distributed mode with this process as 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)." + ) + 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." + ) + 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)." + ) + master_group.add_argument( + '--expect-slaves', + action='store_true', + help=configargparse.SUPPRESS + ) + + worker_group = parser.add_argument_group( + "Worker options", + textwrap.dedent(""" + Options for running a Locust Worker node when running Locust distributed. + Only the LOCUSTFILE (-f option) need to be specified when starting a Worker, since other options such as -c, -r, -t are specified on the Master node. + """), + ) # if locust should be run in distributed mode as worker - parser.add_argument( + worker_group.add_argument( '--worker', action='store_true', help="Set locust to run in distributed mode with this process as worker" ) - parser.add_argument( + worker_group.add_argument( '--slave', action='store_true', help=configargparse.SUPPRESS ) # master host options - parser.add_argument( + 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." ) - parser.add_argument( + 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." ) - parser.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)." - ) - parser.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." - ) - parser.add_argument( - '--expect-workers', - type=int, - default=1, - help="How many workers master should expect to connect before starting the test (only when --no-web used)." + + 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.", ) - parser.add_argument( - '--expect-slaves', + # Adds each stats entry at every iteration to the _stats_history.csv file. + stats_group.add_argument( + '--csv-full-history', action='store_true', - help=configargparse.SUPPRESS - ) + default=False, + dest='stats_history_enabled', + help="Store each stats entry in CSV format to _stats_history.csv file", + ) # if we should print stats in the console - parser.add_argument( - '--no-web', + stats_group.add_argument( + '--print-stats', action='store_true', - help="Disable the web interface, and instead start running the test immediately. Requires -c and -t to be specified." - ) - # Number of clients - parser.add_argument( - '-c', '--clients', - type=int, - dest='num_clients', - default=1, - help="Number of concurrent Locust users. Only used together with --no-web" + help="Print stats in the console" ) - # Client hatch rate - parser.add_argument( - '-r', '--hatch-rate', - type=float, - default=1, - help="The rate per second in which clients are spawned. Only used together with --no-web" + # only print summary stats + stats_group.add_argument( + '--only-summary', + action='store_true', + help='Only print the summary stats' ) - # 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 --no-web" + 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", ) + + log_group = parser.add_argument_group("Logging options") # skip logging setup - parser.add_argument( + 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." ) + # log level + log_group.add_argument( + '--loglevel', '-L', + default='INFO', + help="Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL. Default is INFO.", + ) + # log file + log_group.add_argument( + '--logfile', + help="Path to log file. If not set, log will go to stdout/stderr", + ) + + step_load_group = parser.add_argument_group("Step load options") # Enable Step Load mode - parser.add_argument( + 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-clients and --step-time to be specified." ) # Number of clients to incease by Step - parser.add_argument( + step_load_group.add_argument( '--step-clients', type=int, default=1, help="Client count to increase by step in Step Load mode. Only used together with --step-load" ) # Time limit of each step - parser.add_argument( + 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" ) - # log level - parser.add_argument( - '--loglevel', '-L', - default='INFO', - help="Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL. Default is INFO.", - ) - # log file - parser.add_argument( - '--logfile', - help="Path to log file. If not set, log will go to stdout/stderr", - ) - # if we should print stats in the console - parser.add_argument( - '--print-stats', - action='store_true', - help="Print stats in the console" - ) - # only print summary stats - parser.add_argument( - '--only-summary', - action='store_true', - help='Only print the summary stats' - ) - parser.add_argument( - '--no-reset-stats', - action='store_true', - help="[DEPRECATED] Do not reset statistics once hatching has been completed. This is now the default behavior. See --reset-stats to disable", - ) - parser.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", - ) - # 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 locust classes and exit" - ) + + + other_group = parser.add_argument_group("Other options") # Display ratio table of all tasks - parser.add_argument( + other_group.add_argument( '--show-task-ratio', action='store_true', - help="print table of the locust classes' task execution ratio" + help="Print table of the locust classes' task execution ratio" ) # Display ratio table of all tasks in JSON format - parser.add_argument( + other_group.add_argument( '--show-task-ratio-json', action='store_true', - help="print json data of the locust classes' task execution ratio" + help="Print json data of the locust classes' task execution ratio" ) # Version number (optparse gives you --version but we have to do it # ourselves to get -V too. sigh) - parser.add_argument( - '-V', '--version', + 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 - parser.add_argument( + other_group.add_argument( '--exit-code-on-error', type=int, default=1, - help="sets the exit code to post on error" + help="Sets the process exit code to use when a test result contain any failure or error" ) - parser.add_argument( + other_group.add_argument( '-s', '--stop-timeout', action='store', type=int, @@ -318,10 +349,13 @@ def setup_parser_arguments(parser): 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." ) - parser.add_argument( + + locust_classes_group = parser.add_argument_group("Locust user classes") + locust_classes_group.add_argument( 'locust_classes', nargs='*', metavar='LocustClass', + help="Optionally specify which Locust classes that should be used (available Locust classes can be listed with -l or --list)", ) diff --git a/locust/contrib/fasthttp.py b/locust/contrib/fasthttp.py index 0ac735fc17..a0597dc549 100644 --- a/locust/contrib/fasthttp.py +++ b/locust/contrib/fasthttp.py @@ -85,7 +85,7 @@ class by using the :py:func:`@task decorator ` on the methods, """Parameter passed to FastHttpSession. Default True, meaning no SSL verification.""" abstract = True - """dont register this as a locust""" + """Dont register this as a locust that can be run by itself""" def __init__(self, environment): super().__init__(environment) diff --git a/locust/main.py b/locust/main.py index 837b431272..f13c604a85 100644 --- a/locust/main.py +++ b/locust/main.py @@ -27,11 +27,10 @@ version = locust.__version__ -def is_locust(tup): +def is_locust(item): """ - Takes (name, object) tuple, returns True if it's a public Locust subclass. + Check if a variable is a runnable (non-abstract) Locust class """ - name, item = tup return bool( inspect.isclass(item) and issubclass(item, Locust) @@ -86,7 +85,7 @@ def __import_locustfile__(filename, path): sys.path.insert(index + 1, directory) del sys.path[0] # Return our two-tuple - locusts = dict(filter(is_locust, vars(imported).items())) + locusts = {name:value for name, value in vars(imported).items() if is_locust(value)} return imported.__doc__, locusts @@ -206,8 +205,8 @@ def main(): main_greenlet = runner.greenlet if options.run_time: - if not options.no_web: - logger.error("The --run-time argument can only be used together with --no-web") + if not options.headless: + logger.error("The --run-time argument can only be used together with --headless") sys.exit(1) if options.worker: logger.error("--run-time should be specified on the master node, and not on worker nodes") @@ -225,7 +224,7 @@ def timelimit_stop(): gevent.spawn_later(options.run_time, timelimit_stop) # start Web UI - if not options.no_web and not options.worker: + if not options.headless and not options.worker: # spawn web greenlet logger.info("Starting web monitor at http://%s:%s" % (options.web_host or "*", options.web_port)) web_ui = WebUI(environment=environment) @@ -237,7 +236,7 @@ def timelimit_stop(): # need access to the Environment, Runner or WebUI environment.events.init.fire(environment=environment, runner=runner, web_ui=web_ui) - if options.no_web: + if options.headless: # headless mode if options.master: # what for worker nodes to connect @@ -256,7 +255,7 @@ def timelimit_stop(): spawn_run_time_limit_greenlet() stats_printer_greenlet = None - if not options.only_summary and (options.print_stats or (options.no_web and not options.worker)): + if not options.only_summary and (options.print_stats or (options.headless and not options.worker)): # spawn stats printing greenlet stats_printer_greenlet = gevent.spawn(stats_printer(runner.stats)) diff --git a/locust/stats.py b/locust/stats.py index ee7c71e328..f91e9f9b93 100644 --- a/locust/stats.py +++ b/locust/stats.py @@ -853,7 +853,7 @@ def stats_history_csv(stats, stats_history_enabled=False, csv_for_web_ui=False): """Returns the Aggregated stats entry every interval""" # csv_for_web_ui boolean returns the header along with the stats history row so that # it can be returned as a csv for download on the web ui. Otherwise when run with - # the '--no-web' option we write the header first and then append the file with stats + # the '--headless' option we write the header first and then append the file with stats # entries every interval. if csv_for_web_ui: rows = [stats_history_csv_header()] diff --git a/locust/test/test_fasthttp.py b/locust/test/test_fasthttp.py index a4b96820d7..9254fae7f7 100644 --- a/locust/test/test_fasthttp.py +++ b/locust/test/test_fasthttp.py @@ -4,6 +4,7 @@ from locust.core import task, TaskSet from locust.contrib.fasthttp import FastHttpSession, FastHttpLocust from locust.exception import CatchResponseError, InterruptTaskSet, ResponseError +from locust.main import is_locust from .testcases import WebserverTestCase @@ -175,6 +176,10 @@ class MyLocust(FastHttpLocust): class TestFastHttpLocustClass(WebserverTestCase): + def test_is_abstract(self): + self.assertTrue(FastHttpLocust.abstract) + self.assertFalse(is_locust(FastHttpLocust)) + def test_get_request(self): self.response = "" def t1(l): diff --git a/locust/test/test_main.py b/locust/test/test_main.py index afe8bf3110..aa296e9b15 100644 --- a/locust/test/test_main.py +++ b/locust/test/test_main.py @@ -10,10 +10,10 @@ class TestLoadLocustfile(LocustTestCase): def test_is_locust(self): - self.assertFalse(main.is_locust(("Locust", Locust))) - self.assertFalse(main.is_locust(("HttpLocust", HttpLocust))) - self.assertFalse(main.is_locust(("random_dict", {}))) - self.assertFalse(main.is_locust(("random_list", []))) + self.assertFalse(main.is_locust(Locust)) + self.assertFalse(main.is_locust(HttpLocust)) + self.assertFalse(main.is_locust({})) + self.assertFalse(main.is_locust([])) class MyTaskSet(TaskSet): pass @@ -24,13 +24,13 @@ class MyHttpLocust(HttpLocust): class MyLocust(Locust): tasks = [MyTaskSet] - self.assertTrue(main.is_locust(("MyHttpLocust", MyHttpLocust))) - self.assertTrue(main.is_locust(("MyLocust", MyLocust))) + self.assertTrue(main.is_locust(MyHttpLocust)) + self.assertTrue(main.is_locust(MyLocust)) class ThriftLocust(Locust): abstract = True - self.assertFalse(main.is_locust(("ThriftLocust", ThriftLocust))) + self.assertFalse(main.is_locust(ThriftLocust)) def test_load_locust_file_from_absolute_path(self): with mock_locustfile() as mocked: diff --git a/locust/test/test_parser.py b/locust/test/test_parser.py index f55a7a3893..639044aca4 100644 --- a/locust/test/test_parser.py +++ b/locust/test/test_parser.py @@ -26,13 +26,6 @@ def test_reset_stats(self): opts = self.parser.parse_args(args) self.assertEqual(opts.reset_stats, True) - def test_should_accept_legacy_no_reset_stats(self): - args = [ - "--no-reset-stats" - ] - opts = self.parser.parse_args(args) - self.assertEqual(opts.reset_stats, False) - def test_skip_log_setup(self): args = [ "--skip-log-setup"