性能测试工具 locust 性能压测工具问题求助???

王智强 · 2019年07月19日 · 最后由 nullpointer00 回复于 2020年11月30日 · 3782 次阅读

大家上午好:
今天用 locust 工具简单的压测一下接口。
发现几个问题 我先设置集合点等待 10 个用户加载进来然后一起并发 发现这个 locust 的发送请求和 jmeter 里面的不一样
jmeter 里面是设置了集合点后等待所有的用户加载完假如 10 个并发会请求同一个接口 10 次然后就停止了,用察看结果树
可以看到这 10 个用户在同一时间访问这个接口的并发情况,但是用 locust 去做并发发现我设置了 10 个并发等待所有的用
户加载进来后 requests 请求会一直在增加。而且我对 2 个接口进行压测每个接口的请求数还不是一样的,关于这块有没有
大佬帮你解释一下。

如下为代码部分:

coding=utf-8
import requests
from locust import HttpLocust,TaskSet,task
import os
from locust import events
from gevent._semaphore import Semaphore
all_locusts_spawned = Semaphore()
all_locusts_spawned.acquire()
def on_hatch_complete(**kwargs):
    all_locusts_spawned.release()  #创建钩子方法

events.hatch_complete += on_hatch_complete    #挂载到locust钩子函数(所有的Locust实例产生完成时触发)

class Cms(TaskSet):
    # 访问cms后台首页
    @task(1)
    def cms_login(self):
        # 定义请求头
        header = {
            "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36"
        }

        req = self.client.get("/cms/manage/loginJump.do?userAccount=admin&loginPwd=123456",  headers=header, verify=False)
        if req.status_code == 200:
            print("success")
        else:
            print("fails")

    @task(2)
    def cms_queryUserList(self):
        header = {
            "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36"
        }
        queryUserList_data = {
            "startCreateDate": "",
            "endCreateDate": "",
            "searchValue": "",
            "page": "1"
        }
        req = self.client.post("/cms/manage/queryUserList.do",data=queryUserList_data)
        queryUserList_url = 'http://192.168.23.132:8080/cms/manage/queryUserList.do'
        if req.status_code == 200:    #u'查询用户成功!'
            print("success")
        else:
            print("fails")

    def on_start(self):
        """ on_start is called when a Locust start before any task is scheduled """
        # self.cms_login()
        # self.cms_queryUserList()
        all_locusts_spawned.wait()    #限制在所有用户准备完成前处于等待状态

class websitUser(HttpLocust):
    task_set = Cms
    min_wait = 3000  # 单位为毫秒
    max_wait = 6000  # 单位为毫秒

if __name__ == "__main__":
    import os
    os.system("locust -f lesson8.py --host=http://192.168.23.134:8080")


'''
semaphore是一个内置的计数器:
每当调用acquire()时,内置计数器-1
每当调用release()时,内置计数器+1
计数器不能小于0,当计数器为0时,acquire()将阻塞线程直到其他线程调用release()
'''
共收到 7 条回复 时间 点赞

集合点

看测试脚本,先说明下此集合点只会在每个用户(协程)启动时,集合一次,执行过程是没有集合动作的。如下是 locust 源码,通过locust().run(runner=self)起对应协程执行各用户任务。

# runners.py 
        def hatch():
            sleep_time = 1.0 / self.hatch_rate
            while True:
                if not bucket:
                    logger.info("All locusts hatched: %s" % ", ".join(["%s: %d" % (name, count) for name, count in six.iteritems(occurence_count)]))
                    events.hatch_complete.fire(user_count=self.num_clients)
                    return

                locust = bucket.pop(random.randint(0, len(bucket)-1))
                occurence_count[locust.__name__] += 1
                def start_locust(_):
                    try:
                        locust().run(runner=self)
                    except GreenletExit:
                        pass
                new_locust = self.locusts.spawn(start_locust, locust)
                if len(self.locusts) % 10 == 0:
                    logger.debug("%i locusts hatched" % len(self.locusts))
                gevent.sleep(sleep_time)

再看run(),每个用户只会进行一次 on_start() 初始化准备工作,剩下的时间在while (True)中循环执行。同时也可以看到 locust 内部是没有集合点的,通过gevent.sleep(sleep_time)控制每秒加载用户,在此之前加载的用户已经在各自协程中执行任务了。

# core.py
def run(self, *args, **kwargs):
    self.args = args
    self.kwargs = kwargs

    try:
        if hasattr(self, "on_start"):
            self.on_start()
    except InterruptTaskSet as e:
        if e.reschedule:
            six.reraise(RescheduleTaskImmediately, RescheduleTaskImmediately(e.reschedule), sys.exc_info()[2])
        else:
            six.reraise(RescheduleTask, RescheduleTask(e.reschedule), sys.exc_info()[2])

    while (True):
        try:
            if self.locust.stop_timeout is not None and time() - self._time_start > self.locust.stop_timeout:
                return

            if not self._task_queue:
                self.schedule_task(self.get_next_task())

            try:
                self.execute_next_task()
            except RescheduleTaskImmediately:
                pass
            except RescheduleTask:
                self.wait()
            else:
                self.wait()

楼主的集合点放到了on_start()中,只会集合一次,就是初始化的时候。另外再看 locust 用户任务执行。

请求数量

locust 每个协程(用户)运行任务时,具体任务执行有两种方式:@task修饰的权重随机任务(TaskSet),和@seq_task优先级顺序任务(TaskSequence),楼主采用的是@task方式

# core.py

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)

源码如上可以看到,上下文源码不贴了。

以楼主代码为例 每个用户的任务集为[cms_queryUserList,cms_queryUserList,cms_login],这是根据@task权重生成的,旨在达到权重越大被执行的概率越高的效果,每次执行随机从这个列表中取一个执行,如下源码,所以出现了楼主所说请求数量不一致。

def get_next_task(self):
    return random.choice(self.tasks)

如何解决

可以尝试使用@seq_task修饰器,确定执行顺序,然后顺序执行,如下:

coding=utf-8
import requests
from locust import HttpLocust,TaskSet,task
import os
from locust import events
from gevent._semaphore import Semaphore
all_locusts_spawned = Semaphore()
all_locusts_spawned.acquire()
def on_hatch_complete(**kwargs):
    all_locusts_spawned.release()  #创建钩子方法

events.hatch_complete += on_hatch_complete    #挂载到locust钩子函数(所有的Locust实例产生完成时触发)

class Cms(TaskSequence):
    # 访问cms后台首页
    @seq_task(1)
    def cms_login(self):
        # 定义请求头
        header = {
            "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36"
        }

        req = self.client.get("/cms/manage/loginJump.do?userAccount=admin&loginPwd=123456",  headers=header, verify=False)
        if req.status_code == 200:
            print("success")
        else:
            print("fails")

    @seq_task(2)
    def cms_queryUserList(self):
        header = {
            "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36"
        }
        queryUserList_data = {
            "startCreateDate": "",
            "endCreateDate": "",
            "searchValue": "",
            "page": "1"
        }
        req = self.client.post("/cms/manage/queryUserList.do",data=queryUserList_data)
        queryUserList_url = 'http://192.168.23.132:8080/cms/manage/queryUserList.do'
        if req.status_code == 200:    #u'查询用户成功!'
            print("success")
        else:
            print("fails")

    def on_start(self):
        """ on_start is called when a Locust start before any task is scheduled """
        # self.cms_login()
        # self.cms_queryUserList()
        all_locusts_spawned.wait()    #限制在所有用户准备完成前处于等待状态

class websitUser(HttpLocust):
    task_set = Cms
    min_wait = 3000  # 单位为毫秒
    max_wait = 6000  # 单位为毫秒

if __name__ == "__main__":
    import os
    os.system("locust -f lesson8.py --host=http://192.168.23.134:8080")


'''
semaphore是一个内置的计数器:
每当调用acquire()时,内置计数器-1
每当调用release()时,内置计数器+1
计数器不能小于0,当计数器为0时,acquire()将阻塞线程直到其他线程调用release()
'''
织梦 回复

感谢答复:
cms_login 和 cms_queryUserList 方法权重我都改为 1 了
但是用@seq_task(1) 装饰器设置顺序执行后这 2 个接口的请求数量可以进行控制吗?
就是我设置请求数量为 200 每个接口发送 200 个请求就自动停止。

王智强 回复

不能设置请求数量,只能设置执行时间,-t 参数

@task(2) 是指权重为 2, @task(1) 指权重为 1,那么它们装饰的函数执行次数通常是 2:1。所以请求数量上 815:391 是正常的。
可以把权重都设置为 1,或者留空。基本上就是 1:1 的数量。

locust 支持 tcp 么,我一直有这个疑问

萝卜 回复

只要有闲工夫自己写个 client,locust 啥都可以压,推荐用 golang 写的 slave 做压测,locust 能下发的压力有限,没有官网说的那么 6B。。。

楼主你好 这个问题请问你解决了吗

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册