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", ])
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)
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", ])
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)
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)
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)
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)
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)
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)
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"])
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()
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}")
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)
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)
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()
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)
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)
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)
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()
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()