性能测试工具Locust

一、写在前面

官网: https://www.locust.io/

官方使用文档:https://docs.locust.io/en/latest/

大并发量测试时,建议在linux系统下进行。

 

二、Locust安装

1.1、   --->  pip3 install locust 

1.2 、 通过GitHub上克隆项目安装(Python3推荐):https://github.com/locustio/locust  ,然后执行     ...\locust> python setup.py install

 2、安装 pyzmq

 If you intend to run Locust distributed across multiple processes/machines, we recommend you to also install pyzmq.

 如果打算运行Locust 分布在多个进程/机器,需要安装pyzmq.

 通过pip命令安装。 />  pip install pyzmq

3、安装成功,CMD敲入命令验证。 /> locust --help

Options:
  -h, --help            show this help message and exit
  -H HOST, --host=HOST  Host to load test in the following format:
                        http://10.21.32.33
  --web-host=WEB_HOST   Host to bind the web interface to. Defaults to '' (all
                        interfaces)
  -P PORT, --port=PORT, --web-port=PORT
                        Port on which to run web host
  -f LOCUSTFILE, --locustfile=LOCUSTFILE
                        Python module file to import, e.g. '../other.py'.
                        Default: locustfile
  --master              Set locust to run in distributed mode with this
                        process as master
  --slave               Set locust to run in distributed mode with this
                        process as slave
  --master-host=MASTER_HOST
                        Host or IP address of locust master for distributed
                        load testing. Only used when running with --slave.
                        Defaults to 127.0.0.1.
  --master-port=MASTER_PORT
                        The port to connect to that is used by the locust
                        master for distributed load testing. Only used when
                        running with --slave. Defaults to 5557. Note that
                        slaves will also connect to the master node on this
                        port + 1.
  --master-bind-host=MASTER_BIND_HOST
                        Interfaces (hostname, ip) that locust master should
                        bind to. Only used when running with --master.
                        Defaults to * (all available interfaces).
  --master-bind-port=MASTER_BIND_PORT
                        Port that locust master should bind to. Only used when
                        running with --master. Defaults to 5557. Note that
                        Locust will also use this port + 1, so by default the
                        master node will bind to 5557 and 5558.
  --no-web              Disable the web interface, and instead start running
                        the test immediately. Requires -c and -r to be
                        specified.
  -c NUM_CLIENTS, --clients=NUM_CLIENTS
                        Number of concurrent clients. Only used together with
                        --no-web
  -r HATCH_RATE, --hatch-rate=HATCH_RATE
                        The rate per second in which clients are spawned. Only
                        used together with --no-web
  -n NUM_REQUESTS, --num-request=NUM_REQUESTS
                        Number of requests to perform. Only used together with
                        --no-web
  -L LOGLEVEL, --loglevel=LOGLEVEL
                        Choose between DEBUG/INFO/WARNING/ERROR/CRITICAL.
                        Default is INFO.
  --logfile=LOGFILE     Path to log file. If not set, log will go to
                        stdout/stderr
  --print-stats         Print stats in the console
  --only-summary        Only print the summary stats
  -l, --list            Show list of possible locust classes and exit
  --show-task-ratio     print table of the locust classes' task execution
                        ratio
  --show-task-ratio-json
                        print json data of the locust classes' task execution
                        ratio
  -V, --version         show program's version number and exit

参数说明:

-h, --help    查看帮助
-H HOST, --host=HOST    指定被测试的主机,采用以格式:http://10.21.32.33
--web-host=WEB_HOST    指定运行 Locust Web 页面的主机,默认为空 ''-P PORT, --port=PORT, --web-port=PORT    指定 --web-host 的端口,默认是8089
-f LOCUSTFILE, --locustfile=LOCUSTFILE    指定运行 Locust 性能测试文件,默认为: locustfile.py
--csv=CSVFILEBASE, --csv-base-name=CSVFILEBASE    以CSV格式存储当前请求测试数据。
--master    Locust 分布式模式使用,当前节点为 master 节点。
--slave    Locust 分布式模式使用,当前节点为 slave 节点。
--master-host=MASTER_HOST    分布式模式运行,设置 master 节点的主机或 IP 地址,只在与 --slave 节点一起运行时使用,默认为:127.0.0.1.
--master-port=MASTER_PORT    分布式模式运行, 设置 master 节点的端口号,只在与 --slave 节点一起运行时使用,默认为:5557。注意,slave 节点也将连接到这个端口+1 上的 master 节点。
--master-bind-host=MASTER_BIND_HOST    Interfaces (hostname, ip) that locust master should bind to. Only used when running with --master. Defaults to * (all available interfaces).
--master-bind-port=MASTER_BIND_PORT    Port that locust master should bind to. Only used when running with --master. Defaults to 5557. Note that Locust will also use this port + 1, so by default the master node will bind to 5557 and 5558.
--expect-slaves=EXPECT_SLAVES    How many slaves master should expect to connect before starting the test (only when --no-web used).
--no-web    no-web 模式运行测试,需要 -c 和 -r 配合使用.
-c NUM_CLIENTS, --clients=NUM_CLIENTS    指定并发用户数,作用于 --no-web 模式。
-r HATCH_RATE, --hatch-rate=HATCH_RATE    指定每秒启动的用户数,作用于 --no-web 模式。
-t RUN_TIME, --run-time=RUN_TIME    设置运行时间, 例如: (300s, 20m, 3h, 1h30m). 作用于 --no-web 模式。
-L LOGLEVEL, --loglevel=LOGLEVEL    选择 log 级别(DEBUG/INFO/WARNING/ERROR/CRITICAL). 默认是 INFO.
--logfile=LOGFILE    日志文件路径。如果没有设置,日志将去 stdout/stderr
--print-stats    在控制台中打印数据
--only-summary    只打印摘要统计
--no-reset-stats    Do not reset statistics once hatching has been completed。
-l, --list    显示测试类, 配置 -f 参数使用
--show-task-ratio    打印 locust 测试类的任务执行比例,配合 -f 参数使用.
--show-task-ratio-json    以 json 格式打印 locust 测试类的任务执行比例,配合 -f 参数使用.
-V, --version    查看当前 Locust 工具的版本.

 

4、Locust主要由下面的几个库构成:

1) gevent

gevent是一种基于协程的Python网络库,它用到Greenlet提供的,封装了libevent事件循环的高层同步API。

2) flask

Python编写的轻量级Web应用框架。

3) requests

Python Http库

4) msgpack-python

MessagePack是一种快速、紧凑的二进制序列化格式,适用于类似JSON的数据格式。msgpack-python主要提供MessagePack数据序列化及反序列化的方法。

5) six

Python2和3兼容库,用来封装Python2和Python3之间的差异性

6) pyzmq

pyzmq是zeromq(一种通信队列)的Python绑定,主要用来实现Locust的分布式模式运行

当我们在安装 Locust 时,它会检测我们当前的 Python 环境是否已经安装了这些库,如果没有安装,它会先把这些库一一装上。并且对这些库版本有要求,有些是必须等于某版本,有些是大于某版本。我们也可以事先把这些库全部按要求装好,再安装Locust时就会快上许多。

 

三、编写接口压测脚本文件locustfile.py

 

 1 from locust import HttpLocust, TaskSet, task
 2   
 3 class ScriptTasks(TaskSet):
 4 def on_start(self):
 5 self.client.post("/login", {
 6 "username": "test",
 7 "password": "123456"
 8 })
 9   
10 @task(2)
11 def index(self):
12 self.client.get("/")
13   
14 @task(1)
15 def about(self):
16 self.client.get("/about/")
17  
18 @task(1)
19 def demo(self):
20 payload={}
21 headers={}
22 self.client.post("/demo/",data=payload, headers=headers)
23   
24 class WebsiteUser(HttpLocust):
25 task_set = ScriptTasks
26 host = "http://example.com" 
27 min_wait = 1000
28 max_wait = 5000

脚本解读

1、创建ScriptTasks()类继承TaskSet类:  用于定义测试业务。

2、创建index()、about()、demo()方法分别表示一个行为,访问http://example.com。用@task() 装饰该方法为一个任务。1、2表示一个Locust实例被挑选执行的权重,数值越大,执行频率越高。在当前ScriptTasks()行为下的三个方法得执行比例为2:1:1

3、WebsiteUser()类: 用于定义模拟用户。

4、task_set :  指向一个定义了的用户行为类。

5、host:   指定被测试应用的URL的地址

6、min_wait :   用户执行任务之间等待时间的下界,单位:毫秒。

7、max_wait :   用户执行任务之间等待时间的上界,单位:毫秒。

脚本使用场景解读

在这个示例中,定义了针对http://example.com网站的测试场景:先模拟用户登录系统,然后随机地访问首页(/)和关于页面(/about/),请求比例为2:1,demo方法主要用来阐述client对post接口的处理方式;并且,在测试过程中,两次请求的间隔时间为1->5秒间的随机值。

从脚本中可以看出,脚本主要包含两个类,一个是WebsiteUser(继承自HttpLocust,而HttpLocust继承自Locust),另一个是ScriptTasks(继承自TaskSet)。事实上,在Locust的测试脚本中,所有业务测试场景都是在LocustTaskSet两个类的继承子类中进行描的。

那如何理解LocustTaskSet这两个类呢?简单地说,Locust类就好比是一群蝗虫,而每一只蝗虫就是一个类的实例。相应的,TaskSet类就好比是蝗虫的大脑,控制着蝗虫的具体行为,即实际业务场景测试对应的任务集。

 

四、Locust类

实例脚本

伪代码:

from locust import HttpLocust, TaskSet, task

class WebsiteTasks(TaskSet):
    def on_start(self):   #进行初始化的工作,每个Locust用户开始做的第一件事
        payload = {
            "username": "test_user",
            "password": "123456",
        }
        header = {
            "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
        }
        self.client.post("/login",data=payload,headers=header)#self.client属性使用Python request库的所有方法,调用和使用方法和requests完全一致;

    @task(5)    #通过@task()装饰的方法为一个事务,方法的参数用于指定该行为的执行权重,参数越大每次被虚拟用户执行的概率越高,默认为1
    def index(self):
        self.client.get("/")

    @task(1)
    def about(self):
        self.client.get("/about/")

class WebsiteUser(HttpLocust):
    host     = "https://github.com/" #被测系统的host,在终端中启动locust时没有指定--host参数时才会用到
    task_set = WebsiteTasks          #TaskSet类,该类定义用户任务信息,必填。这里就是:WebsiteTasks类名,因为该类继承TaskSet;
    min_wait = 5000  #每个用户执行两个任务间隔时间的上下限(毫秒),具体数值在上下限中随机取值,若不指定默认间隔时间固定为1秒
    max_wait = 15000

 伪代码中对https://github.com/网站的测试场景,先模拟用户登录系统,然后随机访问首页/和/about/,请求比例5:1,并且在测试过程中,两次请求的间隔时间1-5秒的随机值;

    on_start方法,在正式执行测试前执行一次,主要用于完成一些初始化的工作,例如登录操作;

WebsiteTasks类中如何去调用 WebsiteUser(HttpLocust)类中定义的字段和方法呢?

 通过在WebsiteTasks类中self.locust.xxoo      xxoo就是我们在WebsiteUser类中定义的字段或方法;

伪代码:

from locust import HttpLocust, TaskSet, task
import hashlib
import queue
 
class WebsiteTasks(TaskSet):
 
    @task(5)
    def index(self):
        data = self.locust.user_data_queue  #获取WebsiteUser里面定义的ser_data_queue队列
        md5_data=self.locust.md5_encryption() #获取WebsiteUser里面定义的md5_encryption()方法
        self.client.get("/")
 
class WebsiteUser(HttpLocust):
    host     = "https://github.com/"
    task_set = WebsiteTasks
    min_wait = 5000
    max_wait = 15000
    user_data_queue = queue.Queue()
 
    def md5_encryption(self,star):
        '''md5加密方法'''
        obj    = hashlib.md5()
        obj.update(bytes(star,encoding="utf-8"))
        result = obj.hexdigest()
        return result

伪代码中测试场景如何表达?

代码主要包含两个类:

  1. WebsiteUser继承(HttpLocust,而HttpLocust继承自Locust)
  2. WebsiteTasks继承(TaskSet)

在Locust测试脚本中,所有业务测试场景都是在Locust和TaskSet两个类的继承子类中进行描述;

简单说:Locust类就类似一群蝗虫,而每只蝗虫就是一个类的实例。TaskSet类就类似蝗虫的大脑,控制蝗虫的具体行为,即实际业务场景测试对应的任务集;

源码中:class Locust(object)和class HttpLocust(Locust)

 1 class Locust(object):
 2     """
 3     Represents a "user" which is to be hatched and attack the system that is to be load tested.
 4      
 5     The behaviour of this user is defined by the task_set attribute, which should point to a
 6     :py:class:`TaskSet <locust.core.TaskSet>` class.
 7      
 8     This class should usually be subclassed by a class that defines some kind of client. For
 9     example when load testing an HTTP system, you probably want to use the
10     :py:class:`HttpLocust <locust.core.HttpLocust>` class.
11     """
12      
13     host = None
14     """Base hostname to swarm. i.e: http://127.0.0.1:1234"""
15      
16     min_wait = 1000
17     """Minimum waiting time between the execution of locust tasks"""
18      
19     max_wait = 1000
20     """Maximum waiting time between the execution of locust tasks"""
21      
22     task_set = None
23     """TaskSet class that defines the execution behaviour of this locust"""
24      
25     stop_timeout = None
26     """Number of seconds after which the Locust will die. If None it won't timeout."""
27  
28     weight = 10
29     """Probability of locust being chosen. The higher the weight, the greater is the chance of it being chosen."""
30          
31     client = NoClientWarningRaiser()
32     _catch_exceptions = True
33      
34     def __init__(self):
35         super(Locust, self).__init__()
36      
37     def run(self):
38         try:
39             self.task_set(self).run()
40         except StopLocust:
41             pass
42         except (RescheduleTask, RescheduleTaskImmediately) as e:
43  
44 class HttpLocust(Locust):
45     """
46     Represents an HTTP "user" which is to be hatched and attack the system that is to be load tested.
47      
48     The behaviour of this user is defined by the task_set attribute, which should point to a
49     :py:class:`TaskSet <locust.core.TaskSet>` class.
50      
51     This class creates a *client* attribute on instantiation which is an HTTP client with support
52     for keeping a user session between requests.
53     """
54      
55     client = None
56     """
57     Instance of HttpSession that is created upon instantiation of Locust.
58     The client support cookies, and therefore keeps the session between HTTP requests.
59     """
60     def __init__(self):
61            super(HttpLocust, self).__init__()
62            if self.host is None:
63                raise LocustError("You must specify the base host. Either in the host attribute in the Locust class, or on the command line using the --host option.")
64       self.client = HttpSession(base_url=self.host)

 在Locust类中,静态字段client即客户端的请求方法,这里的client字段没有绑定客户端请求方法,因此在使用Locust时,需要先继承Locust类class HttpLocust(Locust),然后在self.client =HttpSession(base_url=self.host)绑定客户端请求方法;

   对于常见的HTTP(s)协议,Locust已经实现了HttpLocust类,其self.client=HttpSession(base_url=self.host),而HttpSession继承自requests.Session。因此在测试HTTP(s)的Locust脚本中,可以通过client属性来使用Python requests库的所 有方法,调用方式与      reqeusts完全一致。另外,由于requests.Session的使用,client的方法调用之间就自动具有了状态记忆功能。常见的场景就是,在登录系统后可以维持登录状态的Session,从而后续HTTP请求操作都能带上登录状态;

Locust类中,除了client属性,还有几个属性需要关注:

  • task_set ---> 指向一个TaskSet类,TaskSet类定义了用户的任务信息,该静态字段为必填;
  • max_wait/min_wait ---> 每个用户执行两个任务间隔的上下限(毫秒),具体数值在上下限中随机取值,若不指定则默认间隔时间为1秒;
  • host    --->被测试系统的host,当在终端中启动locust时没有指定--host参数时才会用到;
  • weight--->同时运行多个Locust类时,用于控制不同类型的任务执行权重;

Locust流程,测试开始后,每个虚拟用户(Locust实例)运行逻辑都会遵守如下规律:

  1. 先执行WebsiteTasks中的on_start(只执行一次),作为初始化;
  2. 从WebsiteTasks中随机挑选(如果定义了任务间的权重关系,那么就按照权重关系随机挑选)一个任务执行;
  3. 根据Locust类中min_wait和max_wait定义的间隔时间范围(如果TaskSet类中也定义了min_wait或者max_wait,以TaskSet中的优先),在时间范围中随机取一个值,休眠等待;
  4. 重复2~3步骤,直到测试任务终止;

class TaskSet

       TaskSet类实现了虚拟用户所执行任务的调度算法,包括规划任务执行顺序(schedule_task)、挑选下一个任务(execute_next_task)、执行任务(execute_task)、休眠等待(wait)、中断控制(interrupt)等待。在此基础上,就可以在TaskSet子类中采用非常简洁的方式来描述虚拟用户的业务测试场景,对虚拟用户的所有行为进行组织和描述,并可以对不同任务的权重进行配置。

@task

    通过@task()装饰的方法为一个事务。方法的参数用于指定该行为的执行权重。参数越大每次被虚拟用户执行的概率越高。如果不设置默认为1。

    TaskSet子类中定义任务信息时,采取两种方式:@task装饰器和tasks属性。

采用@task装饰器定义任务信息时:

from locust import TaskSet, task

class UserBehavior(TaskSet):
    @task(1)
    def test_job1(self):
        self.client.get('/test1')

    @task(3)
    def test_job2(self):
        self.client.get('/test2')

采用tasks属性定义任务信息时

from locust import TaskSet

def test_job1(obj):
    obj.client.get('/test1')

def test_job2(obj):
    obj.client.get('/test2')

class UserBehavior(TaskSet):
    tasks = {test_job1:1, test_job2:3}
    # tasks = [(test_job1,1), (test_job1,3)] # 两种方式等价

上面两种定义任务信息方式中,均设置了权重属性,即执行test_job2的频率是test_job1的两倍。

若不指定,默认比例为1:1。

 

高级用法:

关联

在某些请求中,需要携带之前response中提取的参数,常见场景就是session_id。Python中可用通过re正则匹配,对于返回的html页面,可用采用lxml库来定位获取需要的参数;

from locust import HttpLocust, TaskSet, task
from lxml import etree

class WebsiteTasks(TaskSet):

    def get_session(self,html): #关联例子
        tages = etree.HTML(html)
        return tages.xpath("//div[@class='btnbox']/input[@name='session']/@value")[0]

    def on_start(self):
        html = self.client.get('/index')
        session = self.get_session(html.text)
        payload = {
            "username": "test_user",
            "password": "123456",
            'session' : session
        }
        header = {
            "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
        }
        self.client.post("/login",data=payload,headers=header)

    @task(5)
    def index(self):
        self.client.get("/")
        assert response['ErrorCode']==0   #断言

    @task(1)
    def about(self):
        self.client.get("/about/")

class WebsiteUser(HttpLocust):
    host     = "https://github.com/"
    task_set = WebsiteTasks
    min_wait = 5000
    max_wait = 15000

 

参数化

作用:循环取数据,数据可重复使用

 例如:模拟3个用户并发请求网页,共有100个URL地址,每个虚拟用户都会依次循环加载100个URL地址

from locust import TaskSet, task, HttpLocust
class UserBehavior(TaskSet):
    def on_start(self):
        self.index = 0
    @task
    def test_visit(self):
        url = self.locust.share_data[self.index]
        print('visit url: %s' % url)
        self.index = (self.index + 1) % len(self.locust.share_data)
        self.client.get(url)
class WebsiteUser(HttpLocust):
    host = 'http://debugtalk.com'
    task_set = UserBehavior
    share_data = ['url1', 'url2', 'url3', 'url4', 'url5']
    min_wait = 1000
    max_wait = 3000

 保证并发测试数据唯一性,不循环取数据;

 所有并发虚拟用户共享同一份测试数据,并且保证虚拟用户使用的数据不重复;

例如:模拟3用户并发注册账号,共有9个账号,要求注册账号不重复,注册完毕后结束测试:

采用队列

from locust import TaskSet, task, HttpLocust
import queue
class UserBehavior(TaskSet):
    @task
    def test_register(self):
        try:
            data = self.locust.user_data_queue.get()
        except queue.Empty:
            print('account data run out, test ended.')
            exit(0)
        print('register with user: {}, pwd: {}'\
            .format(data['username'], data['password']))
        payload = {
            'username': data['username'],
            'password': data['password']
        }
        self.client.post('/register', data=payload)
class WebsiteUser(HttpLocust):
    host = 'http://debugtalk.com'
    task_set = UserBehavior
    user_data_queue = queue.Queue()
    for index in range(100):
        data = {
            "username": "test%04d" % index,
            "password": "pwd%04d" % index,
            "email": "test%04d@debugtalk.test" % index,
            "phone": "186%08d" % index,
        }
        user_data_queue.put_nowait(data)
    min_wait = 1000
    max_wait = 3000

保证并发测试数据唯一性,循环取数据;

所有并发虚拟用户共享同一份测试数据,保证并发虚拟用户使用的数据不重复,并且数据可循环重复使用;

例如:模拟3个用户并发登录账号,总共有9个账号,要求并发登录账号不相同,但数据可循环使用;

from locust import TaskSet, task, HttpLocust
import queue
class UserBehavior(TaskSet):
    @task
    def test_register(self):
        try:
            data = self.locust.user_data_queue.get()
        except queue.Empty:
            print('account data run out, test ended')
            exit(0)
        print('register with user: {0}, pwd: {1}' .format(data['username'], data['password']))
        payload = {
            'username': data['username'],
            'password': data['password']
        }
        self.client.post('/register', data=payload)
        self.locust.user_data_queue.put_nowait(data)
class WebsiteUser(HttpLocust):
    host = 'http://debugtalk.com'
    task_set = UserBehavior
    user_data_queue = queue.Queue()
    for index in range(100):
        data = {
            "username": "test%04d" % index,
            "password": "pwd%04d" % index,
            "email": "test%04d@debugtalk.test" % index,
            "phone": "186%08d" % index,
        }
        user_data_queue.put_nowait(data)
    min_wait = 1000
    max_wait = 3000

 

断言(即检查点)

性能测试也需要设置断言么? 某些情况下是需要,比如你在请求一个页面时,就可以通过状态来判断返回的 HTTP 状态码是不是 200。

通过with self.client.get("url地址",catch_response=True) as response的形式;

response.status_code获取http响应码进行判断,失败后会加到统计错误表中;

python自带的断言assert失败后代码就不会向下走,且失败后不会被Locust报表统计进去;

默认不写参数catch_response=False断言无效,将catch_response=True才生效;

下面例子中:

首先使用python断言对接口返回值进行判断(python断言不通过,代码就不向下执行,get请求数为0),通过后对该接口的http响应是否为200进行判断;

@task
def all_interface(self):
     #豆瓣图书api为例子
     with  self.client.get("https://api.douban.com/v2/book/1220562",name="/LhcActivity/GetActConfig",catch_response=True) as response:
      assert response.json()['rating']['max']==10            #python断言对接口返回值中的max字段进行断言
            if response.status_code ==200:                   #对http响应码是否200进行判断
                response.success()
            else:
                response.failure("GetActConfig[Failed!]")

 

五、Locust运行模式

运行Locust时,通常会使用到两种运行模式:单进程运行多进程分布式运行

单进程运行模式

Locust所有的虚拟并发用户均运行在单个Python进程中,具体从使用形式上,又分为no_webweb两种形式。该种模式由于单进程的原因,并不能完全发挥压力机所有处理器的能力,因此主要用于调试脚本和小并发压测的情况。

当并发压力要求较高时,就需要用到Locust的多进程分布式运行模式。从字面意思上看,大家可能第一反应就是多台压力机同时运行,每台压力机分担负载一部分的压力生成。的确,Locust支持任意多台压力机(一主多从)的分布式运行模式,但这里说到的多进程分布式运行模式还有另外一种情况,就是在同一台压力机上开启多个slave的情况。这是因为当前阶段大多数计算机的CPU都是多处理器(multiple processor cores),单进程运行模式下只能用到一个处理器的能力,而通过在一台压力机上运行多个slave,就能调用多个处理器的能力了。比较好的做法是,如果一台压力机有N个处理器内核,那么就在这台压力机上启动一个masterNslave。当然,我们也可以启动N的倍数个slave,但是根据我的试验数据,效果跟N个差不多,因此只需要启动Nslave即可。 

no_web形式启动locust:

如果采用no_web形式,则需使用--no-web参数,并会用到如下几个参数。

  • -c, --clients:指定并发用户数;
  • -n, --num-request:指定总执行测试次数;
  • -r, --hatch-rate:指定并发加压速率,默认值位1。

示例:

$ locust -f    locustfile.py     --host = xxxxx.com   --no-web -c 1 -n 2

在此基础上,当我们想要调试Locust脚本时,就可以在脚本中需要调试的地方通过print打印日志,然后将并发数和总执行次数都指定为1

$ locust -f    locustfile.py     --host = xxxxx.com   --no-web -c 1 -n 1

执行测试

通过这种方式,我们就能很方便地对Locust脚本进行调试了。

Locust脚本调试通过后,就算是完成了所有准备工作,可以开始进行压力测试了。

web形式启动locust:

如果采用web形式,,则通常情况下无需指定其它额外参数,Locust默认采用8089端口启动web;如果要使用其它端口,就可以使用如下参数进行指定。

  • -P, --port:指定web端口,默认为8089.
  •  终端中--->进入到代码目录: locust     -f    locustfile.py     --host = xxxxx.com      
  • -f            指定性能测试脚本文件
  • -host      被测试应用的URL地址【如果不填写,读取继承(HttpLocust)类中定义的host】
  • 如果Locust运行在本机,在浏览器中访问http://localhost:8089即可进入Locust的Web管理页面;如果Locust运行在其它机器上,那么在浏览器中访问http://locust_machine_ip:8089即可。

多进程分布式运行

不管是单机多进程,还是多机负载模式,运行方式都是一样的,都是先运行一个master,再启动多个slave

启动master时,需要使用--master参数;同样的,如果要使用8089以外的端口,还需要使用-P, --port参数。

D:\workSpaces\ApiAutoTest\TestCases\OpsUltraAPITest\MonitorAPITest>locust -f monitorAgent.py --master --port=8089
[2018-06-05 15:36:30,654] dengshihuang/INFO/locust.main: Starting web monitor at *:8089
[2018-06-05 15:36:30,684] dengshihuang/INFO/locust.main: Starting Locust 0.8.1

启动slave时需要使用--slave参数;在slave中,就不需要再指定端口了。master启动后,还需要启动slave才能执行测试任务。

D:\workSpaces\ApiAutoTest\TestCases\OpsUltraAPITest\MonitorAPITest>locust -f monitorAgent.py --slave
[2018-06-05 15:36:30,654] dengshihuang/INFO/locust.main: Starting web monitor at *:8089
[2018-06-05 15:36:30,684] dengshihuang/INFO/locust.main: Starting Locust 0.8.1
D:\workSpaces\ApiAutoTest\TestCases\OpsUltraAPITest\MonitorAPITest>locust -f monitorAgent.py --slave --master-host=<locust_machine_ip>

masterslave都启动完毕后,就可以在浏览器中通过http://locust_machine_ip:8089进入Locust的Web管理页面了。使用方式跟单进程web形式完全相同,只是此时是通过多进程负载来生成并发压力,在web管理界面中也能看到实际的slave数量。如果slavemaster不在同一台机器上,还需要通过--master-host参数再指定master的IP地址。

运行结果:

 

 Number of users to simulate    设置虚拟用户数,对应中no_web模式的-c, --clients参数;

 Hatch rate(users spawned/second)每秒产生(启动)的虚拟用户数 , 对应着no_web模式的-r, --hatch-rate参数,默认为1。点击Start swarming 按钮,开始运行性能测试。

上图:启动了一个 master 和两个 slave,由两个 slave 来向被测试系统发送请求

性能测试参数

  • Type: 请求的类型,例如GET/POST。

  • Name:请求的路径。这里为百度首页,即:https://www.baidu.com/

  • request:当前请求的数量。

  • fails:当前请求失败的数量。

  • Median:中间值,单位毫秒,一半的服务器响应时间低于该值,而另一半高于该值。

  • Average:平均值,单位毫秒,所有请求的平均响应时间。

  • Min:请求的最小服务器响应时间,单位毫秒。

  • Max:请求的最大服务器响应时间,单位毫秒。

  • Content Size:单个请求的大小,单位字节。

  • reqs/sec:是每秒钟请求的个数。

 相比于LoadRunnerLocust的结果展示十分简单,主要就四个指标:并发数RPS响应时间异常率。但对于大多数场景来说,这几个指标已经足够了。

在上图中,RPS平均响应时间这两个指标显示的值都是根据最近2秒请求响应数据计算得到的统计值,我们也可以理解为瞬时值。

如果想看性能指标数据的走势,就可以在Charts栏查看。在这里,可以查看到RPS平均响应时间在整个运行过程中的波动情况。

除了以上数据,Locust还提供了整个运行过程数据的百分比统计值,例如我们常用的90%响应时间响应时间中位值;平均响应时间和错误数的统计,该数据可以通过Download response time distribution CSV和Download request statistics CSV获得,数据展示效果如下所示。

 

-----------------------------------------------------------

注意:

locust虽然使用方便,但是加压性能和响应时间上面还是有差距的,如果项目有非常大的并发加压请求,可以选择wrk

对比方法与结果:

可以准备两台服务器,服务器A作为施压方,服务器B作为承压方
服务器B上简单的运行一个nginx服务就行了

服务器A上可以安装一些常用的压测工具,比如locust、ab、wrk

我当时测下来,施压能力上 wrk > golang >> ab > locust

因为locust一个进程只使用一核CPU,所以用locust压测时,必须使用主从分布式(zeromq通讯)模式,并根据服务器CPU核数来起slave节点数

wrk约为55K QPS
golang net/http 约 45K QPS
ab 大约 15K QPS
locust 最差,而且response time明显比较长

-------------------------------------------------------------------

好文推荐:

1、https://debugtalk.com/post/locustplus-talk-about-performance-test/

这篇博客从性能测试方法、性能瓶颈定位、性能测试工具的基本组成、性能测试工具推荐(比较了loadrunner,jmeter,Locust优缺点)等方面做了深入的介绍,推荐!

蝗虫比Jmeter好的一点就是高并发,但是相对的不好的地方也有,就是需要另外的工具去监控服务器,而且需要去编写代码。

posted @ 2019-01-09 16:12  艾里_Simple  阅读(14710)  评论(1编辑  收藏