Example #1
0
 def test_csv_full_history_requires_csv(self):
     with mock.patch("sys.stderr", new=StringIO()):
         with self.assertRaises(SystemExit):
             parse_options(args=[
                 "-f",
                 "locustfile.py",
                 "--csv-full-history",
             ])
Example #2
0
 def test_create_environment(self):
     options = parse_options(args=[
         "--host", "https://custom-host",
         "--reset-stats",
     ])
     env = create_environment([], options)
     self.assertEqual("https://custom-host", env.host)
     self.assertTrue(env.reset_stats)
     
     options = parse_options(args=[])
     env = create_environment([], options)
     self.assertEqual(None, env.host)
     self.assertFalse(env.reset_stats)
Example #3
0
 def test_unknown_command_line_arg(self):
     with self.assertRaises(SystemExit):
         with mock.patch("sys.stderr", new=StringIO()):
             parse_options(args=[
                 "-f", "something.py",
                 "-c", "100",
                 "-r", "10",
                 "-t", "5m",
                 "--reset-stats",
                 "--stop-timeout", "5",
                 "--unknown-flag", 
                 "MyLocustClass",
             ])
Example #4
0
 def test_specify_config_file(self):
     with temporary_file(
         textwrap.dedent(
             """
         host = localhost  # With "="
         u 100             # Short form
         spawn-rate 5      # long form
                           # boolean
         headless
         # (for some reason an inline comment makes boolean values fail in configargparse nowadays)
     """
         ),
         suffix=".conf",
     ) as conf_file_path:
         options = parse_options(
             args=[
                 "--config",
                 conf_file_path,
             ]
         )
         self.assertEqual(conf_file_path, options.config)
         self.assertEqual("localhost", options.host)
         self.assertEqual(100, options.num_users)
         self.assertEqual(5, options.spawn_rate)
         self.assertTrue(options.headless)
Example #5
0
    def test_index_with_hatch_options(self):
        html_to_option = {
            'locust_count': ['-c', '100'],
            'hatch_rate': ['-r', '10.0'],
            'step_locust_count': ['--step-clients', '20'],
            'step_duration': ['--step-time', '15'],
        }
        self.environment.step_load = True
        for html_name_to_test in html_to_option.keys():
            # Test that setting each hatch option individually populates the corresponding field in the html, and none of the others
            self.environment.parsed_options = parse_options(
                html_to_option[html_name_to_test])

            response = requests.get("http://127.0.0.1:%i/" % self.web_port)
            self.assertEqual(200, response.status_code)

            d = pq(response.content.decode('utf-8'))

            for html_name in html_to_option.keys():
                start_value = d(f'.start [name={html_name}]').attr('value')
                edit_value = d(f'.edit [name={html_name}]').attr('value')
                if html_name_to_test == html_name:
                    self.assertEqual(html_to_option[html_name][1], start_value)
                    self.assertEqual(html_to_option[html_name][1], edit_value)
                else:
                    self.assertEqual('', start_value)
                    self.assertEqual('', edit_value)
Example #6
0
    def test_index_with_hatch_options(self):
        html_to_option = {
            "user_count": ["-u", "100"],
            "hatch_rate": ["-r", "10.0"],
            "step_user_count": ["--step-users", "20"],
            "step_duration": ["--step-time", "15"],
        }
        self.environment.step_load = True
        for html_name_to_test in html_to_option.keys():
            # Test that setting each hatch option individually populates the corresponding field in the html, and none of the others
            self.environment.parsed_options = parse_options(
                html_to_option[html_name_to_test])

            response = requests.get("http://127.0.0.1:%i/" % self.web_port)
            self.assertEqual(200, response.status_code)

            d = pq(response.content.decode("utf-8"))

            for html_name in html_to_option.keys():
                start_value = d(f".start [name={html_name}]").attr("value")
                edit_value = d(f".edit [name={html_name}]").attr("value")
                if html_name_to_test == html_name:
                    self.assertEqual(html_to_option[html_name][1], start_value)
                    self.assertEqual(html_to_option[html_name][1], edit_value)
                else:
                    self.assertEqual("", start_value)
                    self.assertEqual("", edit_value)
Example #7
0
 def test_parse_options(self):
     options = parse_options(
         args=[
             "-f",
             "locustfile.py",
             "-u",
             "100",
             "-r",
             "10",
             "-t",
             "5m",
             "--reset-stats",
             "--stop-timeout",
             "5",
             "MyUserClass",
         ]
     )
     self.assertEqual("locustfile.py", options.locustfile)
     self.assertEqual(100, options.num_users)
     self.assertEqual(10, options.spawn_rate)
     self.assertEqual("5m", options.run_time)
     self.assertTrue(options.reset_stats)
     self.assertEqual(5, options.stop_timeout)
     self.assertEqual(["MyUserClass"], options.user_classes)
     # check default arg
     self.assertEqual(8089, options.web_port)
Example #8
0
    def test_swarm_custom_argument(self):
        my_dict = {}

        class MyUser(User):
            host = "http://example.com"
            wait_time = constant(1)

            @task(1)
            def my_task(self):
                my_dict["val"] = self.environment.parsed_options.my_argument

        @locust.events.init_command_line_parser.add_listener
        def _(parser, **kw):
            parser.add_argument("--my-argument",
                                type=int,
                                help="Give me a number")

        self.environment.user_classes = [MyUser]
        self.environment.parsed_options = parse_options(
            args=["--my-argument", "42"])
        response = requests.post(
            "http://127.0.0.1:%i/swarm" % self.web_port,
            data={
                "user_count": 1,
                "spawn_rate": 1,
                "host": "",
                "my_argument": "42"
            },
        )
        self.assertEqual(200, response.status_code)
        self.assertEqual(my_dict["val"], 42)
Example #9
0
 def test_command_line_arguments_override_config_file(self):
     with temporary_file("host=from_file", suffix=".conf") as conf_file_path:
         options = parse_options(args=[
             "--config", conf_file_path,
             "--host", "from_args",
         ])
         self.assertEqual("from_args", options.host)
Example #10
0
    def test_custom_argument_included_in_web_ui(self):
        @locust.events.init_command_line_parser.add_listener
        def _(parser, **kw):
            parser.add_argument("--a1", help="a1 help")
            parser.add_argument("--a2",
                                help="a2 help",
                                include_in_web_ui=False)

        args = [
            "-u",
            "666",
            "--a1",
            "v1",
            "--a2",
            "v2",
        ]
        options = parse_options(args=args)
        self.assertEqual(666, options.num_users)
        self.assertEqual("v1", options.a1)
        self.assertEqual("v2", options.a2)

        extra_args = ui_extra_args_dict(args)
        self.assertIn("a1", extra_args)
        self.assertNotIn("a2", extra_args)
        self.assertEqual("v1", extra_args["a1"])
Example #11
0
def run_single_user(
    locust_class: User,
    env=None,
    catch_exceptions=False,
    include_length=False,
    include_time=False,
    include_context=False,
    init_listener=None,
    loglevel=None,
):
    _gevent_debugger_patch()
    if loglevel:
        locust.log.setup_logging(loglevel)
    if env is None:
        env = Environment()
        env.parsed_options = argument_parser.parse_options()
        listeners.Print(env,
                        include_length=include_length,
                        include_time=include_time,
                        include_context=include_context)
    env.events.init.fire(environment=env, runner=None, web_ui=None)
    if init_listener:
        init_listener(env)
    locust_class._catch_exceptions = catch_exceptions
    locust_class(env).run()
Example #12
0
    def test_index_with_spawn_options(self):
        html_to_option = {
            "user_count": ["-u", "100"],
            "spawn_rate": ["-r", "10.0"],
        }
        for html_name_to_test in html_to_option.keys():
            # Test that setting each spawn option individually populates the corresponding field in the html, and none of the others
            self.environment.parsed_options = parse_options(
                html_to_option[html_name_to_test])

            response = requests.get("http://127.0.0.1:%i/" % self.web_port)
            self.assertEqual(200, response.status_code)

            d = pq(response.content.decode("utf-8"))

            for html_name in html_to_option.keys():
                start_value = d(f".start [name={html_name}]").attr("value")
                edit_value = d(f".edit [name={html_name}]").attr("value")
                if html_name_to_test == html_name:
                    self.assertEqual(html_to_option[html_name][1], start_value)
                    self.assertEqual(html_to_option[html_name][1], edit_value)
                else:
                    self.assertEqual(
                        "1",
                        start_value,
                        msg=f"start value was {start_value} for {html_name}")
                    self.assertEqual(
                        "1",
                        edit_value,
                        msg=f"edit value was {edit_value} for {html_name}")
Example #13
0
    def test_custom_argument_help_message(self):
        @locust.events.init_command_line_parser.add_listener
        def _(parser, **kw):
            parser.add_argument("--custom-bool-arg", action="store_true", help="Custom boolean flag")
            parser.add_argument(
                "--custom-string-arg",
                help="Custom string arg",
            )

        out = StringIO()
        with mock.patch("sys.stdout", new=out):
            with self.assertRaises(SystemExit):
                parse_options(args=["--help"])

        out.seek(0)
        stdout = out.read()
        self.assertIn("Custom boolean flag", stdout)
        self.assertIn("Custom string arg", stdout)
Example #14
0
 def test_locustfile_can_be_set_in_config_file(self):
     with temporary_file(
         "locustfile my_locust_file.py",
         suffix=".conf",
     ) as conf_file_path:
         options = parse_options(args=[
             "--config", conf_file_path,
         ])
         self.assertEqual("my_locust_file.py", options.locustfile)
Example #15
0
def run_single_user(
    user_class: Type[User],
    include_length=False,
    include_time=False,
    include_context=False,
    include_payload=False,
    loglevel=None,
):
    """
    Runs a single User. Useful when you want to run a debugger.

    It creates in a new locust :py:attr:`Environment <locust.env.Environment>` and triggers any ``init`` or ``test_start`` :ref:`events <extending_locust>` as normal.

    It does **not** trigger ``test_stop`` or ``quit`` when you quit the debugger.

    It prints some info about every request to stdout, and you can get additional info using the `include_*` flags

    By default, it does not set up locusts logging system (because it could interfere with the printing of requests),
    but you can change that by passing a log level (e.g. *loglevel="INFO"*)
    """
    global _env

    if loglevel:
        locust.log.setup_logging(loglevel)

    if not _env:
        _env = locust.env.Environment(events=locust.events)
        # in case your test goes looking for the file name of your locustfile
        _env.parsed_options = argument_parser.parse_options()
        frame = inspect.stack()[1]
        _env.parsed_options.locustfile = os.path.basename(
            frame[0].f_code.co_filename)
        # log requests to stdout
        PrintListener(
            _env,
            include_length=include_length,
            include_time=include_time,
            include_context=include_context,
            include_payload=include_payload,
        )
        # fire various events (quit and test_stop will never get called, sorry about that)
        _env.events.init.fire(environment=_env, runner=None, web_ui=None)

    # do the things that the Runner usually does
    _env.user_classes = [user_class]
    _env._filter_tasks_by_tags()
    _env.events.test_start.fire(environment=_env)

    # create a single user
    user = user_class(_env)
    setattr(
        _env, "single_user_instance", user
    )  # if you happen to need access to this from the Environment instance
    user.run()
Example #16
0
 def test_specify_config_file(self):
     with temporary_file(textwrap.dedent("""
         host = localhost  # With "="
         u 100             # Short form
         spawn-rate 5      # long form
         headless          # boolean
     """), suffix=".conf") as conf_file_path:
         options = parse_options(args=[
             "--config", conf_file_path,
         ])
         self.assertEqual(conf_file_path, options.config)
         self.assertEqual("localhost", options.host)
         self.assertEqual(100, options.num_users)
         self.assertEqual(5, options.spawn_rate)
         self.assertTrue(options.headless)
Example #17
0
    def test_custom_argument(self):
        @locust.events.init_command_line_parser.add_listener
        def _(parser, **kw):
            parser.add_argument('--custom-bool-arg',
                                action='store_true',
                                help="Custom boolean flag")
            parser.add_argument(
                '--custom-string-arg',
                help="Custom string arg",
            )

        options = parse_options(args=[
            "-u",
            "666",
            "--custom-bool-arg",
            "--custom-string-arg",
            "HEJ",
        ])
        self.assertEqual(666, options.num_users)
        self.assertEqual("HEJ", options.custom_string_arg)
        self.assertTrue(options.custom_bool_arg)
Example #18
0



TARGET_APP_DIR = os.getenv('TARGET_DIR' , './app')
LOCUST_RUN_TIME = os.getenv('LOCUST_RUN_TIME' , 20)
LOCUST_USER_COUNT = os.getenv('LOCUST_USER_COUNT' , 10)
LOCUST_SPAWN_RATE = os.getenv('LOCUST_SPAWN_RATE' , 5)
LOCUST_SPAWN_RATE = os.getenv('LOCUST_SPAWN_RATE' , 5)

setup_logging("INFO", None)
logger = logging.getLogger()
greenlet_exception_handler = greenlet_exception_logger(logger)


options = parse_options()
from app.OnceUser import WebsiteUser
env = Environment(user_classes=[WebsiteUser], host="http://www.google.com", reset_stats=True, parsed_options=options)
if  os.getenv('OnceUser.py'):
    pass    
else:
    pass
    #raise ValueError('test was not loaded')

stats_csv_writer = StatsCSVFileWriter(env, stats.PERCENTILES_TO_REPORT, './app/reports', options.stats_history_enabled)
print(type(stats_csv_writer))
env.create_local_runner()

env.runner.start(LOCUST_USER_COUNT, LOCUST_SPAWN_RATE)
gevent.spawn(stats_printer(env.stats))
gevent.spawn(stats_csv_writer.stats_writer).link_exception(greenlet_exception_handler)
Example #19
0
def main():
    # find specified locustfile and make sure it exists, using a very simplified
    # command line parser that is only used to parse the -f option
    locustfile = parse_locustfile_option()

    # import the locustfile
    docstring, user_classes, shape_class = load_locustfile(locustfile)

    # parse all command line options
    options = parse_options()

    if options.slave or options.expect_slaves:
        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(
            "[DEPRECATED] The --hatch-rate parameter has been renamed --spawn-rate\n"
        )
        options.spawn_rate = options.hatch_rate

    # setup logging
    if not options.skip_log_setup:
        if options.loglevel.upper() in [
                "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"
        ]:
            setup_logging(options.loglevel, options.logfile)
        else:
            sys.stderr.write(
                "Invalid --loglevel. Valid values are: DEBUG/INFO/WARNING/ERROR/CRITICAL\n"
            )
            sys.exit(1)

    logger = logging.getLogger(__name__)
    greenlet_exception_handler = greenlet_exception_logger(logger)

    if options.list_commands:
        print("Available Users:")
        for name in user_classes:
            print("    " + name)
        sys.exit(0)

    if not user_classes:
        logger.error("No User class found!")
        sys.exit(1)

    # make sure specified User exists
    if options.user_classes:
        missing = set(options.user_classes) - set(user_classes.keys())
        if missing:
            logger.error("Unknown User(s): %s\n" % (", ".join(missing)))
            sys.exit(1)
        else:
            names = set(options.user_classes) & set(user_classes.keys())
            user_classes = [user_classes[n] for n in names]
    else:
        # list() call is needed to consume the dict_view object in Python 3
        user_classes = list(user_classes.values())

    try:
        import resource
        if resource.getrlimit(resource.RLIMIT_NOFILE)[0] < 10000:
            # Increasing the limit to 10000 within a running process should work on at least MacOS.
            # It does not work on all OS:es, but we should be no worse off for trying.
            resource.setrlimit(resource.RLIMIT_NOFILE,
                               [10000, resource.RLIM_INFINITY])
    except:
        logger.warning(
            "System open file limit setting is not high enough for load testing, and the OS didn't allow locust to increase it by itself. See https://github.com/locustio/locust/wiki/Installation#increasing-maximum-number-of-open-files-limit for more info."
        )

    # 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.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:
        print("\n Task ratio per User class")
        print("-" * 80)
        print_task_ratio(user_classes)
        print("\n Total task ratio")
        print("-" * 80)
        print_task_ratio(user_classes, total=True)
        sys.exit(0)
    if options.show_task_ratio_json:
        from json import dumps
        task_data = {
            "per_class": get_task_ratio_dict(user_classes),
            "total": get_task_ratio_dict(user_classes, total=True)
        }
        print(dumps(task_data))
        sys.exit(0)

    if options.step_time:
        if not options.step_load:
            logger.error(
                "The --step-time argument can only be used together with --step-load"
            )
            sys.exit(1)
        if options.worker:
            logger.error(
                "--step-time should be specified on the master node, and not on worker nodes"
            )
            sys.exit(1)
        try:
            options.step_time = parse_timespan(options.step_time)
        except ValueError:
            logger.error(
                "Valid --step-time formats are: 20, 20s, 3m, 2h, 1h20m, 3h30m10s, etc."
            )
            sys.exit(1)

    if options.master:
        runner = environment.create_master_runner(
            master_bind_host=options.master_bind_host,
            master_bind_port=options.master_bind_port,
        )
    elif options.worker:
        try:
            runner = environment.create_worker_runner(options.master_host,
                                                      options.master_port)
        except socket.error as e:
            logger.error("Failed to connect to the Locust master: %s", e)
            sys.exit(-1)
    else:
        runner = environment.create_local_runner()

    # main_greenlet is pointing to runners.greenlet by default, it will point the web greenlet later if in web mode
    main_greenlet = runner.greenlet

    if options.run_time:
        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"
            )
            sys.exit(1)
        try:
            options.run_time = parse_timespan(options.run_time)
        except ValueError:
            logger.error(
                "Valid --run-time formats are: 20, 20s, 3m, 2h, 1h20m, 3h30m10s, etc."
            )
            sys.exit(1)

        def spawn_run_time_limit_greenlet():
            logger.info("Run time limit set to %s seconds" % options.run_time)

            def timelimit_stop():
                logger.info("Time limit reached. Stopping Locust.")
                runner.quit()

            gevent.spawn_later(
                options.run_time,
                timelimit_stop).link_exception(greenlet_exception_handler)

    if options.csv_prefix:
        stats_csv_writer = StatsCSVFileWriter(environment,
                                              stats.PERCENTILES_TO_REPORT,
                                              options.csv_prefix,
                                              options.stats_history_enabled)
    else:
        stats_csv_writer = StatsCSV(environment, stats.PERCENTILES_TO_REPORT)

    # start Web UI
    if not options.headless and not options.worker:
        # spawn web greenlet
        protocol = "https" if options.tls_cert and options.tls_key else "http"
        try:
            if options.web_host == "*":
                # special check for "*" so that we're consistent with --master-bind-host
                web_host = ''
            else:
                web_host = options.web_host
            if web_host:
                logger.info("Starting web interface at %s://%s:%s" %
                            (protocol, web_host, options.web_port))
            else:
                logger.info(
                    "Starting web interface at %s://0.0.0.0:%s (accepting connections from all network interfaces)"
                    % (protocol, options.web_port))
            web_ui = environment.create_web_ui(
                host=web_host,
                port=options.web_port,
                auth_credentials=options.web_auth,
                tls_cert=options.tls_cert,
                tls_key=options.tls_key,
                stats_csv_writer=stats_csv_writer,
            )
        except AuthCredentialsError:
            logger.error(
                "Credentials supplied with --web-auth should have the format: username:password"
            )
            sys.exit(1)
        else:
            main_greenlet = web_ui.greenlet
    else:
        web_ui = None

    # Fire locust init event which can be used by end-users' code to run setup code that
    # need access to the Environment, Runner or WebUI
    environment.events.init.fire(environment=environment,
                                 runner=runner,
                                 web_ui=web_ui)

    if options.headless:
        # headless mode
        if options.master:
            # wait for worker nodes to connect
            while len(runner.clients.ready) < options.expect_workers:
                logging.info(
                    "Waiting for workers to be ready, %s of %s connected",
                    len(runner.clients.ready), options.expect_workers)
                time.sleep(1)
        if not options.worker:
            # apply headless mode defaults
            if options.num_users is None:
                options.num_users = 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.spawn_rate,
                                      options.step_users, options.step_time)
            if environment.shape_class:
                environment.runner.start_shape()
            else:
                runner.start(options.num_users, options.spawn_rate)

    if options.run_time:
        spawn_run_time_limit_greenlet()

    stats_printer_greenlet = None
    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))
        stats_printer_greenlet.link_exception(greenlet_exception_handler)

    if options.csv_prefix:
        gevent.spawn(stats_csv_writer.stats_writer).link_exception(
            greenlet_exception_handler)

    gevent.spawn(stats_history, runner)

    def shutdown():
        """
        Shut down locust by firing quitting event, printing/writing stats and exiting
        """
        logger.info("Running teardowns...")
        environment.events.quitting.fire(environment=environment, reverse=True)

        # determine the process exit code
        if log.unhandled_greenlet_exception:
            code = 2
        elif environment.process_exit_code is not None:
            code = environment.process_exit_code
        elif len(runner.errors) or len(runner.exceptions):
            code = options.exit_code_on_error
        else:
            code = 0

        logger.info("Shutting down (exit code %s), bye." % code)
        if stats_printer_greenlet is not None:
            stats_printer_greenlet.kill(block=False)
        logger.info("Cleaning up runner...")
        if runner is not None:
            runner.quit()

        print_stats(runner.stats, current=False)
        print_percentile_stats(runner.stats)

        print_error_report(runner.stats)

        sys.exit(code)

    # install SIGTERM handler
    def sig_term_handler():
        logger.info("Got SIGTERM signal")
        shutdown()

    gevent.signal_handler(signal.SIGTERM, sig_term_handler)

    try:
        logger.info("Starting Locust %s" % version)
        main_greenlet.join()
        shutdown()
    except KeyboardInterrupt as e:
        shutdown()
Example #20
0
def main():
    # 改成直接使用满足boomer中的类
    user_classes = {"name": Dummy}
    user_classes = list(user_classes.values())

    # 解析命令行参数
    options = parse_options()
    print(options)
    # 设置logging
    if not options.skip_log_setup:
        if options.loglevel.upper() in [
                "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"
        ]:
            setup_logging(options.loglevel, options.logfile)
        else:
            sys.stderr.write(
                "非法参数--log-level. 合法值: DEBUG/INFO/WARNING/ERROR/CRITICAL\n")
            sys.exit(1)
    logger = logging.getLogger(__name__)

    logger.warning(
        "这里是locust hazard改造的web-ui专用版本,只支持以下命令:\n" +
        "\t--master-host(必须)、--master-bind-port、--web-port、--web-auth、--tls-cert、--tls-key、--log-level"
    )

    logger.info("options.master_host=" + options.master_host)
    if not options.master_host:
        sys.stdout.write("请提供--master-host参数,以便通知压测机\n")
        exit(0)

    try:
        import resource
        if resource.getrlimit(resource.RLIMIT_NOFILE)[0] < 10000:
            # Increasing the limit to 10000 within a running process should work on at least MacOS.
            # It does not work on all OS:es, but we should be no worse off for trying.
            resource.setrlimit(resource.RLIMIT_NOFILE,
                               [10000, resource.RLIM_INFINITY])
    except:
        logger.warning(
            "System open file limit setting is not high enough for load testing, and the OS wouldnt allow locust to increase it by itself.\n"
            +
            "\tSee https://docs.locust.io/en/stable/installation.html#increasing-maximum-number-of-open-files-limit for more info."
        )

    # create locust Environment
    environment = create_environment(user_classes,
                                     options,
                                     events=locust.events)

    # 只使用master模式运行
    runner = environment.create_master_runner(
        master_bind_host="*", master_bind_port=options.master_bind_port)
    runner.state = runners.STATE_STOPPED
    stats_csv_writer = StatsCSV(environment, stats.PERCENTILES_TO_REPORT)

    # 开启web-ui服务
    web_host = "0.0.0.0"
    protocol = "https" if options.tls_cert and options.tls_key else "http"
    logger.info("Starting web interface at %s://%s:%s" %
                (protocol, '127.0.0.1', options.web_port))
    try:
        web_ui = WebUI(
            environment,
            host=web_host,
            port=options.web_port,
            masterHost=options.master_host,
            auth_credentials=options.web_auth,
            tls_cert=options.tls_cert,
            tls_key=options.tls_key,
            stats_csv_writer=stats_csv_writer,
        )
    except AuthCredentialsError:
        logger.error(
            "Credentials supplied with --web-auth should have the format: username:password"
        )
        sys.exit(1)
    else:
        main_greenlet = web_ui.greenlet

    # Fire locust init event which can be used by end-users' code to run setup code that
    # need access to the Environment, Runner or WebUI
    environment.events.init.fire(environment=environment,
                                 runner=runner,
                                 web_ui=web_ui)

    stats_printer_greenlet = None  # 指标打印协程

    def shutdown():
        """
        Shut down locust by firing quitting event, printing/writing stats and exiting
        """
        logger.info("Running teardowns...")
        environment.events.quitting.fire(environment=environment, reverse=True)

        # determine the process exit code
        if log.unhandled_greenlet_exception:
            code = 2
        elif environment.process_exit_code is not None:
            code = environment.process_exit_code
        elif len(runner.errors) or len(runner.exceptions):
            code = options.exit_code_on_error
        else:
            code = 0

        logger.info("Shutting down (exit code %s), bye." % code)
        if stats_printer_greenlet is not None:
            stats_printer_greenlet.kill(block=False)
        logger.info("Cleaning up runner...")
        if runner is not None:
            runner.quit()

        print_stats(runner.stats, current=False)
        print_percentile_stats(runner.stats)

        print_error_report(runner.stats)
        sys.exit(code)

    # install SIGTERM handler
    def sig_term_handler():
        logger.info("Got SIGTERM signal, shutdown.......")
        shutdown()

    gevent.signal_handler(signal.SIGTERM, sig_term_handler)

    try:
        logger.info("Starting Locust %s" % version)
        main_greenlet.join()
        logger.info("The main_greenlet finished, shutdown.......")
        shutdown()
    except KeyboardInterrupt as e:
        logger.error("Got the KeyboardInterrupt, shutdown.......")
        shutdown()