Locust 在性能测试界已经比较出名了,本身是 python 写的,代码量不多,学习起来挺好。
github:[https://github.com/locustio/locust]
我本身是想搞一个网页版的性能测试平台,希望使用 python 语言实现,以方便 app 端 ui 测试代码迁移。
Locust 源码主要的文件有两个:main.py 和 runners.py
它执行程序的入口,以下代码仅包含核心代码。
它用来解析传入的参数,阅读它可以了解 Locust 都接受哪些参数,并且大致是什么作用。
基本代码:
"""
Handle command-line options with optparse.OptionParser.
Return list of arguments, largely for use in `parse_arguments`.
"""
# Initialize
parser = OptionParser(usage="locust [options] [LocustClass [LocustClass2 ... ]]")
parser.add_option(
'-H', '--host',
dest="host",
default=None,
help="Host to load test in the following format: http://10.21.32.33"
)
写的挺清楚了,-H 和 --host 是相同的,默认是 None,提示是 help。
找到并加载我们手写的 locust 用例,即-f 传入的文件,py 结尾。
核心代码(节选):
# Perform the import (trimming off the .py)
imported = __import__(os.path.splitext(locustfile)[0])
# Return our two-tuple
locusts = dict(filter(is_locust, vars(imported).items()))
上面是:1.将自身的 import 导入 locust 用例文件。2.得到其中的用例类。is_locust 是布尔返回类型的方法,用于判断是否继承了 TaskSet。
很长的条件分支,根据输入的参数来走不同的逻辑代码。
options 对象代表着传入的参数。
locusts 对象代表着我们的用例 TaskSet 类。
def timelimit_stop():
logger.info("Time limit reached. Stopping Locust.")
runners.locust_runner.quit()
gevent.spawn_later(options.run_time, timelimit_stop)
使用了协程来执行。
main_greenlet = gevent.spawn(web.start, locust_classes, options)
也是用协程,启动了一个 web 程序,本身是 flask 的。
locust_classes 和 options 是 web 程序参数,包含了 host port。
# spawn client spawning/hatching greenlet
if options.no_web:
runners.locust_runner.start_hatching(wait=True)
main_greenlet = runners.locust_runner.greenlet
if options.run_time:
spawn_run_time_limit_greenlet()
会执行 master 对应的 runners,hatching 是孵化,即开始启动。
main_greenlet 是协程的主体。是协程的池子,Group() ,我理解类似于众多任务的一个集合(from gevent.pool import Group)。
协程就不解释了,这里一个 main_greenlet 就是一个协程的主体,至于你是 4 核的 CPU 最好是 4 个协程,这是定义和启动 4 个 slave 实现的,代码不会判断这些。
runners.locust_runner 是另一个重要文件的内容,后面再解释。
后面代码都很类似。
master runner 和 slave runner 都是继承的 LocustRunner 类,都是其中的方法实现。
Locust 执行的方法类。
由于内容比较多,后面另开一个帖子介绍吧。先介绍一下 events,便于后面的 runners.py 阅读。
Locust 事件的框架,简单来说,就是声明一个方法,加入到指定的 events 中。
只要是同样的方法(参数不同),都可以加入到这个 events 中。
之后调用 events 的 fire(self, **kwargs) ,调用到之前声明定义的方法,完成触发动作。
class EventHook(object):
"""
Simple event class used to provide hooks for different types of events in Locust.
Here's how to use the EventHook class::
my_event = EventHook()
def on_my_event(a, b, **kw):
print "Event was fired with arguments: %s, %s" % (a, b)
my_event += on_my_event
my_event.fire(a="foo", b="bar")
"""
def __init__(self):
self._handlers = []
def __iadd__(self, handler):
self._handlers.append(handler)
return self
def __isub__(self, handler):
self._handlers.remove(handler)
return self
def fire(self, **kwargs):
for handler in self._handlers:
handler(**kwargs)
# 一个例子
request_success = EventHook()
使用的代码举例:
# register listener that resets stats when hatching is complete
def on_hatch_complete(user_count):
self.state = STATE_RUNNING
if self.options.reset_stats:
logger.info("Resetting stats\n")
self.stats.reset_all()
events.hatch_complete += on_hatch_complete
如上,events.hatch_complete 相当于一个触发的任务链(使用 += 添加任务)。
使用下面代码调用:
events.hatch_complete.fire(user_count=self.num_clients)
Locust 并不完美,或者说离 Jmeter 的程度都是非常遥远的。
Jmeter 几乎每天都在更新,Locust 几乎没啥更新。
相关 Locust 的思考后续写一下吧,使用 Locust 要慎重,慎重。