def test_spawn_zero_locusts(self): class MyTaskSet(TaskSet): @task def my_task(self): pass class MyTestLocust(Locust): task_set = MyTaskSet wait_time = constant(0.1) runner = LocalLocustRunner([MyTestLocust], self.options) timeout = gevent.Timeout(2.0) timeout.start() try: runner.start_hatching(0, 1, wait=True) runner.greenlet.join() except gevent.Timeout: self.fail("Got Timeout exception. A locust seems to have been spawned, even though 0 was specified.") finally: timeout.cancel()
def test_cpu_warning(self): _monitor_interval = runners.CPU_MONITOR_INTERVAL runners.CPU_MONITOR_INTERVAL = 2.0 try: class CpuLocust(Locust): wait_time = constant(0.001) @task def cpu_task(self): for i in range(1000000): _ = 3 / 2 environment = Environment(options=mocked_options(), ) runner = LocalLocustRunner(environment, [CpuLocust]) self.assertFalse(runner.cpu_warning_emitted) runner.spawn_locusts(1, 1, wait=False) sleep(2.5) runner.quit() self.assertTrue(runner.cpu_warning_emitted) finally: runners.CPU_MONITOR_INTERVAL = _monitor_interval
def test_stop_event_stop_and_quit(self): class User(Locust): wait_time = constant(1) @task def my_task(self): pass test_stop_run = [0] environment = Environment(options=mocked_options()) def on_test_stop(*args, **kwargs): test_stop_run[0] += 1 environment.events.test_stop.add_listener(on_test_stop) runner = LocalLocustRunner(environment, locust_classes=[User]) runner.start(locust_count=3, hatch_rate=3, wait=False) self.assertEqual(0, test_stop_run[0]) runner.stop() runner.quit() self.assertEqual(1, test_stop_run[0])
def test_exception_is_catched(self): """ Test that exceptions are stored, and execution continues """ class MyTaskSet(TaskSet): def __init__(self, *a, **kw): super(MyTaskSet, self).__init__(*a, **kw) self._task_queue = [ {"callable":self.will_error, "args":[], "kwargs":{}}, {"callable":self.will_stop, "args":[], "kwargs":{}}, ] @task(1) def will_error(self): raise HeyAnException(":(") @task(1) def will_stop(self): raise StopLocust() class MyLocust(Locust): wait_time = constant(0.01) tasks = [MyTaskSet] runner = LocalLocustRunner(self.environment, [MyLocust]) l = MyLocust(self.environment) # make sure HeyAnException isn't raised l.run() l.run() # make sure we got two entries in the error log self.assertEqual(2, len(self.mocked_log.error)) # make sure exception was stored self.assertEqual(1, len(runner.exceptions)) hash_key, exception = runner.exceptions.popitem() self.assertTrue("traceback" in exception) self.assertTrue("HeyAnException" in exception["traceback"]) self.assertEqual(2, exception["count"])
def test_exception_in_task(self): class HeyAnException(Exception): pass class MyLocust(Locust): class task_set(TaskSet): @task def will_error(self): raise HeyAnException(":(") runner = LocalLocustRunner([MyLocust], self.options) l = MyLocust() l._catch_exceptions = False self.assertRaises(HeyAnException, l.run) self.assertRaises(HeyAnException, l.run) self.assertEqual(1, len(runner.exceptions)) hash_key, exception = runner.exceptions.popitem() self.assertTrue("traceback" in exception) self.assertTrue("HeyAnException" in exception["traceback"]) self.assertEqual(2, exception["count"])
def test_spawn_zero_locusts(self): class MyTaskSet(TaskSet): @task def my_task(self): pass class MyTestLocust(Locust): tasks = [MyTaskSet] wait_time = constant(0.1) environment = Environment(options=mocked_options()) runner = LocalLocustRunner(environment, [MyTestLocust]) timeout = gevent.Timeout(2.0) timeout.start() try: runner.start(0, 1, wait=True) runner.hatching_greenlet.join() except gevent.Timeout: self.fail("Got Timeout exception. A locust seems to have been spawned, even though 0 was specified.") finally: timeout.cancel()
def test_no_reset_stats(self): class User(Locust): wait_time = constant(0) @task class task_set(TaskSet): @task def my_task(self): self.locust.environment.events.request_success.fire( request_type="GET", name="/test", response_time=666, response_length=1337, ) sleep(2) environment = Environment(reset_stats=False, options=mocked_options()) runner = LocalLocustRunner(environment, locust_classes=[User]) runner.start(locust_count=6, hatch_rate=12, wait=False) sleep(0.25) self.assertGreaterEqual(runner.stats.get("/test", "GET").num_requests, 3) sleep(0.3) self.assertEqual(6, runner.stats.get("/test", "GET").num_requests) runner.quit()
def test_start_event(self): class User(Locust): wait_time = constant(1) task_run_count = 0 @task def my_task(self): User.task_run_count += 1 test_start_run = [0] environment = Environment(locust_classes=[User]) def on_test_start(*args, **kwargs): test_start_run[0] += 1 environment.events.test_start.add_listener(on_test_start) runner = LocalLocustRunner(environment) runner.start(locust_count=3, hatch_rate=3, wait=False) runner.hatching_greenlet.get(timeout=3) self.assertEqual(1, test_start_run[0]) self.assertEqual(3, User.task_run_count)
wait_time = between(1, 3) host = "https://docs.locust.io" class task_set(TaskSet): @task def my_task(self): self.client.get("/") @task def task_404(self): self.client.get("/non-existing-path") # setup Environment and Runner env = Environment() runner = LocalLocustRunner(environment=env, locust_classes=[User]) # start a WebUI instance web_ui = WebUI(environment=env) gevent.spawn(lambda: web_ui.start("127.0.0.1", 8089)) # TODO: fix #def on_request_success(request_type, name, response_time, response_length, **kwargs): # report_to_grafana("%_%s" % (request_type, name), response_time) #env.events.request_succes.add_listener(on_request_success) # start a greenlet that periodically outputs the current stats gevent.spawn(stats_printer(runner.stats)) # start the test runner.start(1, hatch_rate=10) # wait for the greenlets (indefinitely)
def test_runner_reference_on_environment(self): env = Environment() runner = LocalLocustRunner(environment=env, locust_classes=[]) self.assertEqual(env, runner.environment)
def main(): parser, options, arguments = parse_options() # setup logging if options.show_version: print("Locust %s" % (version,)) sys.exit(0) locustfile = find_locustfile(options.locustfile) if not locustfile: logger.error("Could not find any locustfile! Ensure file ends in '.py' and see --help for available options.") sys.exit(1) docstring, locusts = load_locustfile(locustfile) if options.list_commands: logger.info("Available Locusts:") for name in locusts: logger.info(" " + name) sys.exit(0) if not locusts: logger.error("No Locust class found!") sys.exit(1) # make sure specified Locust exists if arguments: missing = set(arguments) - set(locusts.keys()) if missing: logger.error("Unknown Locust(s): %s\n" % (", ".join(missing))) sys.exit(1) else: names = set(arguments) & set(locusts.keys()) locust_classes = [locusts[n] for n in names] else: locust_classes = locusts.values() if options.show_task_ratio: logger.info("\n Task ratio per locust class") logger.info( "-" * 80) print_task_ratio(locust_classes) logger.info("\n Total task ratio") logger.info("-" * 80) print_task_ratio(locust_classes, total=True) sys.exit(0) if options.show_task_ratio_json: from json import dumps task_data = { "per_class": get_task_ratio_dict(locust_classes), "total": get_task_ratio_dict(locust_classes, total=True) } logger.info(dumps(task_data)) sys.exit(0) # if --master is set, make sure --no-web isn't set if options.master and options.no_web: logger.error("Locust can not run distributed with the web interface disabled (do not use --no-web and --master together)") sys.exit(0) if not options.no_web and not options.slave: # spawn web greenlet logger.info("Starting web monitor at %s:%s" % (options.web_host or "*", options.port)) main_greenlet = gevent.spawn(web.start, locust_classes, options) if not options.master and not options.slave: runners.locust_runner = LocalLocustRunner(locust_classes, options) # spawn client spawning/hatching greenlet if options.no_web: runners.locust_runner.start_hatching(wait=True) main_greenlet = runners.locust_runner.greenlet elif options.master: runners.locust_runner = MasterLocustRunner(locust_classes, options) elif options.slave: try: runners.locust_runner = SlaveLocustRunner(locust_classes, options) main_greenlet = runners.locust_runner.greenlet except socket.error as e: logger.error("Failed to connect to the Locust master: %s", e) sys.exit(-1) if not options.only_summary and (options.print_stats or (options.no_web and not options.slave)): # spawn stats printing greenlet gevent.spawn(stats_printer) def shutdown(code=0): """ Shut down locust by firing quitting event, printing stats and exiting """ logger.info("Shutting down (exit code %s), bye." % code) events.quitting.fire() print_stats(runners.locust_runner.request_stats) print_percentile_stats(runners.locust_runner.request_stats) print_error_report() sys.exit(code) # install SIGTERM handler def sig_term_handler(): logger.info("Got SIGTERM signal") shutdown(0) gevent.signal(signal.SIGTERM, sig_term_handler) try: logger.info("Starting Locust %s" % version) main_greenlet.join() code = 0 if len(runners.locust_runner.errors): code = 1 shutdown(code=code) except KeyboardInterrupt as e: shutdown(0)
def main(): parser, options = parse_options() # setup logging if not options.skip_log_setup: setup_logging(options.loglevel, options.logfile) logger = logging.getLogger(__name__) locust_path = get_locust_path() if options.web: web_port = options.web_port logger.info('Running easy-locust web: 0.0.0.0:{}'.format(web_port)) init_app(port=web_port) sys.exit(0) if options.demo: if not locust_path: logger.error( '''Cannot locate Python path, make sure it is in right place. If windows add it to sys PATH, if linux make sure python is installed in /usr/local/lib/''') sys.exit(1) pt_demo_path = os.path.join(locust_path, 'demo', 'demo_pressuretest.xls') pt_demo_path_json = os.path.join(locust_path, 'demo', 'demo_locustfile.json') pt_new_demo = os.path.join(os.getcwd(), 'PtDemo.xls') pt_new_demo_json = os.path.join(os.getcwd(), 'demo.json') shutil.copyfile(pt_demo_path, pt_new_demo) shutil.copyfile(pt_demo_path_json, pt_new_demo_json) sys.exit(0) if options.xlsfile: pt_file = options.xlsfile if not (pt_file.endswith('.xls') or pt_file.endswith('.json')): logger.error( "PressureTest file must be end with '.xls' or '.json' and see --help for available options." ) sys.exit(1) if not os.path.isfile(pt_file): logger.error('PressureTest file is not exist, please check it.') sys.exit(1) _status = generate_locust_file(pt_file) if not _status: sys.exit(1) sys.exit(0) locustfile = find_locustfile(options.locustfile) if not locustfile: logger.error( "Could not find any locustfile! Ensure file ends in '.py' and see --help for available options." ) sys.exit(1) if locustfile == "locust.py" or locustfile == "locust.xls" or locustfile == "locust.json": logger.error( "The locustfile must not be named `locust.py` or `locust.xls` or `locust.json`. " "Please rename the file and try again.") sys.exit(1) docstring, locusts = load_locustfile(locustfile) if options.list_commands: console_logger.info("Available Locusts:") for name in locusts: console_logger.info(" " + name) sys.exit(0) if not locusts: logger.error("No Locust class found!") sys.exit(1) # make sure specified Locust exists if options.locust_classes: missing = set(options.locust_classes) - set(locusts.keys()) if missing: logger.error("Unknown Locust(s): %s\n" % (", ".join(missing))) sys.exit(1) else: names = set(options.locust_classes) & set(locusts.keys()) locust_classes = [locusts[n] for n in names] else: # list() call is needed to consume the dict_view object in Python 3 locust_classes = list(locusts.values()) if options.show_task_ratio: console_logger.info("\n Task ratio per locust class") console_logger.info("-" * 80) print_task_ratio(locust_classes) console_logger.info("\n Total task ratio") console_logger.info("-" * 80) print_task_ratio(locust_classes, total=True) sys.exit(0) if options.show_task_ratio_json: from json import dumps task_data = { "per_class": get_task_ratio_dict(locust_classes), "total": get_task_ratio_dict(locust_classes, total=True) } console_logger.info(dumps(task_data)) sys.exit(0) if options.run_time: if not options.no_web: logger.error( "The --run-time argument can only be used together with --no-web" ) 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.") runners.locust_runner.quit() gevent.spawn_later(options.run_time, timelimit_stop) 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) 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: # Add -d for automatically run slaves if options.distribute: ptpy = locustfile if options.locustfile.endswith('.xls'): _type = 'xls' pt_s = PtExcel(options.locustfile) master_ip, pt_slave_info = pt_s.pt_slave() else: _type = 'dict' with open(options.locustfile, 'r') as f: _d = json.load(f, encoding='utf-8') master_ip = _d.get('master_ip') pt_slave_info = _d.get('slaves') if master_ip == '': logger.error( 'master IP cannot be None if you use --distribute') sys.exit(1) if options.boomer: locust_cli_slave = 'nohup ./client_v1 --web --master-host={masteIP} > /dev/null 2>&1 &'.format( masteIP=master_ip) targets_dict, file_list = gen_boomer_client_json( options.locustfile) boomer_client_file = os.path.join(locust_path, 'boomer_client', 'client_v1') file_list.append(boomer_client_file) thread_pool = [] try: for slave in pt_slave_info: if _type == 'xls': slave_ip, slave_username, slave_password = slave else: slave_ip, slave_username, slave_password = slave[ 'ip'], slave['username'], slave['password'] _t = Thread(target=pt_slave_boomer, args=(slave_ip, slave_username, slave_password, file_list, locust_cli_slave, targets_dict)) logger.info('Prepare slave {}'.format(slave_ip)) thread_pool.append(_t) _t.start() for each_t in thread_pool: each_t.join() file_list.pop() for each in file_list: os.remove(each) except KeyboardInterrupt: pass except Exception as e: logger.error( 'Something happened, collect Exceptions here: {}'. format(e)) else: try: locust_cli_slave = 'nohup locust -f /root/locust_client.py --slave --master-host={masteIP} > /dev/null 2>&1 &'\ .format(masteIP=master_ip) thread_pool = [] for slave in pt_slave_info: if _type == 'xls': slave_ip, slave_username, slave_password = slave else: slave_ip, slave_username, slave_password = slave[ 'ip'], slave['username'], slave['password'] _t = Thread(target=pt_slave, args=(slave_ip, slave_username, slave_password, ptpy, locust_cli_slave)) logger.info('Prepare slave {}'.format(slave_ip)) thread_pool.append(_t) _t.start() for each_t in thread_pool: each_t.join() except KeyboardInterrupt: pass except Exception as e: logger.error( 'Something happened, collect Exceptions here: {}'. format(e)) runners.locust_runner = MasterLocustRunner(locust_classes, options) else: runners.locust_runner = LocalLocustRunner(locust_classes, options) # main_greenlet is pointing to runners.locust_runner.greenlet by default, it will point the web greenlet later if in web mode main_greenlet = runners.locust_runner.greenlet if options.no_web: if options.master: while len(runners.locust_runner.clients.ready ) < options.expect_slaves: logging.info( "Waiting for slaves to be ready, %s of %s connected", len(runners.locust_runner.clients.ready), options.expect_slaves) time.sleep(1) if options.step_time: runners.locust_runner.start_stepload(options.num_clients, options.hatch_rate, options.step_clients, options.step_time) else: runners.locust_runner.start_hatching(options.num_clients, options.hatch_rate) # make locusts are spawned time.sleep(1) else: # spawn web greenlet logger.info("Starting web monitor at http://%s:%s" % (options.web_host or "*", options.port)) main_greenlet = gevent.spawn(web.start, locust_classes, options) if options.run_time: spawn_run_time_limit_greenlet() stats_printer_greenlet = None if not options.only_summary and (options.print_stats or options.no_web): # spawn stats printing greenlet stats_printer_greenlet = gevent.spawn(stats_printer) if options.csvfilebase: gevent.spawn(stats_writer, options.csvfilebase, options.stats_history_enabled) def shutdown(code=0): """ Shut down locust by firing quitting event, printing/writing stats and exiting """ 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 runners.locust_runner is not None: runners.locust_runner.quit() logger.info("Running teardowns...") events.quitting.fire(reverse=True) print_stats(runners.locust_runner.stats, current=False) print_percentile_stats(runners.locust_runner.stats) if options.csvfilebase: write_stat_csvs(options.csvfilebase, options.stats_history_enabled) print_error_report() sys.exit(code) # install SIGTERM handler def sig_term_handler(): logger.info("Got SIGTERM signal") shutdown(0) gevent.signal(signal.SIGTERM, sig_term_handler) try: logger.info("Starting Locust %s" % version) main_greenlet.join() code = 0 if len(runners.locust_runner.errors) or len( runners.locust_runner.exceptions): code = options.exit_code_on_error shutdown(code=code) except KeyboardInterrupt: shutdown(0)
def main(user_params=None, common_param=None): parser, options, arguments = parse_options() # setup logging setup_logging(options.loglevel, options.logfile) logger = logging.getLogger(__name__) if options.show_version: print("Locust %s" % (version, )) sys.exit(0) locustfile = find_locustfile(options.locustfile) if not locustfile: logger.error( "Could not find any locustfile! Ensure file ends in '.py' and see --help for available options." ) sys.exit(1) if locustfile == "locust.py": logger.error( "The locustfile must not be named `locust.py`. Please rename the file and try again." ) sys.exit(1) docstring, locusts = load_locustfile(locustfile) if options.list_commands: console_logger.info("Available Locusts:") for name in locusts: console_logger.info(" " + name) sys.exit(0) if not locusts: logger.error("No Locust class found!") sys.exit(1) # make sure specified Locust exists if arguments: missing = set(arguments) - set(locusts.keys()) if missing: logger.error("Unknown Locust(s): %s\n" % (", ".join(missing))) sys.exit(1) else: names = set(arguments) & set(locusts.keys()) locust_classes = [locusts[n] for n in names] else: # list() call is needed to consume the dict_view object in Python 3 locust_classes = list(locusts.values()) if options.show_task_ratio: console_logger.info("\n Task ratio per locust class") console_logger.info("-" * 80) print_task_ratio(locust_classes) console_logger.info("\n Total task ratio") console_logger.info("-" * 80) print_task_ratio(locust_classes, total=True) sys.exit(0) if options.show_task_ratio_json: from json import dumps task_data = { "per_class": get_task_ratio_dict(locust_classes), "total": get_task_ratio_dict(locust_classes, total=True) } console_logger.info(dumps(task_data)) sys.exit(0) if not options.no_web and not options.slave: # spawn web greenlet logger.info("Starting web monitor at %s:%s" % (options.web_host or "*", options.port)) main_greenlet = gevent.spawn(web.start, locust_classes, options) if not options.master and not options.slave: if user_params: runners.locust_runner = ParameterizableLocustRunner( locust_classes, options, user_params, common_param) else: runners.locust_runner = LocalLocustRunner(locust_classes, options) # spawn client spawning/hatching greenlet if options.no_web: runners.locust_runner.start_hatching(wait=True) main_greenlet = runners.locust_runner.greenlet elif options.master: runners.locust_runner = MasterLocustRunner(locust_classes, options) if options.no_web: while len(runners.locust_runner.clients.ready ) < options.expect_slaves: logging.info( "Waiting for slaves to be ready, %s of %s connected", len(runners.locust_runner.clients.ready), options.expect_slaves) time.sleep(1) runners.locust_runner.start_hatching(options.num_clients, options.hatch_rate) main_greenlet = runners.locust_runner.greenlet elif options.slave: try: runners.locust_runner = SlaveLocustRunner(locust_classes, options) main_greenlet = runners.locust_runner.greenlet except socket.error as e: logger.error("Failed to connect to the Locust master: %s", e) sys.exit(-1) if not options.only_summary and (options.print_stats or (options.no_web and not options.slave)): # spawn stats printing greenlet gevent.spawn(stats_printer) if options.csvfilebase: gevent.spawn(stats_writer, options.csvfilebase) def shutdown(code=0): """ Shut down locust by firing quitting event, printing/writing stats and exiting """ logger.info("Shutting down (exit code %s), bye." % code) events.quitting.fire() print_stats(runners.locust_runner.request_stats) print_percentile_stats(runners.locust_runner.request_stats) if options.csvfilebase: write_stat_csvs(options.csvfilebase) print_error_report() sys.exit(code) # install SIGTERM handler def sig_term_handler(): logger.info("Got SIGTERM signal") shutdown(0) gevent.signal(gevent.signal.SIGTERM, sig_term_handler) try: logger.info("Starting Locust %s" % version) main_greenlet.join() code = 0 if len(runners.locust_runner.errors): code = 1 shutdown(code=code) except KeyboardInterrupt as e: shutdown(0)
def start_test(self): # install SIGTERM handler def sig_term_handler(): logger.info("##### Locust plugin: Got SIGTERM signal") self.shutdown(0) gevent.signal(signal.SIGTERM, sig_term_handler) def spawn_local_slaves(count): """ Spawn *local* locust slaves : data aggregation will NOT work with *remote* slaves """ try: args = ['locust'] args.append('--locustfile={}'.format(str(self.locustfile))) args.append('--slave') args.append('--master-host={}'.format(self.master_host)) args.append('--master-port={}'.format(self.master_port)) args.append('--resplogfile={}'.format(self.locustlog_file)) logger.info("##### Locust plugin: slave args = {}".format(args)) # Spawning the slaves in shell processes (security warning with the use of 'shell=True') self._locustslaves = [subprocess.Popen(' '.join(args), shell=True, stdin=None, stdout=open('{}/locust-slave-{}.log'.format(self.core.artifacts_dir, i), 'w'), stderr=subprocess.STDOUT) for i in range(count)] #slaves = [SlaveLocustRunner(self._locustclasses, self._options) for _ in range(count)] # <-- WRONG: This will spawn slave running on the same CPU core as master time.sleep(1) logger.info("##### Locust plugin: Started {} new locust slave(s)".format(len(self._locustslaves))) logger.info("##### Locust plugin: locust slave(s) PID = {}".format(self._locustslaves)) except socket.error as e: logger.error("##### Locust plugin: Failed to connect to the Locust master: %s", e) sys.exit(-1) except Exception as e: logger.error("##### Locust plugin: Failed to spawn locust slaves: %s", e) sys.exit(-1) try: logger.info("##### Locust plugin: Starting Locust %s" % version) # run the locust ### FIXME #if self.csvfilebase: # gevent.spawn(stats_writer, self.csvfilebase) ### /FIXME if self.run_time: if not self.no_web: logger.error("##### Locust plugin: The --run-time argument can only be used together with --no-web") sys.exit(1) try: self.run_time = parse_timespan(self.run_time) except ValueError: logger.error("##### Locust plugin: Valid --time-limit formats are: 20, 20s, 3m, 2h, 1h20m, 3h30m10s, etc.") sys.exit(1) def spawn_run_time_limit_greenlet(): logger.info("##### Locust plugin: Run time limit set to %s seconds" % self.run_time) def timelimit_stop(): logger.info("##### Locust plugin: Time limit reached. Stopping Locust.") self._locustrunner.quit() logger.debug("######## DEBUG: timelimit_stop()/self._locustrunner.quit() passed") def on_greenlet_completion(): logger.debug("######## DEBUG: Locust plugin: on_greenlet_completion()") #gevent.spawn_later(self.run_time, timelimit_stop) gl = gevent.spawn_later(self.run_time, timelimit_stop) # linking timelimit greenlet to main greenlet and get a feedback of its execution #gl.link(on_greenlet_completion) # locust runner : web monitor if not self.no_web and not self.slave and self._locustrunner is None: # spawn web greenlet logger.info("##### Locust plugin: Starting web monitor at %s:%s" % (self.web_host or "*", self.port)) main_greenlet = gevent.spawn(web.start, self._locustclasses, self._options) # locust runner : standalone if not self.master and not self.slave and self._locustrunner is None: logger.info("##### Locust plugin: LocalLocustRunner about to be launched") self._locustrunner = LocalLocustRunner(self._locustclasses, self._options) # spawn client spawning/hatching greenlet if self.no_web: logger.info("##### Locust plugin: LocalLocustRunner.start_hatching()") self._locustrunner.start_hatching(wait=True) main_greenlet = self._locustrunner.greenlet if self.run_time: logger.info("##### Locust plugin: spawn_run_time_limit_greenlet()") spawn_run_time_limit_greenlet() logger.info("##### Locust plugin: spawn_run_time_limit_greenlet() passed") # locust runner : master/slave mode (master here) elif self.master and self._locustrunner is None: self._locustrunner = MasterLocustRunner(self._locustclasses, self._options) logger.info("##### Locust plugin: MasterLocustRunner started") time.sleep(1) if self.no_web: gevent.spawn(spawn_local_slaves(self.expect_slaves)) while len(self._locustrunner.clients.ready) < self.expect_slaves: logger.info("##### Locust plugin: Waiting for slaves to be ready, %s of %s connected", len(self._locustrunner.clients.ready), self.expect_slaves) time.sleep(1) self._locustrunner.start_hatching(self.num_clients, self.hatch_rate) logger.debug("######## DEBUG: MasterLocustRunner/start_hatching()") main_greenlet = self._locustrunner.greenlet if self.run_time: try: spawn_run_time_limit_greenlet() except Exception as e: logger.error("##### Locust plugin: exception raised in spawn_run_time_limit_greenlet() = {}".format(e)) # locust runner : master/slave mode (slave here) #elif self.slave and self._locustrunner is None: # if self.run_time: # logger.error("##### Locust plugin: --run-time should be specified on the master node, and not on slave nodes") # sys.exit(1) # try: # self._locustrunner = SlaveLocustRunner(self._locustclasses, self._options) # main_greenlet = self._locustrunner.greenlet # except socket.error as e: # logger.error("##### Locust plugin: Failed to connect to the Locust master: %s", e) # sys.exit(-1) return self._locustrunner self._locustrunner.greenlet.join() code = 0 if len(self._locustrunner.errors): code = 1 self.shutdown(code=code) except KeyboardInterrupt as e: self.shutdown(0)