Locust 是一個開源的python壓力測試的框架,代碼相對簡單椭赋,涵蓋的東西卻不少抚岗。 修改起來挺方便,適合學(xué)習(xí)一個項目哪怔。
OptionParser
OptionParser 是一個生成commandline 解析各個參數(shù)的庫宣蔚,并能自動生成幫助文件,很強(qiáng)大
基本用法
parser = OptionParser(usage="locust [options] [LocustClass [LocustClass2 ... ]]")
parser.add_option(
'--loglevel', '-L',
action='store',
type='str',
dest='loglevel',
default='INFO',
help="Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL. Default is INFO.",
)
其中action
有一組固定的值可供選擇认境,默認(rèn)是’store ‘胚委,表示將命令行參數(shù)值保存在 options 對象里。 store_true 和 store_false 元暴,用于處理帶命令行參數(shù)后面不 帶值的情況篷扩。如 -v,-q 等命令行參數(shù):
parser.add_option("-v", action="store_true", dest="verbose")
parser.add_option("-q", action="store_false", dest="verbose")
其它的 actions
值還有:store_const
、append
茉盏、count
、callback
枢冤。
命令分組
如果程序有很多的命令行參數(shù)鸠姨,你可能想為他們進(jìn)行分組,這時可以使用 OptonGroup
group = OptionGroup(parser, ``Dangerous Options'',
``Caution: use these options at your own risk. ``
``It is believed that some of them bite.'')
group.add_option(``-g'', action=''store_true'', help=''Group option.'')
parser.add_option_group(group)
下面是將會打印出來的幫助信息:
usage: [options] arg1 arg2
options:
-h, --help show this help message and exit
-v, --verbose make lots of noise [default]
-q, --quiet be vewwy quiet (I'm hunting wabbits)
-fFILE, --file=FILE write output to FILE
-mMODE, --mode=MODE interaction mode: one of 'novice', 'intermediate'
[default], 'expert'
Dangerous Options:
Caution: use of these options is at your own risk. It is believed that
some of them bite.
-g Group option.
顯示程序版本
象 usage message 一樣淹真,你可以在創(chuàng)建 OptionParser 對象時讶迁,指定其 version 參數(shù),用于顯示當(dāng)前程序的版本信息:
parser = OptionParser(usage="%prog [-f] [-q]", version="%prog 1.0")
這樣核蘸,optparse 就會自動解釋 –version 命令行參數(shù):
$ /usr/bin/foo --version
foo 1.0
處理異常
包括程序異常和用戶異常巍糯。這里主要討論的是用戶異常,是指因用戶輸入無效的客扎、不完整的命令行參數(shù)而引發(fā)的異常祟峦。optparse 可以自動探測并處理一些用戶異常:
$ /usr/bin/foo -n 4x
usage: foo [options]
foo: error: option -n: invalid integer value: '4x'
$ /usr/bin/foo -n
usage: foo [options]
foo: error: -n option requires an argument
用戶也可以使用 parser.error() 方法來自定義部分異常的處理:
(options, args) = parser.parse_args()
[...]
if options.a and options.b:
parser.error("options -a and -b are mutually exclusive")
上面的例子,當(dāng) -b 和 -b 命令行參數(shù)同時存在時徙鱼,會打印出“options -a and -b are mutually exclusive“宅楞,以警告用戶。
如果以上的異常處理方法還不能滿足要求袱吆,你可能需要繼承 OptionParser 類厌衙,并重載 exit() 和 erro() 方法。
load python 文件
locust 框架是load 你自己寫的python 文件并解析的绞绒,這個也挺有意思的婶希。 def load_locustfile(path)
函數(shù)是實現(xiàn)這個目的的, 代碼如下:
def load_locustfile(path):
"""
Import given locustfile path and return (docstring, callables).
Specifically, the locustfile's ``__doc__`` attribute (a string) and a
dictionary of ``{'name': callable}`` containing all callables which pass
the "is a Locust" test.
"""
# Get directory and locustfile name
directory, locustfile = os.path.split(path)
# If the directory isn't in the PYTHONPATH, add it so our import will work
added_to_path = False
index = None
if directory not in sys.path:
sys.path.insert(0, directory)
added_to_path = True
# If the directory IS in the PYTHONPATH, move it to the front temporarily,
# otherwise other locustfiles -- like Locusts's own -- may scoop the intended
# one.
else:
i = sys.path.index(directory)
if i != 0:
# Store index for later restoration
index = i
# Add to front, then remove from original position
sys.path.insert(0, directory)
del sys.path[i + 1]
# Perform the import (trimming off the .py)
imported = __import__(os.path.splitext(locustfile)[0])
# Remove directory from path if we added it ourselves (just to be neat)
if added_to_path:
del sys.path[0]
# Put back in original index if we moved it
if index is not None:
sys.path.insert(index + 1, directory)
del sys.path[0]
# Return our two-tuple
locusts = dict(filter(is_locust, vars(imported).items()))
return imported.__doc__, locusts
要點(diǎn)是 imported = __import__(os.path.splitext(locustfile)[0])
導(dǎo)入你寫的python 文件蓬衡, 然后用 locusts = dict(filter(is_locust, vars(imported).items()))
進(jìn)行解析喻杈。 導(dǎo)入這個很好理解彤枢,vars()
函數(shù)是python 內(nèi)置的一個函數(shù),在輸入是對象的時候和__dict__
一致奕塑。 這里是用來獲得導(dǎo)入模塊里面的所有內(nèi)容的堂污。filter
函數(shù)也是python 內(nèi)置的一個函數(shù),用來過濾列表的龄砰,很有用哦~
協(xié)程 gevent
可以看到locust有一個web端盟猖,他是用協(xié)程起的
main_greenlet = gevent.spawn(web.start, locust_classes, options)
gevent是第三方庫,通過greenlet實現(xiàn)協(xié)程换棚,其基本思想是:
當(dāng)一個greenlet遇到IO操作時式镐,比如訪問網(wǎng)絡(luò),就自動切換到其他的greenlet固蚤,等到IO操作完成娘汞,再在適當(dāng)?shù)臅r候切換回來繼續(xù)執(zhí)行。由于IO操作非常耗時夕玩,經(jīng)常使程序處于等待狀態(tài)你弦,有了gevent為我們自動切換協(xié)程,就保證總有g(shù)reenlet在運(yùn)行燎孟,而不是等待IO禽作。
gevent 需要自己安裝,底層用C寫的揩页。協(xié)程的概念類似于CPU的中斷和輪詢旷偿,看起來像是多線程,其實只有CPU的一個core 在跑爆侣。
協(xié)程的好處:
- 跨平臺
- 跨體系架構(gòu)
- 無需線程上下文切換的開銷
- 無需原子操作鎖定及同步的開銷
- 方便切換控制流萍程,簡化編程模型
- 高并發(fā)+高擴(kuò)展性+低成本:一個CPU支持上萬的協(xié)程都不是問題。所以很適合用于高并發(fā)處理兔仰。
缺點(diǎn):
- 無法利用多核資源:協(xié)程的本質(zhì)是個單線程,它不能同時將 單個CPU 的多個核用上,協(xié)程- 需要和進(jìn)程配合才能運(yùn)行在多CPU上.當(dāng)然我們?nèi)粘K帉懙慕^大部分應(yīng)用都沒有這個必要茫负,除非是cpu密集型應(yīng)用。
- 進(jìn)行阻塞(Blocking)操作(如IO時)會阻塞掉整個程序:這一點(diǎn)和事件驅(qū)動一樣斋陪,可以使用異步IO操作來解決
Event
一個簡單event實現(xiàn)朽褪,以后可以參考
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()
core
core.py 文件是locust 數(shù)據(jù)結(jié)構(gòu)和核心
用decorate去給task加權(quán)重
def task(weight=1):
"""
Used as a convenience decorator to be able to declare tasks for a TaskSet
inline in the class. Example::
class ForumPage(TaskSet):
@task(100)
def read_thread(self):
pass
@task(7)
def create_thread(self):
pass
"""
def decorator_func(func):
func.locust_task_weight = weight
return func
"""
Check if task was used without parentheses (not called), like this::
@task
def my_task()
pass
"""
if callable(weight):
func = weight
weight = 1
return decorator_func(func)
else:
return decorator_func
用元類去改造Locust類: 加了一個變量new_tasks,并fuzhi
class TaskSetMeta(type):
"""
Meta class for the main Locust class. It's used to allow Locust classes to specify task execution
ratio using an {task:int} dict, or a [(task0,int), ..., (taskN,int)] list.
"""
def __new__(mcs, classname, bases, classDict):
new_tasks = []
for base in bases:
if hasattr(base, "tasks") and base.tasks:
new_tasks += base.tasks
if "tasks" in classDict and classDict["tasks"] is not None:
tasks = classDict["tasks"]
if isinstance(tasks, dict):
tasks = six.iteritems(tasks)
for task in tasks:
if isinstance(task, tuple):
task, count = task
for i in xrange(0, count):
new_tasks.append(task)
else:
new_tasks.append(task)
for item in six.itervalues(classDict):
if hasattr(item, "locust_task_weight"):
for i in xrange(0, item.locust_task_weight):
new_tasks.append(item)
classDict["tasks"] = new_tasks
return type.__new__(mcs, classname, bases, classDict)