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。
运行:
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(运行过程中只会执行task1和task2)
–exclude-tags tag3 取反,排除哪些(运行过程中只会执行task1、task2、task4)
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 ,--locustfile | LOCUST_LOCUSTFILE | locustfile | 指定要运行的 locustfile 文件 |
-H ,--host | LOCUST_HOST | host | 指定被测环境的host |
-u ,--users | LOCUST_USERS | users | 并发 Locust 用户的峰值数量。主要与–headless 或–autostart 一起使用。可以在测试期间通过键盘输入 w、W(生成 1、10 个用户)和 s、S(停止 1、10 个用户)更改 |
-r ,--spawn-rate | LOCUST_SPAWN_RATE | spawn-rate | 每秒生成用户数。主要与 –headless 或 –autostart 一起使用 |
-t ,--run-time | LOCUST_RUN_TIME | run-time | 在指定的时间后停止运行,例如(300s、20m、3h、1h30m 等)。仅与 –headless 或 –autostart 一起使用。locust 默认永远运行。 |
--web-host | LOCUST_WEB_HOST | web-host | 将 Web 界面绑定到的主机。默认为“*”(所有接口) |
--web-port ,-P | LOCUST_WEB_PORT | web-port | 运行 Web 主机的端口 |
--headless | LOCUST_HEADLESS | headless | 禁用 Web 界面,并立即开始测试。使用 -u 和 -t 控制用户数和运行时间 |
--autostart | LOCUST_AUTOSTART | autostart | 立即开始测试(不禁用 Web UI)。使用 -u 和 -t 控制用户数和运行时间 |
--autoquit | LOCUST_AUTOQUIT | autoquit | 在运行完成 X 秒后完全退出 Locust。仅与 –autostart 一起使用。默认设置是保持 Locust 运行,直到使用 CTRL+C 将其关闭 |
--web-auth | LOCUST_WEB_AUTH | web-auth | 为 Web 界面打开基本身份验证。应按以下格式提供:username:password |
--tls-cert | LOCUST_TLS_CERT | tls-cert | https 证书路径 |
--tls-key | LOCUST_TLS_KEY | tls-key | https 证书私钥路径 |
--master | LOCUST_MODE_MASTER | master | 设置 locust 以分布式模式运行,此进程作为主机 |
--master-bind-host | LOCUST_MASTER_BIND_HOST | master-bind-host | locust master 应该绑定的接口(主机名、ip)。仅在使用 –master 运行时使用。默认为 *(所有可用接口)。 |
--master-bind-port | LOCUST_MASTER_BIND_PORT | master-bind-port | locust master 应该绑定的端口。仅在使用 –master 运行时使用。默认为 5557。 |
--expect-workers | LOCUST_EXPECT_WORKERS | expect-workers | 在开始测试之前,master 应该期望连接多少个worker(仅当使用–headless/autostart 时)。 |
--expect-workers-max-wait | LOCUST_EXPECT_WORKERS_MAX_WAIT | expect-workers-max-wait | master 在放弃之前应该等待worker 连接多长时间。默认为永远等待 |
--worker | LOCUST_MODE_WORKER | worker | 设置 locust 以分布式模式运行,此进程作为 worker |
--master-host | LOCUST_MASTER_NODE_HOST | master-host | 指定 worker 需要连接的 master 的 host |
--master-port | LOCUST_MASTER_NODE_PORT | master-port | 指定 worker 需要连接的 master 的端口 |
-T ,--tags | LOCUST_TAGS | tags | 运行指定 tag 的任务 |
-E ,--exclude-tags | LOCUST_EXCLUDE_TAGS | exclude-tags | tag 取反 |
--csv | LOCUST_CSV | csv | 以 CSV 格式将当前请求统计信息存储到文件中。设置此选项将生成三个文件:[CSV_PREFIX]_stats.csv、[CSV_PREFIX]_stats_history.csv 和 [CSV_PREFIX]_failures.csv |
--csv-full-history | LOCUST_CSV_FULL_HISTORY | csv-full-history | 将每个统计信息条目以 CSV 格式存储到 _stats_history.csv 文件中。您还必须指定“–csv”参数才能启用此功能。 |
--print-stats | LOCUST_PRINT_STATS | print-stats | 在控制台中打印统计信息 |
--only-summary | LOCUST_ONLY_SUMMARY | only-summary | 仅打印摘要统计信息 |
--reset-stats | LOCUST_RESET_STATS | reset-stats | 产卵完成后重置统计信息。在分布式模式下运行时,应在 master 和 worker 上都设置 |
--html | LOCUST_HTML | html | 将 HTML 报告存储到指定的文件路径 |
--skip-log-setup | LOCUST_SKIP_LOG_SETUP | skip-log-setup | 禁用 Locust 的日志记录设置。相反,配置由 Locust 测试或 Python 默认值提供。 |
--loglevel ,-L | LOCUST_LOGLEVEL | loglevel | 指定日志等级,默认为INFO。 |
--logfile | LOCUST_LOGFILE | logfile | 日志文件的路径。如果未设置,日志将转到 stderr |
--exit-code-on-error | LOCUST_EXIT_CODE_ON_ERROR | exit-code-on-error | 设置当测试结果包含任何失败或错误时使用的进程退出代码 |
-s ,--stop-timeout | LOCUST_STOP_TIMEOUT | stop-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
- 指定日志文件路径