Skip to content

Commit

Permalink
Merge pull request locustio#4 from cpennington/tablib-and-tabulate
Browse files Browse the repository at this point in the history
Tablib and tabulate
  • Loading branch information
cpennington committed Mar 24, 2015
2 parents 2d632ee + 5ec91bd commit c9377d2
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 192 deletions.
46 changes: 23 additions & 23 deletions locust/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,15 @@ def parse_options():
default="",
help="Host to bind the web interface to. Defaults to '' (all interfaces)"
)

parser.add_option(
'-P', '--port', '--web-port',
type="int",
dest="port",
default=8089,
help="Port on which to run web host"
)

parser.add_option(
'-f', '--locustfile',
dest='locustfile',
Expand All @@ -77,7 +77,7 @@ def parse_options():
default=False,
help="Set locust to run in distributed mode with this process as slave"
)

# master host options
parser.add_option(
'--master-host',
Expand All @@ -87,7 +87,7 @@ def parse_options():
default="127.0.0.1",
help="Host or IP address of locust master for distributed load testing. Only used when running with --slave. Defaults to 127.0.0.1."
)

parser.add_option(
'--master-port',
action='store',
Expand All @@ -105,7 +105,7 @@ def parse_options():
default="*",
help="Interfaces (hostname, ip) that locust master should bind to. Only used when running with --master. Defaults to * (all available interfaces)."
)

parser.add_option(
'--master-bind-port',
action='store',
Expand Down Expand Up @@ -143,7 +143,7 @@ def parse_options():
default=1,
help="The rate per second in which clients are spawned. Only used together with --no-web"
)

# Number of requests
parser.add_option(
'-n', '--num-request',
Expand All @@ -153,7 +153,7 @@ def parse_options():
default=None,
help="Number of requests to perform. Only used together with --no-web"
)

# log level
parser.add_option(
'--loglevel', '-L',
Expand All @@ -163,7 +163,7 @@ def parse_options():
default='INFO',
help="Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL. Default is INFO.",
)

# log file
parser.add_option(
'--logfile',
Expand All @@ -173,7 +173,7 @@ def parse_options():
default=None,
help="Path to log file. If not set, log will go to stdout/stderr",
)

# if we should print stats in the console
parser.add_option(
'--print-stats',
Expand All @@ -191,7 +191,7 @@ def parse_options():
default=False,
help='Only print the summary stats'
)

# List locust commands found in loaded locust files/source files
parser.add_option(
'-l', '--list',
Expand All @@ -200,7 +200,7 @@ def parse_options():
default=False,
help="Show list of possible locust classes and exit"
)

# Display ratio table of all tasks
parser.add_option(
'--show-task-ratio',
Expand All @@ -217,7 +217,7 @@ def parse_options():
default=False,
help="print json data of the locust classes' task execution ratio"
)

# Version number (optparse gives you --version but we have to do it
# ourselves to get -V too. sigh)
parser.add_option(
Expand Down Expand Up @@ -345,7 +345,7 @@ def main():
# setup logging
setup_logging(options.loglevel, options.logfile)
logger = logging.getLogger(__name__)

if options.show_version:
print "Locust %s" % (version,)
sys.exit(0)
Expand Down Expand Up @@ -381,7 +381,7 @@ def main():
locust_classes = [locusts[n] for n in names]
else:
locust_classes = locusts.values()

if options.show_task_ratio:
console_logger.info("\n Task ratio per locust class")
console_logger.info( "-" * 80)
Expand All @@ -393,12 +393,12 @@ def main():
if options.show_task_ratio_json:
from json import dumps
task_data = {
"per_class": get_task_ratio_dict(locust_classes),
"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 --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)")
Expand All @@ -408,7 +408,7 @@ def main():
# 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
Expand All @@ -424,30 +424,30 @@ def main():
except socket.error, 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_stats(runners.locust_runner.stats)
print_percentile_stats(runners.locust_runner.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()
Expand Down
50 changes: 25 additions & 25 deletions locust/runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def __init__(self, locust_classes, options):
self.hatching_greenlet = None
self.exceptions = {}
self.stats = global_stats

# register listener that resets stats when hatching is complete
def on_hatch_complete(user_count):
self.state = STATE_RUNNING
Expand All @@ -48,11 +48,11 @@ def on_hatch_complete(user_count):
@property
def request_stats(self):
return self.stats.entries

@property
def errors(self):
return self.stats.errors

@property
def user_count(self):
return len(self.locusts)
Expand Down Expand Up @@ -97,7 +97,7 @@ def spawn_locusts(self, spawn_count=None, stop_timeout=None, wait=False):

logger.info("Hatching and swarming %i clients at the rate %g clients/s..." % (spawn_count, self.hatch_rate))
occurence_count = dict([(l.__name__, 0) for l in self.locust_classes])

def hatch():
sleep_time = 1.0 / self.hatch_rate
while True:
Expand All @@ -117,7 +117,7 @@ def start_locust(_):
if len(self.locusts) % 10 == 0:
logger.debug("%i locusts hatched" % len(self.locusts))
gevent.sleep(sleep_time)

hatch()
if wait:
self.locusts.join()
Expand Down Expand Up @@ -208,7 +208,7 @@ def __init__(self, locust_classes, options):
self.master_port = options.master_port
self.master_bind_host = options.master_bind_host
self.master_bind_port = options.master_bind_port

def noop(self, *args, **kwargs):
""" Used to link() greenlets to in order to be compatible with gevent 1.0 """
pass
Expand All @@ -222,28 +222,28 @@ def __init__(self, id, state=STATE_INIT):
class MasterLocustRunner(DistributedLocustRunner):
def __init__(self, *args, **kwargs):
super(MasterLocustRunner, self).__init__(*args, **kwargs)

class SlaveNodesDict(dict):
def get_by_state(self, state):
return [c for c in self.itervalues() if c.state == state]

@property
def ready(self):
return self.get_by_state(STATE_INIT)

@property
def hatching(self):
return self.get_by_state(STATE_HATCHING)

@property
def running(self):
return self.get_by_state(STATE_RUNNING)

self.clients = SlaveNodesDict()
self.server = rpc.Server(self.master_bind_host, self.master_bind_port)
self.greenlet = Group()
self.greenlet.spawn(self.client_listener).link_exception(callback=self.noop)

# listener that gathers info on how many locust users the slaves has spawned
def on_slave_report(client_id, data):
if client_id not in self.clients:
Expand All @@ -252,16 +252,16 @@ def on_slave_report(client_id, data):

self.clients[client_id].user_count = data["user_count"]
events.slave_report += on_slave_report

# register listener that sends quit message to slave nodes
def on_quitting():
self.quit()
events.quitting += on_quitting

@property
def user_count(self):
return sum([c.user_count for c in self.clients.itervalues()])

def start_hatching(self, locust_count, hatch_rate):
num_slaves = len(self.clients.ready) + len(self.clients.running)
if not num_slaves:
Expand All @@ -280,7 +280,7 @@ def start_hatching(self, locust_count, hatch_rate):
self.stats.clear_all()
self.exceptions = {}
events.master_start_hatching.fire()

for client in self.clients.itervalues():
data = {
"hatch_rate":slave_hatch_rate,
Expand All @@ -295,20 +295,20 @@ def start_hatching(self, locust_count, hatch_rate):
remaining -= 1

self.server.send(Message("hatch", data, None))

self.stats.start_time = time()
self.state = STATE_HATCHING

def stop(self):
for client in self.clients.hatching + self.clients.running:
self.server.send(Message("stop", None, None))
events.master_stop_hatching.fire()

def quit(self):
for client in self.clients.itervalues():
self.server.send(Message("quit", None, None))
self.greenlet.kill(block=True)

def client_listener(self):
while True:
msg = self.server.recv()
Expand Down Expand Up @@ -349,24 +349,24 @@ class SlaveLocustRunner(DistributedLocustRunner):
def __init__(self, *args, **kwargs):
super(SlaveLocustRunner, self).__init__(*args, **kwargs)
self.client_id = socket.gethostname() + "_" + md5(str(time() + random.randint(0,10000))).hexdigest()

self.client = rpc.Client(self.master_host, self.master_port)
self.greenlet = Group()

self.greenlet.spawn(self.worker).link_exception(callback=self.noop)
self.client.send(Message("client_ready", None, self.client_id))
self.greenlet.spawn(self.stats_reporter).link_exception(callback=self.noop)

# register listener for when all locust users have hatched, and report it to the master node
def on_hatch_complete(user_count):
self.client.send(Message("hatch_complete", {"count":user_count}, self.client_id))
events.hatch_complete += on_hatch_complete
# register listener that adds the current number of spawned locusts to the report that is sent to the master node

# register listener that adds the current number of spawned locusts to the report that is sent to the master node
def on_report_to_master(client_id, data):
data["user_count"] = self.user_count
events.report_to_master += on_report_to_master

# register listener that sends quit message to master
def on_quitting():
self.client.send(Message("quit", None, self.client_id))
Expand Down Expand Up @@ -407,5 +407,5 @@ def stats_reporter(self):
except:
logger.error("Connection lost to master server. Aborting...")
break

gevent.sleep(SLAVE_REPORT_INTERVAL)
Loading

0 comments on commit c9377d2

Please sign in to comment.