def on_exit(**kwargs): if ssh_identity_file is not None: os.remove(ssh_identity_file) locustfile_source.cleanup() print_formatted_stats_on_primary_node(runners.locust_runner.stats) report_to_cloudwatch_metrics(runners.locust_runner.stats) console_logger.info('exiting')
def print_formatted_stats_on_primary_node(stats): if options.master_host is not None: # Slave mode. Do not print stats on slaves return for key in sorted(six.iterkeys(stats.entries)): item = stats.entries[key] console_logger.info(json.dumps({**{ "locust_stat_type": "standard", "rps": item.total_rps }, **item.serialize()})) percentile_stats = [{**{ "locust_stat_type": "percentile", "name": stats.entries[key].name, "method": stats.entries[key].method, "num_request": stats.entries[key].num_requests }, **get_percentiles(stats.entries[key])} for key in sorted(six.iterkeys(stats.entries))] for item in percentile_stats: console_logger.info(json.dumps(item))
def list_top_10_currencies_by_volume_24h(self): try: resp_json = _resp.json() # console_logger.info(resp_json) global _resp_number # Receive 8 responses if _resp_number < 8: _resp_number += 1 console_logger.info("Response #{}".format(_resp_number)) console_logger.info( "Top 10 currencies in past 24 hours by volume:") # Display top 10 currencies for number_of_currency in range(10): console_logger.info( resp_json['data'][number_of_currency]['name']) # console_logger.info(round(resp_json['data'][number_of_currency]['quote']['USD']['volume_24h'], 1)) else: runners.locust_runner.quit() except JSONDecodeError: console_logger.error("ERROR: Failed to decode response into JSON")
def on_slave_report(client_id, data): if 'instanceId' in data: instanceId = data['instanceId'].strip() #console_logger.info( 'instanceId %s' % (instanceId) ) if client_id not in locust.runners.locust_runner.clients: if client_id not in reportedZombies: reportedZombies.add(client_id) console_logger.info('zombie instance %s' % (instanceId)) else: instanceId = None ipAddr = None if 'ipAddr' in data: ipAddr = data['ipAddr'].strip() if client_id not in locust.runners.locust_runner.clients: return # NO FURTHER PROCESSING of reports from zombies if g_stopping: if len(data['stats']): console_logger.info( 'on_slave_report DROPPING because g_stopping len(stats): %d', len(data['stats'])) return if len(data['stats']): #if len(data['stats']) != 1: # console_logger.info( '%d stats objects' % len(data['stats']) ) nUsers = data['user_count'] stats = data['stats_total'] rpss = data['stats_total']['num_reqs_per_sec'] #rpss = data['stats'][0]['num_reqs_per_sec'] if len(rpss) > 0: rps = sorted(rpss.values())[int(len(rpss) / 2)] # approximate median value maxRps = max(rpss.values()) minRps = min(rpss.values()) else: rps = maxRps = minRps = 0 numReqs = data['stats_total']['num_requests'] rtimes = data['stats_total'][ 'response_times'] # these are quantized when large if numReqs: meanRTime = data['stats_total']['total_response_time'] / numReqs else: meanRTime = 0 #meanRTime = sum(rtimes.keys()) / len(rtimes.keys()) # old way, imprecise maxRTime = data['stats_total'][ 'max_response_time'] # was max( rtimes.keys() ) minRTime = data['stats_total']['min_response_time'] if minRTime == None: minRTime = 0 #minRTime = min( rtimes.keys() ) # old way, imprecise medRTime = median_from_dict(numReqs, rtimes) if medRTime == None: medRTime = 0 # get begin and end time stamps (they are request times, not response-received times) startTimeStamp = data['stats_total']['start_time'] # endTimeStamp = data['stats_total']['last_request_timestamp'] # not useful, it is truncated startDateTime = datetime.datetime.fromtimestamp( startTimeStamp ) # could be bad if closks out of sync (or wrong timezone) masterDateTime = datetime.datetime.now() timeDiscrep = (masterDateTime - startDateTime).total_seconds() - 3 if abs(timeDiscrep) > 6.0: console_logger.info('timeDiscrep: %.3f for instance %s', timeDiscrep, instanceId) if abs(timeDiscrep) > 15: console_logger.info('DROPPING due to time discrepancy %s', instanceId) return # DROPPING data endDateTime = datetime.datetime.now() if instanceId and ipAddr: workerName = '%s_%s' % (ipAddr, instanceId) elif instanceId: workerName = '_%s' % (instanceId) else: workerName = client_id.rsplit('_', 1)[0] if workerName == 'localhost': if instanceId: workerName = instanceId else: workerName = client_id if g_jlogFile: json.dump([workerName, data], g_jlogFile) print("", file=g_jlogFile) g_jlogFile.flush() numFails = stats['num_failures'] #console_logger.info( '%s worker: %s; reqs: %d RPS: %.1f (%.1f-%.1f); response time: %.1f (%.1f-%.1f)' % \ # (datetime.datetime.now().isoformat(), workerName, numReqs, rps, minRps, maxRps, meanRTime, minRTime, maxRTime) ) #console_logger.info( 'rTimes: %s' % (data['stats_total']['response_times'].keys()) ) #console_logger.info( 'stats_total: %s' % (data['stats_total']) ) if data['stats_total']['max_response_time'] != maxRTime: console_logger.info('stats_total: %s' % (data['stats_total'])) if numFails > 0: console_logger.info('num_failures: %d' % (numFails)) if False: console_logger.info('disabled') else: try: outString = '%s,%s,%s,%d,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%d,%d' % \ (startDateTime.isoformat(), endDateTime.isoformat(), workerName, numReqs, rps, maxRps, minRps, meanRTime, maxRTime, medRTime, minRTime, numFails, nUsers ) if g_statsOutFile: print(outString, file=g_statsOutFile) g_statsOutFile.flush() else: console_logger.info('NO g_statsOutFile (master)') except: print('outString error', rps, maxRps, minRps, meanRTime, maxRTime, medRTime, minRTime, file=sys.stderr)
def on_master_stop_hatching(): '''event hook function to be called by Locust ''' global g_stopping console_logger.info('on_master_stop_hatching called') g_stopping = True
def on_hatch_complete(user_count=0): '''event hook function to be called by Locust ''' console_logger.info('on_hatch_complete called with count %d', user_count)
def main(): parser, options = parse_options() # setup logging if not options.skip_log_setup: setup_logging(options.loglevel, options.logfile) logger = logging.getLogger(__name__) locust_path = get_locust_path() if options.web: web_port = options.web_port logger.info('Running easy-locust web: 0.0.0.0:{}'.format(web_port)) init_app(port=web_port) sys.exit(0) if options.demo: if not locust_path: logger.error( '''Cannot locate Python path, make sure it is in right place. If windows add it to sys PATH, if linux make sure python is installed in /usr/local/lib/''') sys.exit(1) pt_demo_path = os.path.join(locust_path, 'demo', 'demo_pressuretest.xls') pt_demo_path_json = os.path.join(locust_path, 'demo', 'demo_locustfile.json') pt_new_demo = os.path.join(os.getcwd(), 'PtDemo.xls') pt_new_demo_json = os.path.join(os.getcwd(), 'demo.json') shutil.copyfile(pt_demo_path, pt_new_demo) shutil.copyfile(pt_demo_path_json, pt_new_demo_json) sys.exit(0) if options.xlsfile: pt_file = options.xlsfile if not (pt_file.endswith('.xls') or pt_file.endswith('.json')): logger.error( "PressureTest file must be end with '.xls' or '.json' and see --help for available options." ) sys.exit(1) if not os.path.isfile(pt_file): logger.error('PressureTest file is not exist, please check it.') sys.exit(1) _status = generate_locust_file(pt_file) if not _status: sys.exit(1) sys.exit(0) locustfile = find_locustfile(options.locustfile) if not locustfile: logger.error( "Could not find any locustfile! Ensure file ends in '.py' and see --help for available options." ) sys.exit(1) if locustfile == "locust.py" or locustfile == "locust.xls" or locustfile == "locust.json": logger.error( "The locustfile must not be named `locust.py` or `locust.xls` or `locust.json`. " "Please rename the file and try again.") sys.exit(1) docstring, locusts = load_locustfile(locustfile) if options.list_commands: console_logger.info("Available Locusts:") for name in locusts: console_logger.info(" " + name) sys.exit(0) if not locusts: logger.error("No Locust class found!") sys.exit(1) # make sure specified Locust exists if options.locust_classes: missing = set(options.locust_classes) - set(locusts.keys()) if missing: logger.error("Unknown Locust(s): %s\n" % (", ".join(missing))) sys.exit(1) else: names = set(options.locust_classes) & set(locusts.keys()) locust_classes = [locusts[n] for n in names] else: # list() call is needed to consume the dict_view object in Python 3 locust_classes = list(locusts.values()) if options.show_task_ratio: console_logger.info("\n Task ratio per locust class") console_logger.info("-" * 80) print_task_ratio(locust_classes) console_logger.info("\n Total task ratio") console_logger.info("-" * 80) print_task_ratio(locust_classes, total=True) sys.exit(0) if options.show_task_ratio_json: from json import dumps task_data = { "per_class": get_task_ratio_dict(locust_classes), "total": get_task_ratio_dict(locust_classes, total=True) } console_logger.info(dumps(task_data)) sys.exit(0) if options.run_time: if not options.no_web: logger.error( "The --run-time argument can only be used together with --no-web" ) sys.exit(1) try: options.run_time = parse_timespan(options.run_time) except ValueError: logger.error( "Valid --run-time formats are: 20, 20s, 3m, 2h, 1h20m, 3h30m10s, etc." ) sys.exit(1) def spawn_run_time_limit_greenlet(): logger.info("Run time limit set to %s seconds" % options.run_time) def timelimit_stop(): logger.info("Time limit reached. Stopping Locust.") runners.locust_runner.quit() gevent.spawn_later(options.run_time, timelimit_stop) if options.step_time: if not options.step_load: logger.error( "The --step-time argument can only be used together with --step-load" ) sys.exit(1) try: options.step_time = parse_timespan(options.step_time) except ValueError: logger.error( "Valid --step-time formats are: 20, 20s, 3m, 2h, 1h20m, 3h30m10s, etc." ) sys.exit(1) if options.master: # Add -d for automatically run slaves if options.distribute: ptpy = locustfile if options.locustfile.endswith('.xls'): _type = 'xls' pt_s = PtExcel(options.locustfile) master_ip, pt_slave_info = pt_s.pt_slave() else: _type = 'dict' with open(options.locustfile, 'r') as f: _d = json.load(f, encoding='utf-8') master_ip = _d.get('master_ip') pt_slave_info = _d.get('slaves') if master_ip == '': logger.error( 'master IP cannot be None if you use --distribute') sys.exit(1) if options.boomer: locust_cli_slave = 'nohup ./client_v1 --web --master-host={masteIP} > /dev/null 2>&1 &'.format( masteIP=master_ip) targets_dict, file_list = gen_boomer_client_json( options.locustfile) boomer_client_file = os.path.join(locust_path, 'boomer_client', 'client_v1') file_list.append(boomer_client_file) thread_pool = [] try: for slave in pt_slave_info: if _type == 'xls': slave_ip, slave_username, slave_password = slave else: slave_ip, slave_username, slave_password = slave[ 'ip'], slave['username'], slave['password'] _t = Thread(target=pt_slave_boomer, args=(slave_ip, slave_username, slave_password, file_list, locust_cli_slave, targets_dict)) logger.info('Prepare slave {}'.format(slave_ip)) thread_pool.append(_t) _t.start() for each_t in thread_pool: each_t.join() file_list.pop() for each in file_list: os.remove(each) except KeyboardInterrupt: pass except Exception as e: logger.error( 'Something happened, collect Exceptions here: {}'. format(e)) else: try: locust_cli_slave = 'nohup locust -f /root/locust_client.py --slave --master-host={masteIP} > /dev/null 2>&1 &'\ .format(masteIP=master_ip) thread_pool = [] for slave in pt_slave_info: if _type == 'xls': slave_ip, slave_username, slave_password = slave else: slave_ip, slave_username, slave_password = slave[ 'ip'], slave['username'], slave['password'] _t = Thread(target=pt_slave, args=(slave_ip, slave_username, slave_password, ptpy, locust_cli_slave)) logger.info('Prepare slave {}'.format(slave_ip)) thread_pool.append(_t) _t.start() for each_t in thread_pool: each_t.join() except KeyboardInterrupt: pass except Exception as e: logger.error( 'Something happened, collect Exceptions here: {}'. format(e)) runners.locust_runner = MasterLocustRunner(locust_classes, options) else: runners.locust_runner = LocalLocustRunner(locust_classes, options) # main_greenlet is pointing to runners.locust_runner.greenlet by default, it will point the web greenlet later if in web mode main_greenlet = runners.locust_runner.greenlet if options.no_web: if options.master: while len(runners.locust_runner.clients.ready ) < options.expect_slaves: logging.info( "Waiting for slaves to be ready, %s of %s connected", len(runners.locust_runner.clients.ready), options.expect_slaves) time.sleep(1) if options.step_time: runners.locust_runner.start_stepload(options.num_clients, options.hatch_rate, options.step_clients, options.step_time) else: runners.locust_runner.start_hatching(options.num_clients, options.hatch_rate) # make locusts are spawned time.sleep(1) else: # spawn web greenlet logger.info("Starting web monitor at http://%s:%s" % (options.web_host or "*", options.port)) main_greenlet = gevent.spawn(web.start, locust_classes, options) if options.run_time: spawn_run_time_limit_greenlet() stats_printer_greenlet = None if not options.only_summary and (options.print_stats or options.no_web): # spawn stats printing greenlet stats_printer_greenlet = gevent.spawn(stats_printer) if options.csvfilebase: gevent.spawn(stats_writer, options.csvfilebase, options.stats_history_enabled) def shutdown(code=0): """ Shut down locust by firing quitting event, printing/writing stats and exiting """ logger.info("Shutting down (exit code %s), bye." % code) if stats_printer_greenlet is not None: stats_printer_greenlet.kill(block=False) logger.info("Cleaning up runner...") if runners.locust_runner is not None: runners.locust_runner.quit() logger.info("Running teardowns...") events.quitting.fire(reverse=True) print_stats(runners.locust_runner.stats, current=False) print_percentile_stats(runners.locust_runner.stats) if options.csvfilebase: write_stat_csvs(options.csvfilebase, options.stats_history_enabled) print_error_report() sys.exit(code) # install SIGTERM handler def sig_term_handler(): logger.info("Got SIGTERM signal") shutdown(0) gevent.signal(signal.SIGTERM, sig_term_handler) try: logger.info("Starting Locust %s" % version) main_greenlet.join() code = 0 if len(runners.locust_runner.errors) or len( runners.locust_runner.exceptions): code = options.exit_code_on_error shutdown(code=code) except KeyboardInterrupt: shutdown(0)
def main(user_params=None, common_param=None): parser, options, arguments = parse_options() # setup logging setup_logging(options.loglevel, options.logfile) logger = logging.getLogger(__name__) if options.show_version: print("Locust %s" % (version, )) sys.exit(0) locustfile = find_locustfile(options.locustfile) if not locustfile: logger.error( "Could not find any locustfile! Ensure file ends in '.py' and see --help for available options." ) sys.exit(1) if locustfile == "locust.py": logger.error( "The locustfile must not be named `locust.py`. Please rename the file and try again." ) sys.exit(1) docstring, locusts = load_locustfile(locustfile) if options.list_commands: console_logger.info("Available Locusts:") for name in locusts: console_logger.info(" " + name) sys.exit(0) if not locusts: logger.error("No Locust class found!") sys.exit(1) # make sure specified Locust exists if arguments: missing = set(arguments) - set(locusts.keys()) if missing: logger.error("Unknown Locust(s): %s\n" % (", ".join(missing))) sys.exit(1) else: names = set(arguments) & set(locusts.keys()) locust_classes = [locusts[n] for n in names] else: # list() call is needed to consume the dict_view object in Python 3 locust_classes = list(locusts.values()) if options.show_task_ratio: console_logger.info("\n Task ratio per locust class") console_logger.info("-" * 80) print_task_ratio(locust_classes) console_logger.info("\n Total task ratio") console_logger.info("-" * 80) print_task_ratio(locust_classes, total=True) sys.exit(0) if options.show_task_ratio_json: from json import dumps task_data = { "per_class": get_task_ratio_dict(locust_classes), "total": get_task_ratio_dict(locust_classes, total=True) } console_logger.info(dumps(task_data)) sys.exit(0) if not options.no_web and not options.slave: # spawn web greenlet logger.info("Starting web monitor at %s:%s" % (options.web_host or "*", options.port)) main_greenlet = gevent.spawn(web.start, locust_classes, options) if not options.master and not options.slave: if user_params: runners.locust_runner = ParameterizableLocustRunner( locust_classes, options, user_params, common_param) else: runners.locust_runner = LocalLocustRunner(locust_classes, options) # spawn client spawning/hatching greenlet if options.no_web: runners.locust_runner.start_hatching(wait=True) main_greenlet = runners.locust_runner.greenlet elif options.master: runners.locust_runner = MasterLocustRunner(locust_classes, options) if options.no_web: while len(runners.locust_runner.clients.ready ) < options.expect_slaves: logging.info( "Waiting for slaves to be ready, %s of %s connected", len(runners.locust_runner.clients.ready), options.expect_slaves) time.sleep(1) runners.locust_runner.start_hatching(options.num_clients, options.hatch_rate) main_greenlet = runners.locust_runner.greenlet elif options.slave: try: runners.locust_runner = SlaveLocustRunner(locust_classes, options) main_greenlet = runners.locust_runner.greenlet except socket.error as e: logger.error("Failed to connect to the Locust master: %s", e) sys.exit(-1) if not options.only_summary and (options.print_stats or (options.no_web and not options.slave)): # spawn stats printing greenlet gevent.spawn(stats_printer) if options.csvfilebase: gevent.spawn(stats_writer, options.csvfilebase) def shutdown(code=0): """ Shut down locust by firing quitting event, printing/writing stats and exiting """ logger.info("Shutting down (exit code %s), bye." % code) events.quitting.fire() print_stats(runners.locust_runner.request_stats) print_percentile_stats(runners.locust_runner.request_stats) if options.csvfilebase: write_stat_csvs(options.csvfilebase) print_error_report() sys.exit(code) # install SIGTERM handler def sig_term_handler(): logger.info("Got SIGTERM signal") shutdown(0) gevent.signal(gevent.signal.SIGTERM, sig_term_handler) try: logger.info("Starting Locust %s" % version) main_greenlet.join() code = 0 if len(runners.locust_runner.errors): code = 1 shutdown(code=code) except KeyboardInterrupt as e: shutdown(0)
def main(): options = parse_options() ssh_pvt_key_ssm_param_name = options.ssh_pvt_key_ssm_param_name def get_ssh_identity_file(): if ssh_pvt_key_ssm_param_name is not None: ssm = boto3.client('ssm') ssh_pvt_key = ssm.get_parameter(Name=ssh_pvt_key_ssm_param_name, WithDecryption=True)['Parameter']['Value'] fd, file_name = tempfile.mkstemp() with open(file_name, 'w') as file: file.write(ssh_pvt_key) os.close(fd) return file_name return None ssh_identity_file = get_ssh_identity_file() locustfile_selector = LocustFileSelectorPipeline( [GitLocustFileSelectorMiddleware(ssh_identity_file=ssh_identity_file)]) locustfile_source = locustfile_selector.select(options.locustfile_source) locusfile = locustfile_source.fetch() def get_percentiles(stat_entry): return {str(e) + '%': stat_entry.get_response_time_percentile(e) for e in PERCENTILES_TO_REPORT} def print_formatted_stats_on_primary_node(stats): if options.master_host is not None: # Slave mode. Do not print stats on slaves return for key in sorted(six.iterkeys(stats.entries)): item = stats.entries[key] console_logger.info(json.dumps({**{ "locust_stat_type": "standard", "rps": item.total_rps }, **item.serialize()})) percentile_stats = [{**{ "locust_stat_type": "percentile", "name": stats.entries[key].name, "method": stats.entries[key].method, "num_request": stats.entries[key].num_requests }, **get_percentiles(stats.entries[key])} for key in sorted(six.iterkeys(stats.entries))] for item in percentile_stats: console_logger.info(json.dumps(item)) def create_standard_metric_data(stat_entry): return [ { 'MetricName': 'Req/s', 'Value': stat_entry.total_rps, 'Unit': 'Count/Second' }, { 'MetricName': 'Min Response Time', 'Value': stat_entry.min_response_time, 'Unit': 'Milliseconds' }, { 'MetricName': 'Max Response Time', 'Value': stat_entry.max_response_time, 'Unit': 'Milliseconds' }, { 'MetricName': 'Avg Response Time', 'Value': stat_entry.avg_response_time, 'Unit': 'Milliseconds' }, { 'MetricName': 'Median Response Time', 'Value': stat_entry.median_response_time, 'Unit': 'Milliseconds' }, { 'MetricName': 'Total Requests', 'Value': stat_entry.num_requests, 'Unit': 'Count' }, { 'MetricName': 'Total Failed Requests', 'Value': stat_entry.num_failures, 'Unit': 'Count' }, ] def create_percentile_metric_data(stat_entry): return [{ 'MetricName': str(p * 100) + '% Latency', 'Value': stat_entry.get_response_time_percentile(p), 'Unit': 'Percent' } for p in PERCENTILES_TO_REPORT] def create_metric_data(stat_entry): timestamp = datetime.utcnow() return list(map(lambda e: {**e, **{ 'Timestamp': timestamp, 'Dimensions': [ { 'Name': 'Method', 'Value': stat_entry.method }, { 'Name': 'Name', 'Value': stat_entry.name }, { 'Name': 'Host', 'Value': options.host }]}}, create_standard_metric_data(stat_entry) + create_percentile_metric_data(stat_entry))) cloudwatch = None def ensure_cloudwatch_client_created(): nonlocal cloudwatch if cloudwatch is not None: return cloudwatch = boto3.client('cloudwatch') def report_to_cloudwatch_metrics(stats): namespace = options.cloudwatch_metric_ns if namespace is None: return if options.master_host is not None: # represents that this is the master node # Metrics will not be reported if this is not the master node return ensure_cloudwatch_client_created() for entry in six.itervalues(stats.entries): metric_data = create_metric_data(entry) cloudwatch.put_metric_data(Namespace=namespace, MetricData=metric_data) def on_exit(**kwargs): if ssh_identity_file is not None: os.remove(ssh_identity_file) locustfile_source.cleanup() print_formatted_stats_on_primary_node(runners.locust_runner.stats) report_to_cloudwatch_metrics(runners.locust_runner.stats) console_logger.info('exiting') argv = [sys.argv[0], '-f', locusfile, '--no-web', '-c', options.num_clients, '-r', options.hatch_rate, '-H', options.host] if options.expect_slaves is not None: argv += ['--master', '--expect-slaves', str(options.expect_slaves)] if options.master_host is not None: argv += ['--slave', '--master-host', str(options.master_host)] if options.step_time: argv += ['--step-time', options.step_time, '--step-clients', str(options.step_clients)] else: argv += ['--run-time', options.run_time] if options.step_load: argv += ['--step-load'] sys.argv = argv atexit.register(on_exit) console_logger.info('starting') locust_main.main()