def test_spawn_locusts_in_stepload_mode(self): class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.Server", mocked_rpc()) as server: master = MasterLocustRunner([MyTestLocust], self.options) for i in range(5): server.mocked_send(Message("client_ready", None, "fake_client%i" % i)) # start a new swarming in Step Load mode: total locust count of 10, hatch rate of 2, step locust count of 5, step duration of 5s master.start_stepload(10, 2, 5, 5) # make sure the first step run is started sleep(1) self.assertEqual(5, len(server.outbox)) num_clients = 0 end_of_last_step = len(server.outbox) for _, msg in server.outbox: num_clients += msg.data["num_clients"] self.assertEqual(5, num_clients, "Total number of locusts that would have been spawned for first step is not 5") # make sure the first step run is complete sleep(5) num_clients = 0 idx = end_of_last_step while idx < len(server.outbox): msg = server.outbox[idx][1] num_clients += msg.data["num_clients"] idx += 1 self.assertEqual(10, num_clients, "Total number of locusts that would have been spawned for second step is not 10")
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 """ class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.Server", mocked_rpc_server()) as server: master = MasterLocustRunner(MyTestLocust, self.options) for i in range(5): server.mocked_send( Message("client_ready", None, "fake_client%i" % i)) master.start_hatching(7, 7) self.assertEqual(5, len(server.outbox)) num_clients = 0 for _, msg in server.outbox: num_clients += Message.unserialize(msg).data["num_clients"] self.assertEqual( 7, num_clients, "Total number of locusts that would have been spawned is not 7" )
def test_spawn_fewer_locusts_than_slaves(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.Server", mocked_rpc_server()) as server: master = MasterLocustRunner(MyTestLocust, self.options) for i in range(5): server.mocked_send( Message("client_ready", None, "fake_client%i" % i)) sleep(0) master.start_hatching(2, 2) self.assertEqual(5, len(server.outbox)) num_clients = 0 for msg in server.outbox: num_clients += Message.unserialize(msg).data["num_clients"] self.assertEqual( 2, num_clients, "Total number of locusts that would have been spawned is not 2" )
def test_heartbeat(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.MasterServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.master.Process", mocked_process()): server.mocked_send( 'all', Message("slave_ready", None, "fake_client0")) self.master = MasterLocustRunner(MyTestLocust, config.locust_config()) sleep(0) 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.master.HEARTBEAT_INTERVAL) self.assertEqual(1, self.master.slave_count) sleep(runners.master.HEARTBEAT_INTERVAL) self.assertEqual(0, self.master.slave_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.MasterServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.master.Process", mocked_process()): server.mocked_send( 'all', Message("slave_ready", None, "fake_client0")) self.master = MasterLocustRunner(MyTestLocust, config.locust_config()) for i in range(1, 5): server.mocked_send( "all", Message("slave_ready", None, "fake_client%i" % i)) sleep(0) server.outbox_direct = [] self.master.start_hatching(7, 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( 7, num_clients, "Total number of locusts that would have been spawned is not 7" )
def test_spawn_fewer_locusts_than_slaves(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.MasterServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.master.Process", mocked_process()): server.mocked_send( 'all', Message("slave_ready", None, "fake_client0")) self.master = MasterLocustRunner(MyTestLocust, config.locust_config()) for i in range(1, 5): server.mocked_send( 'all', Message("slave_ready", None, "fake_client%i" % i)) sleep(0) server.outbox_direct = [] self.master.start_hatching(2, 2) 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( 2, num_clients, "Total number of locusts that would have been spawned is not 2" )
def test_slave_stats_report_median(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.MasterServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.master.Process", mocked_process()): server.mocked_send('all', Message("slave_ready", None, "fake_client")) self.master = MasterLocustRunner(MyTestLocust, config.locust_config()) sleep(0) self.master.stats.get("Task", "/", "GET").log(100, 23455) self.master.stats.get("Task", "/", "GET").log(800, 23455) self.master.stats.get("Task", "/", "GET").log(700, 23455) data = {"user_count": 1, "worker_count": 1} events.report_to_master.fire(node_id="fake_client", data=data) self.master.stats.clear_all() server.mocked_send("all", Message("stats", data, "fake_client")) sleep(0) s = self.master.stats.get("Task", "/", "GET") self.assertEqual(700, s.median_response_time)
def test_on_demand_options_propagate(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.MasterServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.master.Process", mocked_process()): server.mocked_send( 'all', Message("slave_ready", None, "zeh_fake_client1")) self.master = MasterLocustRunner(MyTestLocust, config.locust_config()) sleep(0) self.assertEqual(1, self.master.slave_count) server.outbox_all = [] config_upd = { 'host': 'custom_host.com', 'master_host': 'new_master_host.com' } self.master.propagate_config(config_upd) self.assertEqual(1, len(server.outbox_all)) msg = Message.unserialize(server.outbox_all[0]).data self.assertEqual(msg['host'], 'custom_host.com') self.assertEqual(msg['master_host'], '127.0.0.1')
def test_rebalance_locust_users_on_slave_connect(self): class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.Server", mocked_rpc()) as server: master = MasterLocustRunner(MyTestLocust, self.options) server.mocked_send(Message("client_ready", None, "zeh_fake_client1")) self.assertEqual(1, len(master.clients)) self.assertTrue("zeh_fake_client1" in master.clients, "Could not find fake client in master instance's clients dict") master.start_hatching(100, 20) self.assertEqual(1, len(server.outbox)) client_id, msg = server.outbox.pop() self.assertEqual(100, msg.data["num_clients"]) self.assertEqual(20, msg.data["hatch_rate"]) # let another slave connect server.mocked_send(Message("client_ready", None, "zeh_fake_client2")) self.assertEqual(2, len(master.clients)) self.assertEqual(2, len(server.outbox)) client_id, msg = server.outbox.pop() self.assertEqual(50, msg.data["num_clients"]) self.assertEqual(10, msg.data["hatch_rate"]) client_id, msg = server.outbox.pop() self.assertEqual(50, msg.data["num_clients"]) self.assertEqual(10, msg.data["hatch_rate"])
def test_slave_connect(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.MasterServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.master.Process", mocked_process()): server.mocked_send( 'all', Message("slave_ready", None, "zeh_fake_client1")) self.master = MasterLocustRunner(MyTestLocust, config.locust_config()) sleep(0) self.assertEqual(1, self.master.slave_count) self.assertTrue( "zeh_fake_client1" in self.master.slaves, "Could not find fake client in master instance's clients dict" ) server.mocked_send( "all", Message("slave_ready", None, "zeh_fake_client2")) server.mocked_send( "all", Message("slave_ready", None, "zeh_fake_client3")) server.mocked_send( "all", Message("slave_ready", None, "zeh_fake_client4")) sleep(0) self.assertEqual(4, self.master.slave_count) server.mocked_send("all", Message("quit", None, "zeh_fake_client3")) sleep(0) self.assertEqual(3, self.master.slave_count)
def test_csv_stats_on_master_from_aggregated_stats(self): # Failing test for: https://github.com/locustio/locust/issues/1315 with mock.patch("locust.rpc.rpc.Server", mocked_rpc()) as server: master = MasterLocustRunner(self.environment, [], master_bind_host="*", master_bind_port=0) server.mocked_send(Message("client_ready", None, "fake_client")) master.stats.get("/", "GET").log(100, 23455) master.stats.get("/", "GET").log(800, 23455) master.stats.get("/", "GET").log(700, 23455) data = {"user_count": 1} self.environment.events.report_to_master.fire( client_id="fake_client", data=data) master.stats.clear_all() server.mocked_send(Message("stats", data, "fake_client")) s = master.stats.get("/", "GET") self.assertEqual(700, s.median_response_time) locust.stats.write_csv_files(master.stats, self.STATS_BASE_NAME, full_history=True) self.assertTrue(os.path.exists(self.STATS_FILENAME)) self.assertTrue(os.path.exists(self.STATS_HISTORY_FILENAME)) self.assertTrue(os.path.exists(self.STATS_FAILURES_FILENAME))
def test_drone_stats_report_with_none_response_times(self): class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.Server", mocked_rpc()) as server: master = MasterLocustRunner(MyTestLocust, self.options) server.mocked_send(Message("client_ready", None, "fake_client")) master.stats.get("/mixed", "GET").log(0, 23455) master.stats.get("/mixed", "GET").log(800, 23455) master.stats.get("/mixed", "GET").log(700, 23455) master.stats.get("/mixed", "GET").log(None, 23455) master.stats.get("/mixed", "GET").log(None, 23455) master.stats.get("/mixed", "GET").log(None, 23455) master.stats.get("/mixed", "GET").log(None, 23455) master.stats.get("/onlyNone", "GET").log(None, 23455) data = {"user_count":1} events.report_to_master.fire(client_id="fake_client", data=data) master.stats.clear_all() server.mocked_send(Message("stats", data, "fake_client")) s1 = master.stats.get("/mixed", "GET") self.assertEqual(700, s1.median_response_time) self.assertEqual(500, s1.avg_response_time) s2 = master.stats.get("/onlyNone", "GET") self.assertEqual(0, s2.median_response_time) self.assertEqual(0, s2.avg_response_time)
def test_master_total_stats_with_none_response_times(self): class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.Server", mocked_rpc()) as server: master = MasterLocustRunner(MyTestLocust, self.options) server.mocked_send(Message("client_ready", None, "fake_client")) stats = RequestStats() stats.log_request("GET", "/1", 100, 3546) stats.log_request("GET", "/1", 800, 56743) stats.log_request("GET", "/1", None, 56743) stats2 = RequestStats() stats2.log_request("GET", "/2", 700, 2201) stats2.log_request("GET", "/2", None, 2201) stats3 = RequestStats() stats3.log_request("GET", "/3", None, 2201) server.mocked_send(Message("stats", { "stats":stats.serialize_stats(), "stats_total": stats.total.serialize(), "errors":stats.serialize_errors(), "user_count": 1, }, "fake_client")) server.mocked_send(Message("stats", { "stats":stats2.serialize_stats(), "stats_total": stats2.total.serialize(), "errors":stats2.serialize_errors(), "user_count": 2, }, "fake_client")) server.mocked_send(Message("stats", { "stats":stats3.serialize_stats(), "stats_total": stats3.total.serialize(), "errors":stats3.serialize_errors(), "user_count": 2, }, "fake_client")) self.assertEqual(700, master.stats.total.median_response_time)
def test_slave_connect(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.Server", mocked_rpc_server()) as server: master = MasterLocustRunner(MyTestLocust, self.options) server.mocked_send( Message("client_ready", None, "zeh_fake_client1")) sleep(0) self.assertEqual(1, len(master.clients)) self.assertTrue( "zeh_fake_client1" in master.clients, "Could not find fake client in master instance's clients dict") server.mocked_send( Message("client_ready", None, "zeh_fake_client2")) server.mocked_send( Message("client_ready", None, "zeh_fake_client3")) server.mocked_send( Message("client_ready", None, "zeh_fake_client4")) sleep(0) self.assertEqual(4, len(master.clients)) server.mocked_send(Message("quit", None, "zeh_fake_client3")) sleep(0) self.assertEqual(3, len(master.clients))
def test_spawn_fewer_locusts_than_slaves(self): class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.Server", mocked_rpc_server()) as server: master = MasterLocustRunner(MyTestLocust, self.options) for i in range(5): server.mocked_send(Message("client_ready", None, "fake_client%i" % i)) master.start_hatching(2, 2) self.assertEqual(5, len(server.outbox)) num_clients = 0 for _, msg in server.outbox: num_clients += Message.unserialize(msg).data["num_clients"] self.assertEqual(2, num_clients, "Total number of locusts that would have been spawned is not 2")
def test_automatic_options_propagate(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.MasterServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.master.Process", mocked_process()): server.mocked_send( 'all', Message("slave_ready", None, "zeh_fake_client1")) self.master = MasterLocustRunner(MyTestLocust, config.locust_config()) sleep(0) self.assertEqual(1, self.master.slave_count) self.assertEqual(1, len(server.outbox_direct)) msg = Message.unserialize(server.outbox_direct[0][1]).data self.assertEqual(self.master.options._config, msg)
def launch(classname, n_clients, run_time=180): """ Launches the tests :param: classname: class inherited from HttpLocust defining the test :param: n_clients: Number of concurrent users :param: run_time: Stop testing after the specified amount of seconds """ base_url = os.environ.get('KOLIBRI_BASE_URL', 'http://127.0.0.1:8000') options = Namespace(**{ 'host': base_url, 'num_clients': n_clients, 'hatch_rate': n_clients, # this way it will be 1 request per second per client 'num_requests': 9999999, # obsolete, discontinued in new locust versions 'run_time': run_time, 'no_web': True, 'no_reset_stats': True, 'csvfilebase': os.path.join(get_or_create_output_dir(), get_csv_filename()), 'expect_slaves': 1, 'master_host': '127.0.0.1', 'master_port': 5557, 'master_bind_host': '*', 'master_bind_port': 5557 }) setup_logging('INFO', None) if classname.__module__ == '__main__': test_path = 'scenarios/{}.py'.format(get_test_calling()) else: test_path = 'scenarios/{}.py'.format(classname.__module__) locust_executable = spawn.find_executable('locust') slave_args = [locust_executable, '--slave', '-f', test_path] for slave in range(n_clients): subprocess.Popen(slave_args, env={'KOLIBRI_BASE_URL': base_url}) if '127.0.0.1' in base_url or 'localhost' in base_url: time.sleep(1) else: time.sleep(5) runners.locust_runner = MasterLocustRunner([classname], options) while len(runners.locust_runner.clients.ready) < options.expect_slaves: time.sleep(1) # spawn client spawning/hatching greenlets: runners.locust_runner.start_hatching(options.num_clients, options.hatch_rate) main_greenlet = runners.locust_runner.greenlet spawn_run_time_limit_greenlet(options) if options.csvfilebase: gevent.spawn(stats_writer, options.csvfilebase) try: main_greenlet.join() code = 0 if len(runners.locust_runner.errors): code = 1 shutdown(options, code=code) except KeyboardInterrupt: shutdown(options, 0)
def test_master_marks_downed_drones_as_missing(self): class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.Server", mocked_rpc()) as server: master = MasterLocustRunner(MyTestLocust, self.options) server.mocked_send(Message("client_ready", None, "fake_client")) sleep(0.1) # print(master.clients['fake_client'].__dict__) assert master.clients['fake_client'].state == STATE_MISSING
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 """ class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.Server", mocked_rpc_server()) as server: master = MasterLocustRunner(MyTestLocust, self.options) for i in range(5): server.mocked_send(Message("client_ready", None, "fake_client%i" % i)) master.start_hatching(7, 7) self.assertEqual(5, len(server.outbox)) num_clients = 0 for _, msg in server.outbox: num_clients += Message.unserialize(msg).data["num_clients"] self.assertEqual(7, num_clients, "Total number of locusts that would have been spawned is not 7")
def _run_locust(locust_classes, master): config_options(master=master) print('*** Starting locust: {}:{} ***'.format(options.web_host, options.port)) if options.master: runners.locust_runner = MasterLocustRunner(locust_classes, options) else: runners.locust_runner = LocalLocustRunner(locust_classes, options) logging.info("Starting web monitor at http://%s:%s" % (options.web_host or "0.0.0.0", options.port)) main_greenlet = gevent.spawn(locust_web.start, locust_classes, options) stats_printer_greenlet = None def shutdown(code=0): """ Shut down locust by firing quitting event, printing/writing stats and exiting """ logging.info("Shutting down (exit code %s), bye." % code) if stats_printer_greenlet is not None: stats_printer_greenlet.kill(block=False) logging.info("Cleaning up runner...") if runners.locust_runner is not None: runners.locust_runner.quit() logging.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(): logging.info("Got SIGTERM signal") shutdown(0) gevent.signal_handler(signal.SIGTERM, sig_term_handler) try: logging.info("Starting Locust...") main_greenlet.join() code = 0 lr = runners.locust_runner if len(lr.errors) or len(lr.exceptions) or lr.cpu_log_warning(): code = options.exit_code_on_error shutdown(code=code) except KeyboardInterrupt: shutdown(0)
def test_master_current_response_times(self): class MyTestLocust(Locust): pass start_time = 1 with mock.patch("time.time") as mocked_time: mocked_time.return_value = start_time global_stats.reset_all() with mock.patch("locust.rpc.rpc.Server", mocked_rpc()) as server: master = MasterLocustRunner(MyTestLocust, self.options) mocked_time.return_value += 1.0234 server.mocked_send(Message("client_ready", None, "fake_client")) stats = RequestStats() stats.log_request("GET", "/1", 100, 3546) stats.log_request("GET", "/1", 800, 56743) server.mocked_send(Message("stats", { "stats":stats.serialize_stats(), "stats_total": stats.total.get_stripped_report(), "errors":stats.serialize_errors(), "user_count": 1, }, "fake_client")) mocked_time.return_value += 1 stats2 = RequestStats() stats2.log_request("GET", "/2", 400, 2201) server.mocked_send(Message("stats", { "stats":stats2.serialize_stats(), "stats_total": stats2.total.get_stripped_report(), "errors":stats2.serialize_errors(), "user_count": 2, }, "fake_client")) mocked_time.return_value += 4 self.assertEqual(400, master.stats.total.get_current_response_time_percentile(0.5)) self.assertEqual(800, master.stats.total.get_current_response_time_percentile(0.95)) # let 10 second pass, do some more requests, send it to the master and make # sure the current response time percentiles only accounts for these new requests mocked_time.return_value += 10.10023 stats.log_request("GET", "/1", 20, 1) stats.log_request("GET", "/1", 30, 1) stats.log_request("GET", "/1", 3000, 1) server.mocked_send(Message("stats", { "stats":stats.serialize_stats(), "stats_total": stats.total.get_stripped_report(), "errors":stats.serialize_errors(), "user_count": 2, }, "fake_client")) self.assertEqual(30, master.stats.total.get_current_response_time_percentile(0.5)) self.assertEqual(3000, master.stats.total.get_current_response_time_percentile(0.95))
def setUp(self): super(TestWebUI, self).setUp() stats.global_stats.clear_all() # parser = parse_options()[0] # options = parser.parse_args([])[0] runners.main = MasterLocustRunner([], config.locust_config()) web.request_stats.clear_cache() self._web_ui_server = wsgi.WSGIServer(('127.0.0.1', 0), web.app, log=None) gevent.spawn(lambda: self._web_ui_server.serve_forever()) gevent.sleep(0.5) self.web_port = self._web_ui_server.server_port
def test_distributed_integration_run(self): """ Full integration test that starts both a MasterLocustRunner and three WorkerLocustRunner instances and makes sure that their stats is sent to the Master. """ class TestUser(Locust): wait_time = constant(0.1) @task def incr_stats(l): l.environment.events.request_success.fire( request_type="GET", name="/", response_time=1337, response_length=666, ) with mock.patch("locust.runners.WORKER_REPORT_INTERVAL", new=0.3): # start a Master runner master_env = Environment() master = MasterLocustRunner(master_env, [TestUser], master_bind_host="*", master_bind_port=0) sleep(0) # start 3 Worker runners workers = [] for i in range(3): worker_env = Environment() worker = WorkerLocustRunner(worker_env, [TestUser], master_host="127.0.0.1", master_port=master.server.port) workers.append(worker) # give workers time to connect sleep(0.1) # issue start command that should trigger TestUsers to be spawned in the Workers master.start(6, hatch_rate=1000) sleep(0.1) # check that slave nodes have started locusts for worker in workers: self.assertEqual(2, worker.user_count) # give time for users to generate stats, and stats to be sent to master sleep(1) master.quit() # make sure users are killed for worker in workers: self.assertEqual(0, worker.user_count) # check that stats are present in master self.assertGreater( master_env.runner.stats.total.num_requests, 20, "For some reason the master node's stats has not come in", )
def test_sends_hatch_data_to_ready_running_hatching_drones(self): '''Sends hatch job to running, ready, or hatching drones''' class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.Server", mocked_rpc()) as server: master = MasterLocustRunner(MyTestLocust, self.options) master.clients[1] = DroneNode(1) master.clients[2] = DroneNode(2) master.clients[3] = DroneNode(3) master.clients[1].state = STATE_INIT master.clients[2].state = STATE_HATCHING master.clients[3].state = STATE_RUNNING master.start_hatching(5,5) self.assertEqual(3, len(server.outbox))
def test_slave_stats_report_median(self): class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.Server", mocked_rpc_server()) as server: master = MasterLocustRunner(MyTestLocust, self.options) server.mocked_send(Message("client_ready", None, "fake_client")) master.stats.get("/", "GET").log(100, 23455) master.stats.get("/", "GET").log(800, 23455) master.stats.get("/", "GET").log(700, 23455) data = {"user_count": 1} events.report_to_master.fire(client_id="fake_client", data=data) master.stats.clear_all() server.mocked_send(Message("stats", data, "fake_client")) s = master.stats.get("/", "GET") self.assertEqual(700, s.median_response_time)
def test_sends_hatch_data_to_ready_running_hatching_slaves(self): '''Sends hatch job to running, ready, or hatching slaves''' class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.Server", mocked_rpc_server()) as server: master = MasterLocustRunner(MyTestLocust, self.options) master.clients[1] = SlaveNode(1) master.clients[2] = SlaveNode(2) master.clients[3] = SlaveNode(3) master.clients[1].state = STATE_INIT master.clients[2].state = STATE_HATCHING master.clients[3].state = STATE_RUNNING master.start_hatching(5,5) self.assertEqual(3, len(server.outbox))
class TestMasterRunner(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.master.quit() def test_slave_connect(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.MasterServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.master.Process", mocked_process()): server.mocked_send( 'all', Message("slave_ready", None, "zeh_fake_client1")) self.master = MasterLocustRunner(MyTestLocust, config.locust_config()) sleep(0) self.assertEqual(1, self.master.slave_count) self.assertTrue( "zeh_fake_client1" in self.master.slaves, "Could not find fake client in master instance's clients dict" ) server.mocked_send( "all", Message("slave_ready", None, "zeh_fake_client2")) server.mocked_send( "all", Message("slave_ready", None, "zeh_fake_client3")) server.mocked_send( "all", Message("slave_ready", None, "zeh_fake_client4")) sleep(0) self.assertEqual(4, self.master.slave_count) server.mocked_send("all", Message("quit", None, "zeh_fake_client3")) sleep(0) self.assertEqual(3, self.master.slave_count) def test_automatic_options_propagate(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.MasterServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.master.Process", mocked_process()): server.mocked_send( 'all', Message("slave_ready", None, "zeh_fake_client1")) self.master = MasterLocustRunner(MyTestLocust, config.locust_config()) sleep(0) self.assertEqual(1, self.master.slave_count) self.assertEqual(1, len(server.outbox_direct)) msg = Message.unserialize(server.outbox_direct[0][1]).data self.assertEqual(self.master.options._config, msg) def test_on_demand_options_propagate(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.MasterServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.master.Process", mocked_process()): server.mocked_send( 'all', Message("slave_ready", None, "zeh_fake_client1")) self.master = MasterLocustRunner(MyTestLocust, config.locust_config()) sleep(0) self.assertEqual(1, self.master.slave_count) server.outbox_all = [] config_upd = { 'host': 'custom_host.com', 'master_host': 'new_master_host.com' } self.master.propagate_config(config_upd) self.assertEqual(1, len(server.outbox_all)) msg = Message.unserialize(server.outbox_all[0]).data self.assertEqual(msg['host'], 'custom_host.com') self.assertEqual(msg['master_host'], '127.0.0.1') def test_slave_stats_report_median(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.MasterServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.master.Process", mocked_process()): server.mocked_send('all', Message("slave_ready", None, "fake_client")) self.master = MasterLocustRunner(MyTestLocust, config.locust_config()) sleep(0) self.master.stats.get("Task", "/", "GET").log(100, 23455) self.master.stats.get("Task", "/", "GET").log(800, 23455) self.master.stats.get("Task", "/", "GET").log(700, 23455) data = {"user_count": 1, "worker_count": 1} events.report_to_master.fire(node_id="fake_client", data=data) self.master.stats.clear_all() server.mocked_send("all", Message("stats", data, "fake_client")) sleep(0) s = self.master.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.MasterServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.master.Process", mocked_process()): server.mocked_send( 'all', Message("slave_ready", None, "fake_client0")) self.master = MasterLocustRunner(MyTestLocust, config.locust_config()) for i in range(1, 5): server.mocked_send( "all", Message("slave_ready", None, "fake_client%i" % i)) sleep(0) server.outbox_direct = [] self.master.start_hatching(7, 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( 7, num_clients, "Total number of locusts that would have been spawned is not 7" ) def test_spawn_fewer_locusts_than_slaves(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.MasterServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.master.Process", mocked_process()): server.mocked_send( 'all', Message("slave_ready", None, "fake_client0")) self.master = MasterLocustRunner(MyTestLocust, config.locust_config()) for i in range(1, 5): server.mocked_send( 'all', Message("slave_ready", None, "fake_client%i" % i)) sleep(0) server.outbox_direct = [] self.master.start_hatching(2, 2) 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( 2, num_clients, "Total number of locusts that would have been spawned is not 2" ) def test_heartbeat(self): import mock class MyTestLocust(Locust): pass with mock.patch("locust.rpc.rpc.MasterServer", mocked_rpc_server()) as server: with mock.patch("locust.runners.master.Process", mocked_process()): server.mocked_send( 'all', Message("slave_ready", None, "fake_client0")) self.master = MasterLocustRunner(MyTestLocust, config.locust_config()) sleep(0) 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.master.HEARTBEAT_INTERVAL) self.assertEqual(1, self.master.slave_count) sleep(runners.master.HEARTBEAT_INTERVAL) self.assertEqual(0, self.master.slave_count)
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)
class Plugin(AbstractPlugin, GeneratorPlugin): """ Locust tank plugin """ SECTION = 'locust' def __init__(self, core, cfg, cfg_updater): AbstractPlugin.__init__(self, core, cfg, cfg_updater) self.core = core self._locustrunner = None self._locustclasses = None self._options = None self._user_count = 0 self._state = '' self._locuststats = '' self._locustslaves = None self.stats_reader = None self.reader = None self.host = None self.web_host = '' self.port = 8089 self.locustfile = 'locustfile' self.master = False self.slave = False self.master_host = "127.0.0.1" self.master_port = 5557 self.master_bind_host = "*" self.master_bind_port = 5557 self.expect_slaves = 0 self.no_web = True self.num_clients = int(1) self.hatch_rate = float(1) self.num_requests = None self.run_time = None self.loglevel = 'INFO' self.logfile = None self.csvfilebase = None self.csvappend = True self.print_stats = True self.only_summary = True self.list_commands = False self.show_task_ratio = False self.show_task_ratio_json = False self.show_version = True self.locustlog_level = 'INFO' self.cfg = cfg # setup logging ll.setup_logging(self.loglevel, self.logfile) @property def locustlog_file(self): logger.debug("######## DEBUG: self.core.artifacts_dir = {}".format(self.core.artifacts_dir)) return "{}/locust.log".format(self.core.artifacts_dir) def get_available_options(self): return [ "host", "port", "locustfile", "num_clients", "hatch_rate", "run_time", #"num_requests", "logfile", "loglevel", "csvfilebase", "master", "master_bind_host", "master_bind_port", "expect_slaves", "master_host", "master_port" ] def _get_variables(self): res = {} for option in self.core.config.get_options(self.SECTION): if option[0] not in self.get_available_options(): res[option[0]] = option[1] logger.debug("Variables: %s", res) return res def get_reader(self): if self.reader is None: self.reader = LocustReader(self, self.locustlog_file) return self.reader def get_stats_reader(self): if self.stats_reader is None: self.stats_reader = self.reader.stats_reader logger.debug("######## DEBUG: plugin.reader.stats_reader.source = %s" % self.stats_reader.source) return self.stats_reader def configure(self): self.host = self.get_option("host") self.port = self.get_option("port") self.locustfile = self.get_option("locustfile") self.num_clients = int(self.get_option ("num_clients")) self.hatch_rate = float(self.get_option("hatch_rate")) self.run_time = self.get_option("run_time") self.logfile = self.get_option("logfile") self.loglevel = self.get_option("loglevel") self.csvfilebase = self.get_option("csvfilebase") self.locustlog_level = self.get_option("locustlog_level") self.show_version = True self.master = self.get_option("master") self.master_bind_host = self.get_option("master_bind_host") self.master_bind_port = self.get_option("master_bind_port") self.expect_slaves = self.get_option("expect_slaves") self.master_host = self.get_option("master_host") self.master_port = self.get_option("master_port") if self.locustlog_file: logger.debug("######## DEBUG: configuring Locust resplog") ll.setup_resplogging(self.locustlog_level, self.locustlog_file) def get_options(self): options = {optname : self.__getattribute__(optname) for optname in self.get_available_options()} logger.debug("##### Locust plugin: get_options() : options = {}".format(options)) return options def prepare_test(self): logger = logging.getLogger(__name__) try: logger.debug("######## DEBUG: looking for a console object") ### DEBUG: enable/disable Console console = self.core.get_plugin_of_type(ConsolePlugin) except Exception as ex: logger.debug("######## DEBUG: Console not found: %s", ex) console = None if console: logger.debug("######## DEBUG: console found") widget = LocustInfoWidget(self) console.add_info_widget(widget) logger.debug("######## DEBUG: locust widget added to console") try: locustfile = lm.find_locustfile(self.locustfile) if not locustfile: logger.error("##### Locust plugin: 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("##### Locust plugin: The locustfile must not be named `locust.py`. Please rename the file and try again.") sys.exit(1) docstring, locusts = lm.load_locustfile(locustfile) logger.info("##### Locust plugin: locustfile = {}".format(locustfile)) if not locusts: logger.error("##### Locust plugin: No Locust class found!") sys.exit(1) else: logger.info("##### Locust plugin: Locust classes found in {} : {}".format(locustfile, locusts)) self._locustclasses = list(locusts.values()) options = Opts(**self.get_options()) self._options = options logger.debug("##### Locust plugin: main() : options = {}".format(options)) except Exception as e: logger.error("##### Locust plugin: prepare_test() CRITICAL ERROR : %s", e) sys.exit(1) def is_any_slave_up(self): if self.master and self._locustslaves is not None: poll_slaves = [s.poll() for s in self._locustslaves] res = any([False if x is not None else True for x in poll_slaves]) logger.debug("######## DEBUG: is_any_slave_up/any(res) = {}".format(res)) return res elif self.master: logger.error("##### Locust plugin: no slave alive to poll") return False else: return False 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) def shutdown(self, code=0): """ Shut down locust by firing quitting event, printing stats and exiting """ logger.debug("######## DEBUG: shutdown()/_locustrunner = {}".format(self._locustrunner)) logger.info("##### Locust plugin: Cleaning up runner...") if self._locustrunner is not None and self.is_any_slave_up(): #if self.csvfilebase: # write_stat_csvs(self.csvfilebase) retcode = self._locustrunner.quit() logger.debug("######## DEBUG: shutdown()/_locustrunner.quit() passed # retcode = {}".format(retcode)) logger.info("##### Locust plugin: Running teardowns...") while not self.reader.is_stat_queue_empty(): logger.info("##### Locust plugin: {} items remaining is stats queue".format(self.reader.stat_queue.qsize())) time.sleep(1) ### FIXME : possibly causing a greenlet looping infinitely #events.quitting.fire(reverse=True) print_stats(self._locustrunner.request_stats) print_percentile_stats(self._locustrunner.request_stats) print_error_report() self.reader.close() logger.info("##### Locust plugin: Shutting down (exit code %s), bye." % code) def is_test_finished(self): """ Fetch locustrunner stats: min/max/median/avg response time, current RPS, fail ratio """ if self._locustrunner: self._locuststats = self._locustrunner.stats.total """ Fetch locustrunner status: 'ready', 'hatching', 'running', 'stopped' and returns status code """ logger.debug("######## DEBUG: is_test_finished()? -> Fetching locust status") logger.debug("######## DEBUG: is_test_finished() -> self._locustrunner.state = {}".format(self._locustrunner.state)) logger.debug("######## DEBUG: is_test_finished() -> is_any_slave_up() = {}".format(self.is_any_slave_up())) self._state = self._locustrunner.state if self._locustrunner.state == 'stopped' or self.master and not self.is_any_slave_up(): self._user_count = 0 return 0 else: self._user_count = self._locustrunner.user_count return -1 def end_test(self, retcode): if self.is_test_finished() < 0: logger.info("##### Locust plugin: Terminating Locust") self.shutdown(retcode) else: logger.info("##### Locust plugin: Locust has been terminated already") self.shutdown(retcode) return retcode
def get_runner(self): return MasterLocustRunner(self.environment, [], master_bind_host="*", master_bind_port=5557)
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(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 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)