Locust 2.8 使用文档 - 基础篇

Locust2.8使用文档

简介

locust是一个基于Python的性能测试工具,有较高的扩展性,可以用任何Python代码来定义用户行为

以下是locust2.8版本的使用总结,以及官方文档摘录,只提到我自己使用过的部分

文档未列出的部分可以参考官方文档:

http://docs.locust.io/en/stable/

安装

  • pip安装,推荐在pycharm中使用pipenv
pip3 install locust

编写locustfile

  • 官方文档示例:
import time
from locust import HttpUser, task, between


# QuickstartUser继承于HttpUser这个类,HttpUser为每一个用户提供一个client属性
# 该属性是HttpSession的实例,可以发出http请求
# locust将为每一个user创建一个HttpSession的实例,每个用户都在自己的线程中运行
class QuickstartUser(HttpUser):
    # wait_time属性可以让user在执行一个任务之后,等待1-5s
    wait_time = between(1, 5)
	
    # locust会执行task修饰过的方法
    # 当前的两个task是同时进行的
    @task
    def hello_world(self):
        self.client.get("/hello")
        self.client.get("/world")
	
    # task可以设置权重,view_items被执行的概率是hello_world的三倍
    @task(3)
    def view_items(self):
        for item_id in range(10):
            # 页面的统计信息是以url进行分组的,这里指定的name属性将所有的请求分组到/item下
            self.client.get(f"/item?id={item_id}", name="/item")
            time.sleep(1)
	
    # 在每个用户启动之前运行
    def on_start(self):
        self.client.post("/login", json={"username":"foo", "password":"bar"})
        
# tips:HttpUser不是真正的浏览器,因此不会解析HTML响应来加载资源或呈现页面。但它会跟踪cookie。

在这里插入图片描述

运行:
123

User类

Locust 将为正在模拟的每个用户生成一个 User 类的实例。用户类可以定义一些通用属性。

wait_time属性

在每次任务执行后引入延迟,如果未指定wait_time,则下一个任务将在完成后立即执行。

  • constant 固定时间

    class MyUser(User):
        wait_time = constant(3)
    
  • between 最小值和最大值之间的随机时间

    class MyUser(User):
        wait_time = between(3.0, 10.5)
    
  • constant_throughput用于确保任务每秒运行(最多)X 次的自适应时间。

    在以下示例中,无论任务执行时间如何,任务总是每 10 秒执行一次:

    class MyUser(User):
        wait_time = constant_throughput(0.1)
        @task
        def my_task(self):
            time.sleep(random.random())
    
  • constant_pacing用于确保任务每 X 秒(最多)运行一次的自适应时间

    在以下示例中,无论任务执行时间如何,任务总是每 10 秒执行一次:

    class MyUser(User):
        wait_time = constant_pacing(10)
        @task
        def my_task(self):
            time.sleep(random.random())
    
weight 和 fixed_count 属性

如果文件中存在多个用户类,并且在命令行上没有指定用户类,Locust 将生成相同数量的每个用户类。您还可以通过将它们作为命令行参数传递来指定要使用同一 locustfile 中的哪些用户类:

locust -f locust_file.py WebUser MobileUser

如果想模拟更多特定类型的用户,可以在这些类上设置权重属性。例如,WebUser的可能性是MobileUser的三倍:

class WebUser(User):
    weight = 3
    pass

class MobileUser(User):
    weight = 1
    pass

设置fixed_count属性。在这种情况下,权重属性将被忽略,并且将产生精确计数的用户。这些用户首先产生。在下面的示例中,将生成 AdminUser 的唯一实例以进行一些特定的工作,更准确地控制请求计数,而与总用户计数无关。

class AdminUser(User):
    wait_time = constant(600)
    fixed_count = 1

    @task
    def restart_app(self):
        pass

class WebUser(User):
    pass
on_start 和 on_stop 方法

on_start :

在用户开始运行时调用的方法,对于TaskSet,在模拟用户开始执行该TaskSet时调用

on_stop :

在用户停止运行时调用的方法,对于TaskSet,在模拟用户停止执行该TaskSet时调用

task

@task

locust会执行task修饰过的方法

@task接受一个可选的权重参数,可用于指定任务的执行率。在以下示例中,task2 的执行率是 task1 的2倍:

from locust import User, task, between

class MyUser(User):
    wait_time = between(5, 15)

    @task(3)
    def task1(self):
        pass

    @task(6)
    def task2(self):
        pass
@tasks

tasks属性可以是一个 Task 列表,也可以是一个< Task : int> dict

Task 可以是 python 可调用对象,也可以是TaskSet类。

如果任务是普通的 python 函数,它会收到一个参数,即正在执行任务的用户实例。

普通python函数示例:

from locust import User, constant

def my_task(user):
    pass

class MyUser(User):
    tasks = [my_task]
    wait_time = constant(1)
# <Task : int> dict
tasks = {my_task: 3, another_task: 1}
# 最终执行方式
random.choice([my_task, my_task, my_task, another_task])
@tag 装饰器

通过@tag装饰器标记任务

– tags tag1(运行过程中只会执行task1task2

–exclude-tags tag3 取反,排除哪些(运行过程中只会执行task1task2task4

from locust import User, constant, task, tag

class MyUser(User):
    wait_time = constant(1)

    @tag('tag1')
    @task
    def task1(self):
        pass

    @tag('tag1', 'tag2')
    @task
    def task2(self):
        pass

    @tag('tag3')
    @task
    def task3(self):
        pass

    @task
    def task4(self):
        pass

Evens

test_start 和 test_stop

负载开始前和结束后运行

from locust import events

@events.test_start.add_listener
def on_test_start(environment, **kwargs):
    print("A new test is starting")

@events.test_stop.add_listener
def on_test_stop(environment, **kwargs):
    print("A new test is ending")
init

在每个 Locust 进程开始时触发。分布式负载时在每个工作进程中做初始化

from locust import events
from locust.runners import MasterRunner

@events.init.add_listener
def on_locust_init(environment, **kwargs):
    if isinstance(environment.runner, MasterRunner):
        print("I'm on master node")
    else:
        print("I'm on a worker or standalone node")

HttpUser 类

HttpUser 添加了一个 client 属性用于发出 HTTP 请求的属性。

from locust import HttpUser, task, between

class MyUser(HttpUser):
    wait_time = between(5, 15)

    @task(4)
    def index(self):
        self.client.get("/")

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

client 是 HttpSession 的一个实例。HttpSession 是 requests.Session 的子类。

HttpSession 主要是将请求结果报告给 Locust(成功/失败、响应时间、响应长度、名称)。

client 包含所有 HTTP 方法的方法:get、post、put ……

client 在请求之间保留 cookie。

发出 POST 请求,查看响应并传递 cookie 给下一个请求

response = self.client.post("/login", {"username":"testuser", "password":"secret"})
print("Response status code:", response.status_code)
print("Response text:", response.text)
response = self.client.get("/my-profile")
响应验证

如果 HTTP 响应代码正常(<400),则请求被认为是成功的

可以使用 catch_response 参数、with 语句以及 response.failure() 的调用来自定义标记请求的失败原因

with self.client.get("/", catch_response=True) as response:
    if response.text != "Success":
        response.failure("Got wrong response")
    elif response.elapsed.total_seconds() > 0.5:
        response.failure("Request took too long")

即使响应代码错误,也可以将请求标记为成功:

with self.client.get("/does_not_exist/", catch_response=True) as response:
    if response.status_code == 404:
        response.success()

可以通过抛出异常然后在 with 之外捕获异常来避免记录请求。或者抛出一个 Locust异常,如下例所示,然后让 Locust 捕获它。

from locust.exception import RescheduleTask

with self.client.get("/does_not_exist/", catch_response=True) as response:
    if response.status_code == 404:
        raise RescheduleTask()

下面是一个调用 REST API 并验证响应的示例:

from json import JSONDecodeError
...
with self.client.post("/", json={"foo": 42, "bar": None}, catch_response=True) as response:
    try:
        if response.json()["greeting"] != "hello":
            response.failure("Did not get expected value in greeting")
    except JSONDecodeError:
        response.failure("Response could not be decoded as JSON")
    except KeyError:
        response.failure("Response did not contain expected key 'greeting'")
请求分组

locust是根据请求进行 URL 分组的,但 URL 通常包含某个动态参数。在测试结果统计中将这些 URL 聚合在一起才是有意义的。

例子:

for i in range(10):
    self.client.get("/blog?id=%i" % i, name="/blog?id=[id]")

在某些情况下,无法将参数传递给请求函数,client.request_name 属性提供了分组请求的另一种说法

self.client.request_name="/blog?id=[id]"
for i in range(10):
    self.client.get("/blog?id=%i" % i)
self.client.request_name=None

在 with 中可以使用,client.rename_request()

@task
def multiple_groupings_example(self):

    with self.client.rename_request("/blog?id=[id]"):
        for i in range(10):
            self.client.get("/blog?id=%i" % i)

    with self.client.rename_request("/article?id=[id]"):
        for i in range(10):
            self.client.get("/article?id=%i" % i)

自定义配置locust.config

locust.config

运行时指定 --config /your/paht/locust.config

示例:

# 以 locust.config 为根路径设置你的配置参数
locustfile = locust_files/my_locust_file.py
headless = true
master = true
expect-workers = 5
host = http://target-system
users = 100
spawn-rate = 10
run-time = 10m

所有可用的配置选项

这是所有可用配置选项的表格,以及它们对应的环境和配置文件键:

命令行环境配置文件描述
-f,--locustfileLOCUST_LOCUSTFILElocustfile指定要运行的 locustfile 文件
-H,--hostLOCUST_HOSThost指定被测环境的host
-u,--usersLOCUST_USERSusers并发 Locust 用户的峰值数量。主要与–headless 或–autostart 一起使用。可以在测试期间通过键盘输入 w、W(生成 1、10 个用户)和 s、S(停止 1、10 个用户)更改
-r,--spawn-rateLOCUST_SPAWN_RATEspawn-rate每秒生成用户数。主要与 –headless 或 –autostart 一起使用
-t,--run-timeLOCUST_RUN_TIMErun-time在指定的时间后停止运行,例如(300s、20m、3h、1h30m 等)。仅与 –headless 或 –autostart 一起使用。locust 默认永远运行。
--web-hostLOCUST_WEB_HOSTweb-host将 Web 界面绑定到的主机。默认为“*”(所有接口)
--web-port,-PLOCUST_WEB_PORTweb-port运行 Web 主机的端口
--headlessLOCUST_HEADLESSheadless禁用 Web 界面,并立即开始测试。使用 -u 和 -t 控制用户数和运行时间
--autostartLOCUST_AUTOSTARTautostart立即开始测试(不禁用 Web UI)。使用 -u 和 -t 控制用户数和运行时间
--autoquitLOCUST_AUTOQUITautoquit在运行完成 X 秒后完全退出 Locust。仅与 –autostart 一起使用。默认设置是保持 Locust 运行,直到使用 CTRL+C 将其关闭
--web-authLOCUST_WEB_AUTHweb-auth为 Web 界面打开基本身份验证。应按以下格式提供:username:password
--tls-certLOCUST_TLS_CERTtls-certhttps 证书路径
--tls-keyLOCUST_TLS_KEYtls-keyhttps 证书私钥路径
--masterLOCUST_MODE_MASTERmaster设置 locust 以分布式模式运行,此进程作为主机
--master-bind-hostLOCUST_MASTER_BIND_HOSTmaster-bind-hostlocust master 应该绑定的接口(主机名、ip)。仅在使用 –master 运行时使用。默认为 *(所有可用接口)。
--master-bind-portLOCUST_MASTER_BIND_PORTmaster-bind-portlocust master 应该绑定的端口。仅在使用 –master 运行时使用。默认为 5557。
--expect-workersLOCUST_EXPECT_WORKERSexpect-workers在开始测试之前,master 应该期望连接多少个worker(仅当使用–headless/autostart 时)。
--expect-workers-max-waitLOCUST_EXPECT_WORKERS_MAX_WAITexpect-workers-max-waitmaster 在放弃之前应该等待worker 连接多长时间。默认为永远等待
--workerLOCUST_MODE_WORKERworker设置 locust 以分布式模式运行,此进程作为 worker
--master-hostLOCUST_MASTER_NODE_HOSTmaster-host指定 worker 需要连接的 master 的 host
--master-portLOCUST_MASTER_NODE_PORTmaster-port指定 worker 需要连接的 master 的端口
-T,--tagsLOCUST_TAGStags运行指定 tag 的任务
-E,--exclude-tagsLOCUST_EXCLUDE_TAGSexclude-tagstag 取反
--csvLOCUST_CSVcsv以 CSV 格式将当前请求统计信息存储到文件中。设置此选项将生成三个文件:[CSV_PREFIX]_stats.csv、[CSV_PREFIX]_stats_history.csv 和 [CSV_PREFIX]_failures.csv
--csv-full-historyLOCUST_CSV_FULL_HISTORYcsv-full-history将每个统计信息条目以 CSV 格式存储到 _stats_history.csv 文件中。您还必须指定“–csv”参数才能启用此功能。
--print-statsLOCUST_PRINT_STATSprint-stats在控制台中打印统计信息
--only-summaryLOCUST_ONLY_SUMMARYonly-summary仅打印摘要统计信息
--reset-statsLOCUST_RESET_STATSreset-stats产卵完成后重置统计信息。在分布式模式下运行时,应在 master 和 worker 上都设置
--htmlLOCUST_HTMLhtml将 HTML 报告存储到指定的文件路径
--skip-log-setupLOCUST_SKIP_LOG_SETUPskip-log-setup禁用 Locust 的日志记录设置。相反,配置由 Locust 测试或 Python 默认值提供。
--loglevel,-LLOCUST_LOGLEVELloglevel指定日志等级,默认为INFO。
--logfileLOCUST_LOGFILElogfile日志文件的路径。如果未设置,日志将转到 stderr
--exit-code-on-errorLOCUST_EXIT_CODE_ON_ERRORexit-code-on-error设置当测试结果包含任何失败或错误时使用的进程退出代码
-s,--stop-timeoutLOCUST_STOP_TIMEOUTstop-timeout在退出之前等待模拟用户完成任何正在执行的任务的秒数。默认是立即终止。该参数只需要在运行 Locust 分布式时为主进程指定。

自定义参数

init_command_line_parser 可以在脚本中自定义参数

自定义参数可以设置在 web 中显示,并且在web中修改

示例:

from locust import HttpUser, task, events


@events.init_command_line_parser.add_listener
def _(parser):
    parser.add_argument("--my-argument", type=str, env_var="LOCUST_MY_ARGUMENT", default="", help="It's working")
    # include_in_web_ui 默认为 False
    parser.add_argument("--my-ui-invisible-argument", include_in_web_ui=False, default="I am invisible")


@events.test_start.add_listener
def _(environment, **kw):
    print(f"Custom argument supplied: {environment.parsed_options.my_argument}")


class WebsiteUser(HttpUser):
    @task
    def my_task(self):
        print(f"my_argument={self.environment.parsed_options.my_argument}")
        print(f"my_ui_invisible_argument={self.environment.parsed_options.my_ui_invisible_argument}")

读取配置的优先级,从低到高:

~/locust.conf -> ./locust.conf -> (file specified using --conf) -> env vars -> cmd args

Docker下的分布式

运行 Locust 分布式时,master 和 worker 机器都必须有 locustfile 的副本。

tips

  • 因为 Python 不能在每个进程中充分地使用多个 CPU 内核,所以应该在负载机上的每个CPU内核上运行一个进程,在 Docker 下,只需要启动多个容器就行了
  • 每个内核的用户数量的设置是无限制的,取决于你 CPU 的性能
  • 如果 Locust 即将耗尽 CPU 资源,会有一个警告提醒

Locust分布式启动命令

以主机模式启动 locust:

locust -f my_locustfile.py --master

然后在每个 worker 上:

locust -f my_locustfile.py --worker --master-host=192.168.0.14

Locust的镜像制作

Dockerfile

因为编写脚本需要部分第三方库,所以我们在使用镜像的时候需要提前安装依赖,以下是 Dockerfile 示例

Locust 官方镜像:

# 从dockerhub拉取一个locust镜像
FROM locustio/locust:2.8.3

# 指定当前工作目录
WORKDIR /mnt/locust

# 添加主机当前目录下的所有文件到镜像的locust目录
ADD . .

# 在python镜像中执行安装依赖
RUN pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/

Locust 版本没有 root 权限,部分命令不能执行,所以会用 Python 的镜像

Python 版本:

# 从dockerhub拉取一个python镜像
FROM python:3.8.12

# 在python镜像的根目录创建一个mytest文件夹
RUN mkdir -p /mytest/locust

# 指定当前工作目录是mytest
# 相当于cd mytest (这么理解不是非常准确)
WORKDIR /mytest/locust

# 添加主机当前目录下的所有文件到镜像的mytest里面
ADD . .

# 在python镜像中执行安装依赖,并指定中国的时区
RUN pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ \
    && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo 'Asia/Shanghai' >/etc/timezone

docker compose

贴一个 prometheus + influxdb 的 docker-compose yml 文件,用法详见:
Docker 下 Locust + prometheus + influxdb + grafana 性能测试结果采集、监控、数据持久化

version: "2.8.3"

services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    hostname: prometheus
    restart: always
    privileged: true
    volumes:
    # prometheus 的配置以及,数据的路径
      - /prometheus/prometheus/config:/etc/prometheus
      - /prometheus/prometheus/data:/prometheus
      - /etc/localtime:/etc/localtime
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention=15d'
      - '--query.max-concurrency=50'
      - '--web.enable-lifecycle'
    ports:
      - "9090:9090"
    depends_on:
      - influxdb
    logging:
      driver: "json-file"
      options:
        max-size: "1g"
    networks:
      - prom_monitor
  influxdb:
    image: influxdb:latest
    container_name: influxdb
    hostname: influxdb
    restart: always
    volumes:
    # influxdb 的配置以及数据的路径
      - /prometheus/prometheus/influxdb/config:/etc/influxdb
      - /prometheus/prometheus/influxdb/data:/var/lib/influxdb/data
      - /etc/localtime:/etc/localtime
    ports:
      - "8086:8086"
      - "8083:8083"
    environment:
      - INFLUXDB_DB=prometheus
      - INFLUXDB_ADMIN_ENABLED=true
      - INFLUXDB_ADMIN_USER=admin
      - INFLUXDB_ADMIN_PASSWORD=adminpwd
      - INFLUXDB_USER=prometheus
      - INFLUXDB_USER_PASSWORD=prometheuspwd
      - INFLUXDB_CONFIG_PATH=/etc/influxdb/influxdb.conf
    logging:
      driver: "json-file"
      options:
        max-size: "1g"
    networks:
      - prom_monitor
  locust_master:
  	# 配置你自己的 locust镜像
    image: xxx/locust:v1
    container_name: locust_master
    hostname: locust_master
    restart: always
    volumes:
    # 数据卷自己灵活配置
      - /home/locustperf:/mytest/locust
    ports:
      - "8089:8089"
      - "5557:5557"
    networks:
      - prom_monitor
    command: locust --master --config=locust.config

networks:
  prom_monitor:
    driver: bridge

worker 的 docker-compose yml 文件

# 服务器有几个内核就启动几个worker
docker-compose -f  your-docker-compose.yml up -d --scale locust_worker=4
version: '1'

  locust_worker:
    image: xxx/locust:v1
    volumes:
      - /home/locustperf:/mytest/locust
    command: locust --worker --master-host=xxx.x.xx.223 --config=locust.config

Pycharm Debug

Debug 能够帮助我们快速定位问题,Pycharm 中需要在环境变量里面额外配置一下 GEVENT_SUPPORT=True

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hb1QBgKb-1648381703832)(image-20220327180009708.png)]

自定义负载

如果你的脚本里面有 LoadTestShape 这个类,那么 Locust 将自动使用它

class StagesShape(LoadTestShape):

    def stages_details(self):
        # 持续时间、用户数量、每秒生产用户数
        stages = [
            {"duration": self.runner.environment.parsed_options.duration_time,
             "users": self.runner.environment.parsed_options.users_count,
             "spawn_rate": self.runner.environment.parsed_options.spawn_rate_count},
        ]
        return stages

    def tick(self):
        run_time = self.get_run_time()

        for stage in self.stages_details():
            if run_time < stage["duration"]:
                tick_data = (stage["users"], stage["spawn_rate"])
                return tick_data

        return None

它是从哪里来的?

self.runner.environment.parsed_options.duration_time

自定义一个配置,在 WEB UI 中设置它的值,然后通过 runner.environment.parsed_options 去获取

通过在 WEB UI 中添加配置,将会使你的测试变的更灵活

@events.init_command_line_parser.add_listener
def _(parser):
    parser.add_argument("--duration-time", 
                        include_in_web_ui=True, 
                        type=int, 
                        env_var="LOCUST_DURATION_TIME", 
                        default=300, help="持续时间")

这只是一个简单的示例,你可以定义更复杂的负载形状,参考github 上的示例

FastHttpUser

FastHttpUser 提供了比 HttpUser 更强的性能

官方文档给的数据是单核 5000请求/s HttpUser 大约 850/s(2018 MacBook Pro i7 2.6GHz)

如何使用 FastHttpUser

只需继承 FastHttpUser:

from locust import task, FastHttpUser

class MyUser(FastHttpUser):
    @task
    def index(self):
        # FastHttpUser 相较于 HttpUser 功能会更少一点,比如没有 verify 属性
        response = self.client.get("/")

日志处理

import logging
logging.info("this log message will go wherever the other locust log messages go")

可选参数:

--skip-log-setup - 禁用日志

--loglevel - 指定日志等级

--logfile - 指定日志文件路径

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值