def run(self, timeout=None): ''' Run the load test. Optionally a timeout can be set to limit the run time of the load test ''' if timeout: try: timeout = parse_timespan(timeout) except ValueError: logger.error( "Valid --run-time formats are: 20, 20s, 3m, 2h, 1h20m, 3h30m10s, etc." ) sys.exit(1) self.timeout = timeout logger.info("Run time limit set to %s seconds" % timeout) def timelimit_stop(): logger.info("Time limit reached. Stopping Locust.") logger.info(json.dumps(self.stats())) logger.info("Run time limit reached: {0} seconds".format( self.timeout)) runners.locust_runner.quit() gevent.spawn_later(timeout, timelimit_stop) try: logger.info("Starting Locust with settings {0}".format( vars(self.settings))) runners.locust_runner = runners.LocalLocustRunner( self.settings.classes, self.settings) runners.locust_runner.start_hatching(wait=True) self.start_time = time.time() runners.locust_runner.greenlet.join() self.end_time = time.time() logger.info('Locust completed {0} requests with {1} errors'.format( runners.locust_runner.stats.num_requests, len(runners.locust_runner.errors))) except Exception as e: logger.error("Locust exception {0}".format(repr(e))) finally: events.quitting.fire()
def swarm(): assert request.method == "POST" locust_count = int(request.form["locust_count"]) hatch_rate = float(request.form["hatch_rate"]) #Check that option runtime is running runtime = str(request.form["runtime"]) if runtime is not None: try: boptions.run_time = parse_timespan(runtime) except ValueError: logger.error( "Valid --run-time formats are: 20, 20s, 3m, 2h, 1h20m, 3h30m10s, etc." ) sys.exit(1) runtime = boptions.run_time if runtime is not None: gevent.spawn_later(boptions.run_time, main.timelimit_stop) runners.locust_runner.start_hatching(locust_count, hatch_rate) return jsonify({'success': True, 'message': 'Swarming started'})
def test_parse_timespan(self): self.assertEqual(7, parse_timespan("7")) self.assertEqual(7, parse_timespan("7s")) self.assertEqual(60, parse_timespan("1m")) self.assertEqual(7200, parse_timespan("2h")) self.assertEqual(3787, parse_timespan("1h3m7s"))
class Producer(TaskQueueLocust): """ Virtual TaskQueue user producing tasks """ # Locustio has CLI --run-time parameter which limits execution time, # but it kills locusts without giving them to report latest # action to validation log. stop_timeout = parse_timespan(os.environ['RUN_TIME']) class ProducerTasks(locust.TaskSet): """ Set of actions (locust tasks) performed by producer """ @locust.task def add_tasks(self): """ The only action performed by producer. Sends BulkAdd request to TaskQueue protobuffer API. Every added task is reported to validation log. """ bulk_add = taskqueue_service_pb2.TaskQueueBulkAddRequest() tasks = {} for _ in range(BULK_SIZES): add_task = bulk_add.add_request.add() add_task.app_id = bytes(TEST_PROJECT, 'utf8') add_task.queue_name = bytes(PULL_QUEUE, 'utf8') add_task.mode = taskqueue_service_pb2.TaskQueueMode.PULL add_task.task_name = bytes(str(uuid.uuid4()), 'utf8') work_times = self.generate_work_times() add_task.body = bytes(work_times, 'utf8') + b':' + next(BIG_PAYLOADS) add_task.eta_usec = 0 tasks[add_task.task_name] = work_times # Send request to TaskQueue with self.client.protobuf('BulkAdd', bulk_add) as response: # Report what tasks were added for task in response.taskresult: if task.result == taskqueue_service_pb2.TaskQueueServiceError.OK: self.locust.log_action( 'ADDED', str(task.chosen_task_name, 'utf8'), tasks[task.chosen_task_name] ) else: self.locust.log_action(task.result, '?', '?') @staticmethod def generate_work_times(): """ Generates a string representing execution scenario for a task. The string is space-separated list of numbers. Every number corresponds to task processing attempt and tells worker how long should take execution of a task. """ simulate_failures = next(FAIL_SEQUENCE) work_time_seq = (str(next(WORK_TIME)) for _ in range(simulate_failures + 1)) return ' '.join(work_time_seq) task_set = ProducerTasks min_wait = 800 max_wait = 1200
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)