def test_heartbeat(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.slave.Process", mocked_process()): self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) server.mocked_send( 'all', Message("worker_ready", None, "fake_client0")) self.slave.start_hatching(1, 1) sleep(runners.slave.HEARTBEAT_INTERVAL) self.assertEqual(1, len(server.outbox_all)) self.assertEqual( 'ping', Message.unserialize(server.outbox_all[0]).type) server.mocked_send('all', Message("pong", None, "fake_client0")) sleep(runners.slave.HEARTBEAT_INTERVAL) self.assertEqual(1, self.slave.worker_count) sleep(runners.slave.HEARTBEAT_INTERVAL) self.assertEqual(0, self.slave.worker_count)
def test_worker_stats_report_median(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.slave.Process", mocked_process()): self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) server.mocked_send( 'all', Message("worker_ready", None, "fake_client")) sleep(0) self.slave.start_hatching(1, 1) self.slave.stats.get("Task", "/", "GET").log(100, 23455) self.slave.stats.get("Task", "/", "GET").log(800, 23455) self.slave.stats.get("Task", "/", "GET").log(700, 23455) data = {"user_count": 1} events.report_to_master.fire(node_id="fake_client", data=data) self.slave.stats.clear_all() server.mocked_send("all", Message("stats", data, "fake_client")) sleep(0) s = self.slave.stats.get("Task", "/", "GET") self.assertEqual(700, s.median_response_time)
def test_worker_amount_spawn(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.slave.Process", mocked_process()) as processes: self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) timeout = gevent.Timeout(3.0) timeout.start() try: for i in range(5): server.mocked_send( "all", Message("worker_ready", None, "fake_client%i" % i)) self.slave.start_hatching(42, 2) except gevent.Timeout: self.fail("Got Timeout exception") finally: timeout.cancel() self.assertEqual(5, len(processes.started)) self.assertEqual(5, self.slave.worker_count)
def test_worker_connect(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.slave.Process", mocked_process()): self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) server.mocked_send( 'all', Message("worker_ready", None, "zeh_fake_client1")) sleep(0) self.slave.start_hatching(1, 1) self.assertEqual(1, self.slave.worker_count) self.assertTrue( "zeh_fake_client1" in self.slave.workers, "Could not find fake client in master instance's clients dict" ) server.mocked_send( "all", Message("worker_ready", None, "zeh_fake_client2")) server.mocked_send( "all", Message("worker_ready", None, "zeh_fake_client3")) server.mocked_send( "all", Message("worker_ready", None, "zeh_fake_client4")) sleep(0) self.assertEqual(4, self.slave.worker_count) server.mocked_send("all", Message("quit", None, "zeh_fake_client3")) sleep(0) self.assertEqual(3, self.slave.worker_count)
def test_change_user_count_during_hatching(self): class User(Locust): wait_time = constant(1) class task_set(TaskSet): @task def my_task(self): pass with mock.patch("locust.rpc.rpc.Client", mocked_rpc()) as client: options = mocked_options() options.stop_timeout = None slave = SlaveLocustRunner([User], options) client.mocked_send(Message("hatch", { "hatch_rate": 5, "num_clients": 10, "host": "", "stop_timeout": None, }, "dummy_client_id")) sleep(0.6) self.assertEqual(STATE_HATCHING, slave.state) client.mocked_send(Message("hatch", { "hatch_rate": 5, "num_clients": 9, "host": "", "stop_timeout": None, }, "dummy_client_id")) sleep(0) slave.hatching_greenlet.join() self.assertEqual(9, len(slave.locusts)) slave.quit()
def get_runner(self, environment=None, locust_classes=[]): if environment is None: environment = self.environment return SlaveLocustRunner(environment, locust_classes, master_host="localhost", master_port=5557)
def test_slave_stop_timeout(self): class MyTestLocust(Locust): _test_state = 0 class task_set(TaskSet): wait_time = constant(0) @task def the_task(self): MyTestLocust._test_state = 1 gevent.sleep(0.2) MyTestLocust._test_state = 2 with mock.patch("locust.rpc.rpc.Client", mocked_rpc()) as client: options = mocked_options() slave = SlaveLocustRunner([MyTestLocust], options) self.assertEqual(1, len(client.outbox)) self.assertEqual("client_ready", client.outbox[0].type) client.mocked_send(Message("hatch", { "hatch_rate": 1, "num_clients": 1, "host": "", "stop_timeout": 1, }, "dummy_client_id")) #print("outbox:", client.outbox) # wait for slave to hatch locusts self.assertIn("hatching", [m.type for m in client.outbox]) slave.hatching_greenlet.join() self.assertEqual(1, len(slave.locusts)) # check that locust has started running gevent.sleep(0.01) self.assertEqual(1, MyTestLocust._test_state) # send stop message client.mocked_send(Message("stop", None, "dummy_client_id")) slave.locusts.join() # check that locust user got to finish self.assertEqual(2, MyTestLocust._test_state)
def test_on_ping(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveClient", mocked_rpc_server()) as client: with mock.patch("locust.runners.slave.Process", mocked_process()) as processes: self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) sleep(0) self.assertEqual(2, len(client.outbox_all)) client.mocked_send('all', Message("ping", None, "master")) sleep(0) self.assertEqual(3, len(client.outbox_all))
def test_on_hatch(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveServer", mocked_rpc_server()) as server: with mock.patch("locust.rpc.rpc.SlaveClient", mocked_rpc_server()) as client: with mock.patch("locust.runners.slave.Process", mocked_process()) as processes: self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) for i in range(5): server.mocked_send( "all", Message("worker_ready", None, "fake_client%i" % i)) timeout = gevent.Timeout(2.0) timeout.start() try: data = { "hatch_rate": 10, "num_clients": 43, "num_requests": None, "host": 'host', "stop_timeout": None } client.mocked_send('all', Message("hatch", data, "master")) except gevent.Timeout: self.fail( "Got Timeout exception. A locust seems to have been spawned, even though 0 was specified." ) finally: timeout.cancel() sleep(0) self.assertEqual(5, len(processes.started))
def test_stats_reporting(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveServer", mocked_rpc_server()) as server: with mock.patch("locust.rpc.rpc.SlaveClient", mocked_rpc_server()) as client: with mock.patch("locust.runners.slave.Process", mocked_process()) as processes: self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) server.mocked_send( 'all', Message("worker_ready", None, "fake_client0")) self.slave.start_hatching(1, 1) self.slave.stats.get("Task", "/", "GET").log(100, 23455) self.slave.stats.get("Task", "/", "GET").log(800, 23455) self.slave.stats.get("Task", "/", "GET").log(700, 23455) sleep(runners.slave.SLAVE_STATS_INTERVAL + 0.1) messages = [ Message.unserialize(msg) for msg in client.outbox_all ] data = filter( lambda m: m.type == 'stats' and m.data['stats'], messages)[0].data self.assertEqual({ 800: 1, 100: 1, 700: 1 }, data['stats'][0]['response_times']) self.assertEqual(3, data['stats'][0]['num_requests']) self.assertEqual(1600, data['stats'][0]['total_response_time']) self.assertEqual(1, data['worker_count'])
def test_spawn_uneven_locusts(self): """ Tests that we can accurately spawn a certain number of locusts, even if it's not an even number of the connected slaves """ import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.slave.Process", mocked_process()): self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) server.mocked_send( 'all', Message("worker_ready", None, "fake_client0")) sleep(0) self.slave.start_hatching(1, 1) for i in range(1, 5): server.mocked_send( "all", Message("worker_ready", None, "fake_client%i" % i)) sleep(0) del server.outbox_direct[:] self.slave.start_hatching(42, 7) self.assertEqual(5, len(server.outbox_direct)) num_clients = 0 for msg in server.outbox_direct: num_clients += Message.unserialize( msg[1]).data["num_clients"] self.assertEqual( 42, num_clients, "Total number of locusts that would have been spawned is not 42" )
def test_worker_receive_propagated_config(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveClient", mocked_rpc_server()) as client: with mock.patch("locust.runners.slave.Process", mocked_process()) as processes: self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) sleep(0) self.assertEqual(2, len(client.outbox_all)) msg = { 'host': 'custom_host.com', 'master_host': 'new_master_host.com' } client.mocked_send('all', Message("new_config", msg, "master")) sleep(0) self.assertEqual(self.slave.options.host, 'http://custom_host.com') self.assertEqual(self.slave.options.master_host, '127.0.0.1')
def worker_relaunch(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveServer", mocked_rpc_server()) as server: with mock.patch("locust.rpc.rpc.SlaveClient", mocked_rpc_server()) as client: with mock.patch("locust.runners.slave.Process", mocked_process()) as processes: self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) server.mocked_send( 'all', Message("worker_ready", None, "fake_client0")) self.slave.start_hatching(1, 1) self.assertEqual(1, len(processes.started)) sleep(2 * runners.slave.HEARTBEAT_INTERVAL) self.assertEqual(0, self.slave.worker_count) client.mocked_send('all', Message("ping", None, "master")) sleep(runners.slave.HEARTBEAT_INTERVAL) self.assertEqual(2, len(processes.started))
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.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_new_demo = os.path.join(os.getcwd(), 'PtDemo.xls') shutil.copyfile(pt_demo_path, pt_new_demo) sys.exit(0) if options.xlsfile: pt_file = options.xlsfile if not pt_file.endswith('.xls'): logger.error( "PressureTest file must be end with '.xls' 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) make_locustfile(pt_file) logger.info('Transform XLS to locustfile finish.') 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": logger.error( "The locustfile must not be named `locust.py` or `locust.xls`. 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) if options.slave: logger.error( "--run-time should be specified on the master node, and not on slave 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.") 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) if options.slave: logger.error( "--step-time should be specified on the master node, and not on slave 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: # Add -d for automatically run slaves if options.distribute: ptpy = locustfile pt_s = PtExcel(options.locustfile) master_ip, pt_slave_info = pt_s.pt_slave() if master_ip == '': logger.error( 'master IP cannot be None if you use --distribute') sys.exit(1) try: locust_cli_slave = 'nohup locust -f /root/{locustfile} --slave --master-host={masteIP} > /dev/null 2>&1 &'.format( locustfile=ptpy, masteIP=master_ip) thread_pool = [] for slave in pt_slave_info: slave_ip, slave_username, slave_password = slave _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( 'Must something happened, collect Exceptions here: {}'. format(e)) runners.locust_runner = MasterLocustRunner(locust_classes, options) elif options.slave: try: runners.locust_runner = SlaveLocustRunner(locust_classes, options) except socket.error as e: logger.error("Failed to connect to the Locust master: %s", e) sys.exit(-1) 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) elif not options.slave: runners.locust_runner.start_hatching(options.num_clients, options.hatch_rate) # make locusts are spawned time.sleep(1) elif not options.slave: # 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 and not options.slave)): # 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 as e: shutdown(0)
class TestSlaveRunner(LocustTestCase): def setUp(self): global_stats.reset_all() self._slave_report_event_handlers = [ h for h in events.node_report._handlers ] def tearDown(self): events.node_report._handlers = self._slave_report_event_handlers self.slave.quit() def test_worker_connect(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.slave.Process", mocked_process()): self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) server.mocked_send( 'all', Message("worker_ready", None, "zeh_fake_client1")) sleep(0) self.slave.start_hatching(1, 1) self.assertEqual(1, self.slave.worker_count) self.assertTrue( "zeh_fake_client1" in self.slave.workers, "Could not find fake client in master instance's clients dict" ) server.mocked_send( "all", Message("worker_ready", None, "zeh_fake_client2")) server.mocked_send( "all", Message("worker_ready", None, "zeh_fake_client3")) server.mocked_send( "all", Message("worker_ready", None, "zeh_fake_client4")) sleep(0) self.assertEqual(4, self.slave.worker_count) server.mocked_send("all", Message("quit", None, "zeh_fake_client3")) sleep(0) self.assertEqual(3, self.slave.worker_count) def test_worker_receive_propagated_config(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveClient", mocked_rpc_server()) as client: with mock.patch("locust.runners.slave.Process", mocked_process()) as processes: self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) sleep(0) self.assertEqual(2, len(client.outbox_all)) msg = { 'host': 'custom_host.com', 'master_host': 'new_master_host.com' } client.mocked_send('all', Message("new_config", msg, "master")) sleep(0) self.assertEqual(self.slave.options.host, 'http://custom_host.com') self.assertEqual(self.slave.options.master_host, '127.0.0.1') def test_worker_stats_report_median(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.slave.Process", mocked_process()): self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) server.mocked_send( 'all', Message("worker_ready", None, "fake_client")) sleep(0) self.slave.start_hatching(1, 1) self.slave.stats.get("Task", "/", "GET").log(100, 23455) self.slave.stats.get("Task", "/", "GET").log(800, 23455) self.slave.stats.get("Task", "/", "GET").log(700, 23455) data = {"user_count": 1} events.report_to_master.fire(node_id="fake_client", data=data) self.slave.stats.clear_all() server.mocked_send("all", Message("stats", data, "fake_client")) sleep(0) s = self.slave.stats.get("Task", "/", "GET") self.assertEqual(700, s.median_response_time) def test_spawn_uneven_locusts(self): """ Tests that we can accurately spawn a certain number of locusts, even if it's not an even number of the connected slaves """ import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.slave.Process", mocked_process()): self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) server.mocked_send( 'all', Message("worker_ready", None, "fake_client0")) sleep(0) self.slave.start_hatching(1, 1) for i in range(1, 5): server.mocked_send( "all", Message("worker_ready", None, "fake_client%i" % i)) sleep(0) del server.outbox_direct[:] self.slave.start_hatching(42, 7) self.assertEqual(5, len(server.outbox_direct)) num_clients = 0 for msg in server.outbox_direct: num_clients += Message.unserialize( msg[1]).data["num_clients"] self.assertEqual( 42, num_clients, "Total number of locusts that would have been spawned is not 42" ) def test_worker_amount_spawn(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.slave.Process", mocked_process()) as processes: self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) timeout = gevent.Timeout(3.0) timeout.start() try: for i in range(5): server.mocked_send( "all", Message("worker_ready", None, "fake_client%i" % i)) self.slave.start_hatching(42, 2) except gevent.Timeout: self.fail("Got Timeout exception") finally: timeout.cancel() self.assertEqual(5, len(processes.started)) self.assertEqual(5, self.slave.worker_count) def test_heartbeat(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.slave.Process", mocked_process()): self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) server.mocked_send( 'all', Message("worker_ready", None, "fake_client0")) self.slave.start_hatching(1, 1) sleep(runners.slave.HEARTBEAT_INTERVAL) self.assertEqual(1, len(server.outbox_all)) self.assertEqual( 'ping', Message.unserialize(server.outbox_all[0]).type) server.mocked_send('all', Message("pong", None, "fake_client0")) sleep(runners.slave.HEARTBEAT_INTERVAL) self.assertEqual(1, self.slave.worker_count) sleep(runners.slave.HEARTBEAT_INTERVAL) self.assertEqual(0, self.slave.worker_count) def worker_relaunch(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveServer", mocked_rpc_server()) as server: with mock.patch("locust.rpc.rpc.SlaveClient", mocked_rpc_server()) as client: with mock.patch("locust.runners.slave.Process", mocked_process()) as processes: self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) server.mocked_send( 'all', Message("worker_ready", None, "fake_client0")) self.slave.start_hatching(1, 1) self.assertEqual(1, len(processes.started)) sleep(2 * runners.slave.HEARTBEAT_INTERVAL) self.assertEqual(0, self.slave.worker_count) client.mocked_send('all', Message("ping", None, "master")) sleep(runners.slave.HEARTBEAT_INTERVAL) self.assertEqual(2, len(processes.started)) def test_on_ping(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveClient", mocked_rpc_server()) as client: with mock.patch("locust.runners.slave.Process", mocked_process()) as processes: self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) sleep(0) self.assertEqual(2, len(client.outbox_all)) client.mocked_send('all', Message("ping", None, "master")) sleep(0) self.assertEqual(3, len(client.outbox_all)) def test_on_hatch(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveServer", mocked_rpc_server()) as server: with mock.patch("locust.rpc.rpc.SlaveClient", mocked_rpc_server()) as client: with mock.patch("locust.runners.slave.Process", mocked_process()) as processes: self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) for i in range(5): server.mocked_send( "all", Message("worker_ready", None, "fake_client%i" % i)) timeout = gevent.Timeout(2.0) timeout.start() try: data = { "hatch_rate": 10, "num_clients": 43, "num_requests": None, "host": 'host', "stop_timeout": None } client.mocked_send('all', Message("hatch", data, "master")) except gevent.Timeout: self.fail( "Got Timeout exception. A locust seems to have been spawned, even though 0 was specified." ) finally: timeout.cancel() sleep(0) self.assertEqual(5, len(processes.started)) def test_stats_reporting(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.SlaveServer", mocked_rpc_server()) as server: with mock.patch("locust.rpc.rpc.SlaveClient", mocked_rpc_server()) as client: with mock.patch("locust.runners.slave.Process", mocked_process()) as processes: self.slave = SlaveLocustRunner(MyTestLocust, config.locust_config()) server.mocked_send( 'all', Message("worker_ready", None, "fake_client0")) self.slave.start_hatching(1, 1) self.slave.stats.get("Task", "/", "GET").log(100, 23455) self.slave.stats.get("Task", "/", "GET").log(800, 23455) self.slave.stats.get("Task", "/", "GET").log(700, 23455) sleep(runners.slave.SLAVE_STATS_INTERVAL + 0.1) messages = [ Message.unserialize(msg) for msg in client.outbox_all ] data = filter( lambda m: m.type == 'stats' and m.data['stats'], messages)[0].data self.assertEqual({ 800: 1, 100: 1, 700: 1 }, data['stats'][0]['response_times']) self.assertEqual(3, data['stats'][0]['num_requests']) self.assertEqual(1600, data['stats'][0]['total_response_time']) self.assertEqual(1, data['worker_count'])
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)