locust 源代碼解析

Locust 是一個開源的python壓力測試的框架,代碼相對簡單椭赋,涵蓋的東西卻不少抚岗。 修改起來挺方便,適合學(xué)習(xí)一個項目哪怔。

github: https://github.com/locustio/locust

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_constappend 茉盏、countcallback 枢冤。

命令分組

如果程序有很多的命令行參數(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)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末无虚,一起剝皮案震驚了整個濱河市缔赠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌友题,老刑警劉巖嗤堰,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡踢匣,警方通過查閱死者的電腦和手機(jī)告匠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來离唬,“玉大人后专,你說我怎么就攤上這事∈漭海” “怎么了戚哎?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嫂用。 經(jīng)常有香客問我型凳,道長,這世上最難降的妖魔是什么嘱函? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任甘畅,我火速辦了婚禮,結(jié)果婚禮上往弓,老公的妹妹穿的比我還像新娘疏唾。我一直安慰自己,他們只是感情好函似,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布荸实。 她就那樣靜靜地躺著,像睡著了一般缴淋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上泄朴,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天重抖,我揣著相機(jī)與錄音,去河邊找鬼祖灰。 笑死钟沛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的局扶。 我是一名探鬼主播恨统,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼三妈!你這毒婦竟也來了畜埋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤畴蒲,失蹤者是張志新(化名)和其女友劉穎悠鞍,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體模燥,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咖祭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年掩宜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片么翰。...
    茶點(diǎn)故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡牺汤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出浩嫌,到底是詐尸還是另有隱情檐迟,我是刑警寧澤,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布固该,位于F島的核電站锅减,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏伐坏。R本人自食惡果不足惜怔匣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望桦沉。 院中可真熱鬧每瞒,春花似錦、人聲如沸纯露。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽埠褪。三九已至浓利,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钞速,已是汗流浹背贷掖。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留渴语,地道東北人苹威。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像驾凶,于是被迫代替她去往敵國和親牙甫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評論 2 349

推薦閱讀更多精彩內(nèi)容