def test_return_docstring_and_user_classes(self): with mock_locustfile() as mocked: docstring, user_classes, shape_class = main.load_locustfile(mocked.file_path) self.assertEqual("This is a mock locust file for unit testing", docstring) self.assertIn("UserSubclass", user_classes) self.assertNotIn("NotUserSubclass", user_classes) self.assertNotIn("LoadTestShape", user_classes)
def test_load_locust_file_from_absolute_path(self): with mock_locustfile() as mocked: docstring, user_classes, shape_class = main.load_locustfile( mocked.file_path) self.assertIn('UserSubclass', user_classes) self.assertNotIn('NotUserSubclass', user_classes) self.assertNotIn('LoadTestShape', user_classes)
def test_return_docstring_and_locusts(self): with mock_locustfile() as mocked: docstring, locusts = main.load_locustfile(mocked.file_path) self.assertEqual("This is a mock locust file for unit testing", docstring) self.assertIn('LocustSubclass', locusts) self.assertNotIn('NotLocustSubclass', locusts)
def test_with_shape_class(self): content = MOCK_LOUCSTFILE_CONTENT + '''class LoadTestShape(LoadTestShape): pass ''' with mock_locustfile(content=content) as mocked: docstring, user_classes, shape_class = main.load_locustfile( mocked.file_path) self.assertEqual("This is a mock locust file for unit testing", docstring) self.assertIn('UserSubclass', user_classes) self.assertNotIn('NotUserSubclass', user_classes) self.assertEqual(shape_class.__class__.__name__, 'LoadTestShape')
def test_with_shape_class(self): content = ( MOCK_LOCUSTFILE_CONTENT + """class LoadTestShape(LoadTestShape): pass """ ) with mock_locustfile(content=content) as mocked: docstring, user_classes, shape_class = main.load_locustfile(mocked.file_path) self.assertEqual("This is a mock locust file for unit testing", docstring) self.assertIn("UserSubclass", user_classes) self.assertNotIn("NotUserSubclass", user_classes) self.assertEqual(shape_class.__class__.__name__, "LoadTestShape")
def _run(master=False): if not os.path.exists('locust_file_by_web.py'): if generate() != 'success': return 'Fail to generate locust-file!' docstring, locusts = load_locustfile('locust_file_by_web.py') if not locusts: return 'No Locust class found in locust_file_by_web.py!' locust_classes = list(locusts.values()) p = Process(target=_run_locust, args=( locust_classes, master, )) p.start() return 'success'
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 add_router(): if request.method == "POST": name = request.form.get("name") if not name: name = uuid.uuid4() url = request.form.get("url") if not url: return "url 必填" data = request.form.get("data") if not data: data = {} path = os.path.join(PATH_BASE, "locustfile.py") with open(path, "a+", encoding="utf8") as f: f.write( f" @task\n def {name}(self):\n self.client.get('{url}', data={data})\n" ) docstring, locusts = load_locustfile(path) locust_classes = list(locusts.values()) runners.locust_runner = runners.LocalLocustRunner(locust_classes, None) return redirect("/") return render_template("add_router.html")
def test_load_locust_file_with_a_dot_in_filename(self): self.__create_mock_locust_file("mock_locust_file.py") docstring, locusts = main.load_locustfile(self.file_path)
def test_load_locust_file_from_relative_path(self): with mock_locustfile() as mocked: docstring, user_classes, shape_class = main.load_locustfile( os.path.join(os.path.relpath(mocked.directory, os.getcwd()), mocked.filename) )
def create_settings(from_environment=False, locustfile=None, classes=None, host=None, num_clients=None, hatch_rate=None, reset_stats=False, run_time="3m"): ''' Returns a settings object to be used by a LocalLocustRunner. Arguments from_environment: get settings from environment variables locustfile: locustfile to use for loadtest classes: locust classes to use for load test host: host for load testing num_clients: number of clients to simulate in load test hatch_rate: number of clients per second to start reset_stats: Whether to reset stats after all clients are hatched run_time: The length of time to run the test for. Cannot exceed the duration limit set by lambda If from_environment is set to True then this function will attempt to set the attributes from environment variables. The environment variables are named LOCUST_ + attribute name in upper case. ''' settings = type('', (), {})() settings.from_environment = from_environment settings.locustfile = locustfile settings.classes = classes settings.host = host settings.num_clients = num_clients settings.hatch_rate = hatch_rate settings.reset_stats = reset_stats settings.run_time = run_time # Default settings that are not to be changed settings.no_web = True settings.master = False settings.show_task_ratio_json = False settings.list_commands = False settings.loglevel = 'INFO' settings.slave = False settings.only_summary = True settings.logfile = None settings.show_task_ratio = False settings.print_stats = False settings.step_load = False settings.stop_timeout = None if from_environment: for attribute in ['locustfile', 'classes', 'host', 'run_time', 'num_clients', 'hatch_rate']: var_name = 'LOCUST_{0}'.format(attribute.upper()) var_value = os.environ.get(var_name) if var_value: setattr(settings, attribute, var_value) if settings.locustfile is None and settings.classes is None: raise Exception('One of locustfile or classes must be specified') if settings.locustfile and settings.classes: raise Exception('Only one of locustfile or classes can be specified') if settings.locustfile: docstring, classes = load_locustfile(settings.locustfile) settings.classes = [classes[n] for n in classes] else: if isinstance(settings.classes, str): settings.classes = settings.classes.split(',') for idx, val in enumerate(settings.classes): # This needs fixing settings.classes[idx] = eval(val) for attribute in ['classes', 'host', 'num_clients', 'hatch_rate']: val = getattr(settings, attribute, None) if not val: raise Exception('configuration error, attribute not set: {0}'.format(attribute)) if isinstance(val, str) and val.isdigit(): setattr(settings, attribute, int(val)) return settings
def test_load_locust_file_from_relative_path(self): self.__create_mock_locust_file('mock_locust_file.py') docstring, locusts = main.load_locustfile(os.path.join('./locust/test/', self.filename))
def test_load_locust_file_with_a_dot_in_filename(self): with mock_locustfile(filename_prefix="mocked.locust.file") as mocked: docstring, locusts = main.load_locustfile(mocked.file_path)
def test_load_locust_file_from_absolute_path(self): with mock_locustfile() as mocked: docstring, locusts = main.load_locustfile(mocked.file_path) self.assertIn('LocustSubclass', locusts) self.assertNotIn('NotLocustSubclass', locusts)
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 parse_locustfile(locustfile): docstring, locusts = load_locustfile(locustfile) locust_classes = list(locusts.values()) return locust_classes
#!/usr/local/bin/python import os from locust import events, TaskSet from locust.main import load_locustfile locustfile = os.getenv('FILE') or 'locustfile.py' host = os.getenv('HOST') if (host is None): print("HOST environment variable is not set") exit(1) docstring, locusts = load_locustfile(locustfile) total_result = True request_result = None request_exception = None def request_success(request_type, name, response_time, response_length, **kw): global request_result print("Request succeeded: %s, %s" % (request_type, name)) request_result = True def request_failure(request_type, name, response_time, response_length, exception, **kw): global request_result global request_exception print("Request failed: %s, %s" % (request_type, name))
def test_return_docstring_and_locusts(self): self.__create_mock_locust_file('mock_locust_file.py') docstring, locusts = main.load_locustfile(self.file_path) self.assertEqual(docstring, self.mock_docstring) self.assertIn('LocustSubclass', locusts) self.assertNotIn('NotLocustSubclass', locusts)
def test_load_locust_file_with_multiple_dots_in_filename(self): self.__create_mock_locust_file('mock_locust_file.test.py') docstring, locusts = main.load_locustfile(self.file_path)
def create_settings( from_environment=False, locustfile=None, classes=None, host=None, num_users=None, spawn_rate=None, reset_stats=False, run_time="3m", loglevel="INFO", ): """ Returns a settings object to configure the locust load test. Arguments from_environment: get settings from environment variables locustfile: locustfile to use for loadtest classes: locust classes to use for load test host: host for load testing num_users: number of users to simulate in load test spawn_rate: number of users per second to start reset_stats: Whether to reset stats after all users are hatched run_time: The length of time to run the test for. Cannot exceed the duration limit set by lambda If from_environment is set to True then this function will attempt to set the attributes from environment variables. The environment variables are named LOCUST_ + attribute name in upper case. """ settings = type("", (), {})() settings.from_environment = from_environment settings.locustfile = locustfile # parameters needed to create the locust Environment object settings.classes = classes settings.host = host settings.tags = None settings.exclude_tags = None settings.reset_stats = reset_stats settings.step_load = False settings.stop_timeout = None # parameters to configure test settings.num_users = num_users settings.run_time = run_time settings.spawn_rate = spawn_rate if from_environment: for attribute in [ "locustfile", "classes", "host", "run_time", "num_users", "spawn_rate", "loglevel", ]: var_name = "LOCUST_{0}".format(attribute.upper()) var_value = os.environ.get(var_name) if var_value: setattr(settings, attribute, var_value) if settings.locustfile is None and settings.classes is None: raise Exception("One of locustfile or classes must be specified") if settings.locustfile and settings.classes: raise Exception("Only one of locustfile or classes can be specified") if settings.locustfile: docstring, classes, shape_class = load_locustfile(settings.locustfile) settings.classes = [classes[n] for n in classes] else: if isinstance(settings.classes, str): settings.classes = settings.classes.split(",") for idx, val in enumerate(settings.classes): # This needs fixing settings.classes[idx] = eval(val) for attribute in ["classes", "host", "num_users", "spawn_rate"]: val = getattr(settings, attribute, None) if not val: raise Exception( "configuration error, attribute not set: {0}".format(attribute) ) if isinstance(val, str) and val.isdigit(): setattr(settings, attribute, int(val)) return settings
def test_load_locust_file_from_absolute_path(self): self.__create_mock_locust_file('mock_locust_file.py') docstring, locusts = main.load_locustfile(self.file_path)
def test_load_locust_file_from_relative_path(self): with mock_locustfile() as mocked: docstring, locusts = main.load_locustfile( os.path.join('./locust/test/', mocked.filename))
def main(): parser, options = parse_options() # setup logging if not options.skip_log_setup: setup_logging(options.loglevel, options.logfile) logger = logging.getLogger(__name__) locust_path = get_locust_path() if options.web: web_port = options.web_port logger.info('Running easy-locust web: 0.0.0.0:{}'.format(web_port)) init_app(port=web_port) sys.exit(0) if options.demo: if not locust_path: logger.error( '''Cannot locate Python path, make sure it is in right place. If windows add it to sys PATH, if linux make sure python is installed in /usr/local/lib/''') sys.exit(1) pt_demo_path = os.path.join(locust_path, 'demo', 'demo_pressuretest.xls') pt_demo_path_json = os.path.join(locust_path, 'demo', 'demo_locustfile.json') pt_new_demo = os.path.join(os.getcwd(), 'PtDemo.xls') pt_new_demo_json = os.path.join(os.getcwd(), 'demo.json') shutil.copyfile(pt_demo_path, pt_new_demo) shutil.copyfile(pt_demo_path_json, pt_new_demo_json) sys.exit(0) if options.xlsfile: pt_file = options.xlsfile if not (pt_file.endswith('.xls') or pt_file.endswith('.json')): logger.error( "PressureTest file must be end with '.xls' or '.json' and see --help for available options." ) sys.exit(1) if not os.path.isfile(pt_file): logger.error('PressureTest file is not exist, please check it.') sys.exit(1) _status = generate_locust_file(pt_file) if not _status: sys.exit(1) sys.exit(0) locustfile = find_locustfile(options.locustfile) if not locustfile: logger.error( "Could not find any locustfile! Ensure file ends in '.py' and see --help for available options." ) sys.exit(1) if locustfile == "locust.py" or locustfile == "locust.xls" or locustfile == "locust.json": logger.error( "The locustfile must not be named `locust.py` or `locust.xls` or `locust.json`. " "Please rename the file and try again.") sys.exit(1) docstring, locusts = load_locustfile(locustfile) if options.list_commands: console_logger.info("Available Locusts:") for name in locusts: console_logger.info(" " + name) sys.exit(0) if not locusts: logger.error("No Locust class found!") sys.exit(1) # make sure specified Locust exists if options.locust_classes: missing = set(options.locust_classes) - set(locusts.keys()) if missing: logger.error("Unknown Locust(s): %s\n" % (", ".join(missing))) sys.exit(1) else: names = set(options.locust_classes) & set(locusts.keys()) locust_classes = [locusts[n] for n in names] else: # list() call is needed to consume the dict_view object in Python 3 locust_classes = list(locusts.values()) if options.show_task_ratio: console_logger.info("\n Task ratio per locust class") console_logger.info("-" * 80) print_task_ratio(locust_classes) console_logger.info("\n Total task ratio") console_logger.info("-" * 80) print_task_ratio(locust_classes, total=True) sys.exit(0) if options.show_task_ratio_json: from json import dumps task_data = { "per_class": get_task_ratio_dict(locust_classes), "total": get_task_ratio_dict(locust_classes, total=True) } console_logger.info(dumps(task_data)) sys.exit(0) if options.run_time: if not options.no_web: logger.error( "The --run-time argument can only be used together with --no-web" ) sys.exit(1) try: options.run_time = parse_timespan(options.run_time) except ValueError: logger.error( "Valid --run-time formats are: 20, 20s, 3m, 2h, 1h20m, 3h30m10s, etc." ) sys.exit(1) def spawn_run_time_limit_greenlet(): logger.info("Run time limit set to %s seconds" % options.run_time) def timelimit_stop(): logger.info("Time limit reached. Stopping Locust.") runners.locust_runner.quit() gevent.spawn_later(options.run_time, timelimit_stop) if options.step_time: if not options.step_load: logger.error( "The --step-time argument can only be used together with --step-load" ) sys.exit(1) try: options.step_time = parse_timespan(options.step_time) except ValueError: logger.error( "Valid --step-time formats are: 20, 20s, 3m, 2h, 1h20m, 3h30m10s, etc." ) sys.exit(1) if options.master: # Add -d for automatically run slaves if options.distribute: ptpy = locustfile if options.locustfile.endswith('.xls'): _type = 'xls' pt_s = PtExcel(options.locustfile) master_ip, pt_slave_info = pt_s.pt_slave() else: _type = 'dict' with open(options.locustfile, 'r') as f: _d = json.load(f, encoding='utf-8') master_ip = _d.get('master_ip') pt_slave_info = _d.get('slaves') if master_ip == '': logger.error( 'master IP cannot be None if you use --distribute') sys.exit(1) if options.boomer: locust_cli_slave = 'nohup ./client_v1 --web --master-host={masteIP} > /dev/null 2>&1 &'.format( masteIP=master_ip) targets_dict, file_list = gen_boomer_client_json( options.locustfile) boomer_client_file = os.path.join(locust_path, 'boomer_client', 'client_v1') file_list.append(boomer_client_file) thread_pool = [] try: for slave in pt_slave_info: if _type == 'xls': slave_ip, slave_username, slave_password = slave else: slave_ip, slave_username, slave_password = slave[ 'ip'], slave['username'], slave['password'] _t = Thread(target=pt_slave_boomer, args=(slave_ip, slave_username, slave_password, file_list, locust_cli_slave, targets_dict)) logger.info('Prepare slave {}'.format(slave_ip)) thread_pool.append(_t) _t.start() for each_t in thread_pool: each_t.join() file_list.pop() for each in file_list: os.remove(each) except KeyboardInterrupt: pass except Exception as e: logger.error( 'Something happened, collect Exceptions here: {}'. format(e)) else: try: locust_cli_slave = 'nohup locust -f /root/locust_client.py --slave --master-host={masteIP} > /dev/null 2>&1 &'\ .format(masteIP=master_ip) thread_pool = [] for slave in pt_slave_info: if _type == 'xls': slave_ip, slave_username, slave_password = slave else: slave_ip, slave_username, slave_password = slave[ 'ip'], slave['username'], slave['password'] _t = Thread(target=pt_slave, args=(slave_ip, slave_username, slave_password, ptpy, locust_cli_slave)) logger.info('Prepare slave {}'.format(slave_ip)) thread_pool.append(_t) _t.start() for each_t in thread_pool: each_t.join() except KeyboardInterrupt: pass except Exception as e: logger.error( 'Something happened, collect Exceptions here: {}'. format(e)) runners.locust_runner = MasterLocustRunner(locust_classes, options) else: runners.locust_runner = LocalLocustRunner(locust_classes, options) # main_greenlet is pointing to runners.locust_runner.greenlet by default, it will point the web greenlet later if in web mode main_greenlet = runners.locust_runner.greenlet if options.no_web: if options.master: while len(runners.locust_runner.clients.ready ) < options.expect_slaves: logging.info( "Waiting for slaves to be ready, %s of %s connected", len(runners.locust_runner.clients.ready), options.expect_slaves) time.sleep(1) if options.step_time: runners.locust_runner.start_stepload(options.num_clients, options.hatch_rate, options.step_clients, options.step_time) else: runners.locust_runner.start_hatching(options.num_clients, options.hatch_rate) # make locusts are spawned time.sleep(1) else: # spawn web greenlet logger.info("Starting web monitor at http://%s:%s" % (options.web_host or "*", options.port)) main_greenlet = gevent.spawn(web.start, locust_classes, options) if options.run_time: spawn_run_time_limit_greenlet() stats_printer_greenlet = None if not options.only_summary and (options.print_stats or options.no_web): # spawn stats printing greenlet stats_printer_greenlet = gevent.spawn(stats_printer) if options.csvfilebase: gevent.spawn(stats_writer, options.csvfilebase, options.stats_history_enabled) def shutdown(code=0): """ Shut down locust by firing quitting event, printing/writing stats and exiting """ logger.info("Shutting down (exit code %s), bye." % code) if stats_printer_greenlet is not None: stats_printer_greenlet.kill(block=False) logger.info("Cleaning up runner...") if runners.locust_runner is not None: runners.locust_runner.quit() logger.info("Running teardowns...") events.quitting.fire(reverse=True) print_stats(runners.locust_runner.stats, current=False) print_percentile_stats(runners.locust_runner.stats) if options.csvfilebase: write_stat_csvs(options.csvfilebase, options.stats_history_enabled) print_error_report() sys.exit(code) # install SIGTERM handler def sig_term_handler(): logger.info("Got SIGTERM signal") shutdown(0) gevent.signal(signal.SIGTERM, sig_term_handler) try: logger.info("Starting Locust %s" % version) main_greenlet.join() code = 0 if len(runners.locust_runner.errors) or len( runners.locust_runner.exceptions): code = options.exit_code_on_error shutdown(code=code) except KeyboardInterrupt: shutdown(0)
def test_load_locust_file_from_relative_path(self): self.__create_mock_locust_file('mock_locust_file.py') docstring, locusts = main.load_locustfile( os.path.join('./locust/test/', self.filename))
def test_load_locust_file_from_absolute_path(self): with mock_locustfile() as mocked: docstring, user_classes = main.load_locustfile(mocked.file_path) self.assertIn("UserSubclass", user_classes) self.assertNotIn("NotUserSubclass", user_classes)
def test_load_locust_file_from_relative_path(self): with mock_locustfile() as mocked: docstring, user_classes = main.load_locustfile( os.path.join("./locust/test/", mocked.filename))