写在前面
最近打算学习LocustIO,但是介于英文水平一般,英文文档读起来还是不太顺畅,于是花了两天时间把整个英文文档翻译了一遍,以供学习之用。翻译过程尽量终于原文,但是由于水平有限,难免会有错失遗漏,如有发现,请不吝指正,谢谢!
Locust是什么?
Locust是一个易于使用、可编写脚本且可扩展的性能测试工具。
您可以使用常规Python代码定义用户的行为,而不是使用笨拙的UI或DSL。
这使Locust可以无限扩展,并且对开发人员非常友好。
要开始使用 Locust,请转到[安装](# 安装)。
特性
-
使用纯Python代码编写用户测试场景
如果希望用户循环执行某些条件行为或进行某些计算,则只需使用Python提供的常规编程结构即可。Locust在其自己的greenlet(轻量级 进程/协程)中运行每个用户。这使您能够像普通(阻塞)Python代码一样编写测试,而不必使用回调或其他某种机制。 因为您的场景是 “只是 Python 代码”,所以您可以使用常规的IDE,并以常规代码的形式对测试进行控制(与其他一些使用XML或二进制格式的工具有所不同)
-
分布式&可伸缩——支持数十万用户
Locust使得运行分布在多台计算机上的负载测试变得容易。它是基于事件的(使用gevent),这使得单个进程可以处理数千并发用户。尽管可能还有其他工具可以在给定的硬件上每秒执行更多请求,但是每个Locust用户的低开销使其非常适合测试高并发工作负载。
-
基于Web的UI
Locust具有用户友好的Web界面,可实时显示测试进度。 您甚至可以在测试运行时更改负载。 Locust也可以在没有UI的情况下运行,从而使其易于用于CI / CD测试。
-
可以测试任何系统
尽管Locust是面向 站点/服务 的,但也可以用于测试几乎所有系统或协议。只需为您要测试的系统编写一个客户端,或者浏览社区创建的针对特定需求的客户端即可。
-
可拓展
Locust 非常小,非常灵活,我们打算使其一直保持这种特点。如果你想将报告数据发送到你喜欢的数据库&图形系统,请包装对REST API的调用以便处理系统的细节,或运行一个完全自定义的加载模式,那么你就可以无往不利。
命名和背景
Locust的诞生源于对现有解决方案的失望。 没有一个现有的负载测试工具能够很好地为动态网站生成真实的负载,在动态网站上,大多数页面会针对不同用户提供不同的内容。 现有工具使用笨拙的接口或冗长的配置文件来声明测试。在Locust中,我们采取了不同的方法。 您可以使用 Python 代码而不是配置格式或UI来定义用户的行为。
“Locust” 的这一名称来源于蝗虫物种,此种类以其成群的行为而闻名。
以前的Locust版本借用了自然界中的术语(群集、孵化,攻击等),但现在使用了更多的行业标准命名。
作者
- Jonatan Heyman (@jonatanheyman on Twitter)
- Lars Holmberg (@cyberw on Github)
- Carl Byström (@cgbystrom on Twitter)
- Joakim Hamrén (@Jahaaja on Twitter)
- Hugo Heyman (@hugoheyman on Twitter)
非常感谢我们其他伟大的贡献者!
License
MIT许可下的开源许可(参见LICENSE文件了解详细信息)。
安装
安装 Python3.6+。
使用 pip 安装 Locust:
$ pip3 install locust
验证安装并显示版本号:
$ locust -V
如果一切顺利,请接着阅读下一节。如果遇到问题,请查看 WIKI 页面以查找解决方案。
快速开始
在 Locust 中,可以使用 Python 代码定义用户行为。然后,您可以使用 locust
命令和(可选的)Web界面在收集请求统计信息时生成和模拟大量用户。
示例 locustfile.py
下面是一个简单的 locustfile.py 的例子:
import time
from locust import HttpUser, task, between
class QuickstartUser(HttpUser):
wait_time = between(1, 2.5)
@task
def hello_world(self):
self.client.get("/hello")
self.client.get("/world")
@task(3)
def view_items(self):
for item_id in range(10):
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"})
让我们分开来看:
import time
from locust import HttpUser, task, between
locust 文件只是一个可以从其他文件或包导入代码的普通 Python 模块。
class QuickstartUser(HttpUser):
在这里,我们为要模拟的用户定义了一个继承自 HttpUser
的类 QuickstartUser
。在 HttpUser
类中,属性 client
是每一个模拟用户用来向我们要进行负载测试的目标系统发送 HTTP 请求的 HttpSession
的实例。当测试启动时,Locust 会为每一个模拟用户创建一个 QuickstartUser
类的实例,然后每一个这样的模拟用户将会在它们自己的 green gevent 线程中开始运行。
wait_time = between(1, 2.5)
QuickstartUser
类中定义了一个类属性 wait_time
,该属性会使得每一个模拟用户在每一个任务(下面由 @task
装饰器装饰的方法)执行之后等待 1~2.5 秒。更多细节,参见 [wait_time
属性](# wait_time 属性)。
@task
def hello_world(self):
使用 @task
装饰器装饰的方法是 locust 文件的核心。Locust 会为每一个运行的用户创建一个调用这些方法的 greenlet(微线程)。
def hello_world(self):
self.client.get("/hello")
self.client.get("/world")
@task(3)
def view_items(self):
...
我们通过使用装饰器 @task
装饰 QuickstartUser
方法的方式添加了两个任务,其中一个任务具有较高的权重(3)。当 QuickstartUser
实例运行的时候,它会从声明的方法中选出一个(hello_world
或 view_items
)并执行。任务的选取是随机的,但你可以给它们设置不同的权重。上面示例中的配置将会使得 view_items
被选中的几率是 hello_world
的 3 倍。当一个任务结束运行后后,该任务的所有者(模拟用户)会在等待时间(在上面的示例中,为 1~2.5 秒)内休眠。等待时间结束后,模拟用户会重选择一个任务,然后不断重复这个过程。
注意,只有被 @task
装饰的方法才会被选择,因此你可以以任何你喜欢的方式定义内部的帮助程序。
self.client.get("/hello")
self.client
属性使得模拟用户能够创建被 Locust 记录的 HTTP 调用。对于如何创建其他种类的请求、验证响应等,请参阅 使用 HTTP 客户端。
@task(3)
def view_items(self):
for item_id in range(10)
self.client.get(f"/item?id={item_id}", name="/item")
time.sleep(1)
在 view_items
任务中,我们使用可变的查询参数加载了 10 个不同的 URL。为了不在 Locust 的统计数据中创建 10 个单独的实体——因为统计数据是按照 URL 分组的——我们使用 name 参数 将所有的这 10 个请求分组到名为 "/item"
的单个实体中。
def on_start(self):
self.client.post("/login", json={"username":"foo", "password":"bar"})
另外,我们还声明了一个 on_start
方法。每个模拟用户在启动时都会调用具有该名称的方法。 有关更多信息,请参见 on_start
和 on_stop
方法。
启动 Locust
将上述代码写入当前目录中名为 locustfile.py 的文件中,并运行:
$ locust
如果 Locust 文件位于其他位置,那么可以使用 -f
参数来指定:
$ locust -f locust_files/my_locust_file.py
注意:要查看所有可用选项,请使用命令
locust --help
或查看 [配置](# 配置)。
Locust Web 界面
使用上面的命令行命令启动 Locust 之后,可以在浏览器中打开地址 http://127.0.0.1:8089 (然后,就可以看到如下的画面:
填写表单,然后尝试!(但请注意,如果你没有更改Locust文件以使其与实际目标系统匹配,则大多数情况下会收到错误响应)
更多选项
要运行分布在多个Python进程或计算机上的Locust,可以使用 --master
命令行参数启动单个Locust主进程,然后使用 --worker
命令行参数启动任意数量的Locust worker进程。 有关更多信息,请参见 以分布式方法运行Locust。
要直接开始测试而不使用Web界面,请使用 --headless
。
要在无头模式运行期间 添加/删除 用户,请按 w
或 W(1, 10)
生成用户,按 s
或 S
来停止 (1, 10)
。
如何编写真实的 locust 文件?
上面的示例只是一个小的介绍,请阅读下一节以了解更多信息。
编写locustfile
Locust 文件只是一个普通的Python文件,惟一的要求是在这个文件中必须至少定义一个继承自 User
类的类。
User 类
一个 User
类的实例代表一个用户(如果你愿意,则代表一群蝗虫)。Locust 将为每个被模拟的用户生成(孵化)一个 User
类的实例。User
类可以定义如下一些属性:
wait_time 属性
User
的 wait_time
方法是一个用于确定模拟用户在任务执行期间应该等待多长时间的可选属性。如果没有指定该属性,则新的任务会在前一个任务完成后立即开始执行。
有三个内置的等待时间函数:
constant
用于表示一个固定的等待时间。between
表示一个位于最小值和最大值之间的随机等待时间。constant_pacing
是确保任务每X秒运行一次的自适应时间。
例如,要使得每一个用户在每个任务执行期间等待 0.5~10 秒:
from locust import User, task, between
class MyUser(User):
@task
def my_task(self):
print("executing my_task")
wait_time = between(0.5, 10)
也可以直接在类上声明自己的 wait_time
方法。例如,下面的 User
类将休眠一秒钟,然后休眠两秒,然后休眠三秒,依此类推。
class MyUser(User):
last_wait_time = 0
def wait_time(self):
self.last_wait_time += 1
return self.last_wait_time
...
weight 属性
如果文件中存在多个用户类,并且在命令行上未指定任何用户类,则Locust将为每个用户类创建相同数量的实例。还可以通过将用户类作为命令行参数传递,来指定要从同一个Locust文件中使用哪些用户类:
$ locust -f locust_file.py WebUser MobileUser
如果您希望模拟更多特定类型的用户,则可以在这些类上设置一个 weight
属性。举例来说,Web用户的可能性是移动用户的三倍:
class WebUser(User):
weight = 3
...
class MobileUser(User):
weight = 1
...
host 属性
host
属性是要加载的主机的URL前缀(例如 http://google.com)。通常,可以在Locust的Web UI中指定URL 前缀,也可以在Locust启动时使用 --host
选项在命令行中指定的。
如果在用户类中声明了 host
属性,而没有通过其他方式指定 URL 前缀,那么将使用该属性值。
tasks 属性
可以使用 @task
装饰器将 User
类的方法声明为任务,但也可以使用 task
属性指定任务,这将在下面更详细地描述。
enviroment 属性
该属性是对模拟用户的运行环境的引用。使用该属性与环境或其所包含的运行器进行交互。例如。 从任务方法中停止运行器:
self.environment.runner.quit()
如果在独立的 Locust 实例上运行上述代码,则将停止整个Locust 的运行。如果在worker节点上运行,则将停止该特定节点。
on_start 方法和 on_stop 方法
User
(和 TaskSet
)可以定义一个 on_start
和/或 on_stop
方法。User
将会在开始运行时调用其 on_start
方法,并在停止运行时调用其 on_stop
方法。模拟用户将在开始运行 TaskSet
时调用 TaskSet
的 on_start
方法,并在停止执行 TaskSet
时(当调用 interrupt()
或模拟用户被杀死时)运行 TaskSet
的 on_stop
方法。
任务
启动负载测试后,将为每个模拟用户创建一个 User
类的实例,并且该实例将在其自己的green线程中运行。这些模拟用户运行时,他们选择执行的任务,休眠一会儿,然后选择一个新任务,依此类推。
这些任务是普通的Python可调用对象,并且——如果我们正在对拍卖网站进行负载测试——则它们可以执行诸如 “加载起始页”、“搜索某些产品” 和 “竞标” 之类的工作。
@task 装饰器
为 User
添加任务的最简单方式是使用 task
装饰器。
from locust import User, task, constant
class MyUser(User):
wait_time = constant(1)
@task
def my_task(self):
print("User instance (%r) executing my_task" % self)
@task
装饰器可以接受一个可选的用于指定任务的执行几率的 weight 参数。在下面的示例中,task2 被选中的几率是 task1 的两倍。
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
属性。
tasks
属性可以是任务的列表或者 <任务: int> 字典。其中任务可以是 Python 可调用对象或者 TaskSet
类。如果任务是一个普通 Python 函数,则该函数接受单个参数,这个参数表示运行这个任务的 User
实例。
下面是一个使用普通函数作为 User
任务的示例:
from locust import User, constant
def my_task(user):
pass
class MyUser(User):
tasks = [my_task]
wait_time = constant(1)
如果 tasks
属性指定为一个列表,则每次从列表中随机选择一个任务并执行。
如果 tasks
属性指定为一个字典(可调用对象作为键,int
值作为键值),则使用字典中的任务对应的整数值作为权重,随机的从字典中选择一个任务并执行。
如果有类似下面这样的字典:
{my_task: 3, another_task: 1}
则 my_task 被执行的概率是 another_task 的 3 倍。
在内部,上面的字典将会被转换为下面这样的一个列表(并且 tasks
属性将被更新):
[my_task, my_task, my_task, another_task]
然后,将使用 random.choice()
随机的从该列表中选择一个任务。
@tag 装饰器
通过使用 @tag
装饰器标记任务,您可以使用 --tags
和 --exclude-tags
参数选择在测试期间执行哪些任务。考虑以下示例:
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
如果使用 --tags tag1
启动测试,则仅有 task1 和 task2 在测试期间执行。如果使用 --tags tag2 tag3
启动测试,则仅有 task2 和 task3 在测试期间执行。
--exclude-tags
的行为完全相反。因此,如果您使用 --exclude-tags tag3
启动测试,则只会执行 task1、task2 和 task4。排除总是优先于包括,因此,如果一个任务同时具有被包含的标签和被排除的标签,则该任务将不会执行。
事件
如果要在测试中运行某些设置代码,通常将其放在Locust 文件的模块级别就足够了,但是有时您需要在运行的特定时间进行操作。为此,Locust提供了事件挂钩。
test_start 和 test_stop
如果要在负载测试开始或停止时运行一些代码,应该使用 test_start
事件和 test_stop
事件。可以在 Locust 文件的模块级别设置这些事件的监听器:
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")
当以分布式方式运行 Locust 时,test_start
和 test_stop
事件仅会在主节点中触发。
init
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")
其他事件
查看 使用事件挂钩拓展 Locust 以了解其他事件和使用事件挂钩的更多示例。
HttpUser 类
HttpUser
是最常使用的 User
类。该类中添加了一个用于发送 HTTP 请求的 client
属性。
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(成功/失败、响应时间、响应长度和名称)的功能。
HttpSession
包含所有的 HTTP 方法:get
、post
、put
等。
就像 request.Session
一样,它在请求之间保留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")
HttpSession
会捕获 Session
抛出的任何 requests.RequestException
(由于连接错误、超时或其他类似原因),而不是返回一个虚拟的 status_code 设置为0、content 设置为 None
的 Response
对象。
验证响应
如果 HTTP 响应代码为 “OK”(小于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()
使用动态参数将请求分组到 URL
网站上常有一些网址中包含某种动态参数的网页。通常,在用户的统计信息中将这些网址归为一组是很有意义的。 可以通过将 name 参数传递给 HttpSession
的不同请求方法来实现这个功能。
例如:
# Statistics for these requests will be grouped under: /blog/?id=[id]
for i in range(10):
self.client.get("/blog?id=%i" % i, name="/blog?id=[id]")
HTTP 代理设置
为了提高性能,可以通过将 requests.Session
的 trust_env
属性设置为 False
来将 requests 配置为不在环境中查找HTTP代理设置。 如果您不希望这样做,可以将 locust_instance.client.trust_env
手动设置为 True
。 有关更多详细信息,请参阅 requests 文档。
TaskSet
如果您正在对一个以分层结构(包含部分和子部分)构建的网站进行性能测试,那么以同样的方式构建负载测试可能会很有用。为此,Locust提供了 TaskSet
类。该类是任务的集合,将像直接在 User
类上声明的任务一样执行。
注意
TaskSet
是一项高级功能,很少有用。很多时候,最好使用常规的Python循环和控制语句来实现同一目的。 并且该类也有一些陷阱,最常见的陷阱是忘记调用self.interrupt()
。
from locust import User, TaskSet, constant
class ForumSection(TaskSet):
wait_time = constant(1)
@task(10)
def view_thread(self):
pass
@task
def create_thread(self):
pass
@task
def stop(self):
self.interrupt()
class LoggedInUser(User):
wait_time = constant(5)
tasks = {ForumSection:2}
@task
def my_task(self):
pass
也可以在 User
/TaskSet
类中使用 @task
装饰内联定义 TaskSet
。
class MyUser(User):
@task
class MyTaskSet(TaskSet):
...
TaskSet
类的任务也可以是其他的 TaskSet
类,并允许以任意深度进行嵌套。
这使我们能够以一种更现实的方式定义模拟用户的行为。
例如,我们可以使用以下结构定义 TaskSet
:
- Main user behaviour
- Index page
- Forum page
- Read thread
- Reply
- New thread
- View next page
- Browse categories
- Watch movie
- Filter movies
- About page
当正在运行的用户线程选择 TaskSet
类执行时,将创建该类的实例,然后执行将进入该 TaskSet
。然后将选取并执行 TaskSet
中的一个任务,然后线程将休眠一段时间,该持续时间由用户的 wait_time
函数指定(除非在TaskSet
类上直接声明了 wait_time
函数,在这种情况下,将会使用 TaskSet
类上的 wait_time
函数),然后从 TaskSet
的任务中选择一个新任务,然后再次等待,依此类推。
TaskSet
实例的 user
属性包含对 User
的引用。它还具有 User
的 client
属性的快捷方式。因此,您可以使用 self.client.request()
发出请求,就像您的任务是直接在 HttpUser
上定义的一样。
中断任务
有关 TaskSet
的重要一件事是,它们永远不会停止执行其任务,而是会自行将执行移交给其父级 User
/TaskSet
。要停止 TaskSet
的执行,必须通过调用 TaskSet.interrupt()
方法来完成。
interrupt(self, reschedule=True)
中断 TaskSet
并将执行控制权移交给父 TaskSet
。
如果 reschedule 为 True
,则父 User
会立即重新安排并执行新任务。
在下面的示例中,如果缺少调用 self.interrupt()
的 stop
任务,则模拟用户一旦进入 Forum
任务集中就永远不会停止运行该任务:
class RegisteredUser(User):
@task
class Forum(TaskSet):
@task(5)
def view_thread(self):
pass
@task(1)
def stop(self):
self.interrupt()
@task
def frontpage(self):
pass
可以结合 interrupt()
方法和任务权重来定义模拟用户离开论坛的可能性。
TaskSet中的任务和User类中的任务的不同之处
TaskSet
中的任务和 User
中的任务的不同之处在于,TaskSet
中的任务在执行时传递的参数(对于使用@task装饰器声明为方法的任务本身)是对 TaskSet
实例的引用,而不是对 User
实例的引用。可以通过TaskSet.user
从 TaskSet
实例内部访问 User
实例。TaskSet
还包含一个便捷 client
属性,该属性引用 User
实例上的 client
属性。
引用 User 实例或父 TaskSet 实例
TaskSet
实例有一个指向其 User
实例的 user
属性和一个指向其父 TaskSet
实例的 parent
属性。
Tag 和 TaskSet
可以以标记普通任务的方式使用 @tag
装饰器标记 TaskSet
,但是有一些细微差别值得一提。 标记 TaskSet
会将标记自动应用于所有 TaskSet
的任务。此外,如果您在嵌套 TaskSet
中标记任务,即使未标记 TaskSet
,Locust也会执行该任务。
SequentialTaskSet 类
SequentialTaskSet
是一个会按照任务被声明的顺序执行其任务的 TaskSet
。可以在 TaskSet
中嵌套 SequentialTaskSet
,反之亦然。
例如,下面的代码会按顺序请求 URL /1
、/2
、/3
和 /4
,然后一直重复。
def function_task(taskset):
taskset.client.get("/3")
class SequenceOfTasks(SequentialTaskSet):
@task
def first_task(self):
self.client.get("/1")
self.client.get("/2")
# you can still use the tasks property to specify a list of tasks
tasks = [function_task]
@task
def last_task(self):
self.client.get("/4")
请注意,您不需要 SequentialTaskSet
只是按顺序执行一些请求。 仅在单个任务中完成整个用户流程通常会更容易。
如何组织测试代码
请务必记住,locustfile.py
只是可以由Locust导入的普通Python模块。您可以像在任何Python程序中一样随意导入其他Python代码。当前的工作目录会自动添加到Python的 sys.path
中,因此可以使用Python import
语句导入工作目录中的所有Python 文件/模块/软件包。
对于小型测试,将所有测试代码保存在一个 locustfile.py
中应该可以正常工作,但是对于大型测试套件,您可能需要将代码拆分为多个文件和目录。
当然,如何构造测试源代码完全取决于您,但是我们建议您遵循Python最佳实践。这是一个虚构的Locust项目的示例文件结构:
- 项目根目录
common/
__init__.py
auth.py
config.py
locustfile.py
requirements.txt
(外部 Python 依赖经常记录在 requirements.txt 中)
具有多个不同Locust 文件的项目也可以将它们保存在单独的子目录中:
- 项目根目录
common/
__init__.py
auth.py
config.py
locustfiles/
api.py
website.py
requirements.txt
使用上述任何项目结构,您的Locust 文件都可以使用以下方法导入公共库:
import common.auth
配置
命令行选项
配置如何运行Locust的最直接的方法是通过命令行参数。
可以使用如下命令查看可用的命令行选项:
$ locust --help
公用选项
-
-h/--help
显示帮助信息并退出。
-
-f/--locustfile LOCUSTFILE
要导入的 Python 模块文件,例如
'../other.py'
。默认值为'locustfile'
。 -
-H/--host HOST
要进行负载测试的主机地址,格式为:
http://10.21.32.33
。 -
-u/--user NUM_USERS
并发 Locust 用户的数量。主要与
--headless
选项一起使用。可以通过在测试运行期间通过输入w, W(spawn 1, 10 users)
和s, S(stop 1, 10 users)
来更改。 -
-r/--spawn-rate SPAWN_RATE
每秒生成的用户数量。主要与
--headless
选线一起使用。 -
-t/--run-time RUN_TIME
在指定的特定时间后停止。例如
300s
、20m
、3h
和1h30m
等。仅与--headless
选项一起使用。默认值为永久运行。 -
-l/--list
显式可能的用户类列表并退出。
Web UI 选项
-
--web-host WEB_HOST
Web 界面绑定的主机。默认值为
'*'
(所有的接口)。 -
--web-port/-P WEB_PORT
运行Web主机的端口。
-
--headless
禁用 Web 界面,并立即开始负载测试。需要指定
-u
和-t
选项。 -
--web-auth WEB_AUTH
为 Web 界面开启基本认证。应该以
username:password
的格式提供。 -
--tls-cert TLS_CERT
用于提供 HTTPS 服务的可选的 TLS 证书路径。
-
--tls-KEY TLS_KEY
用于提供 HTTPS 服务的可选的 TLS 私钥。
主节点选项
当以分布式方式运行Locust时用于运行Locust 主节点的选项。 在主节点运行负载测试前,需要有连接到主节点的工作节点。
-
--master
将 Locust 设置为以分布式模式运行,并以该选项指定的进程为主节点。
-
--master-bind-host MASTER_BIND_HOST
Locust 主节点绑定的接口(主机名,IP)。仅用于使用
--master
选项运行Locust时。默认值为'*'
(所有可用接口)。 -
--master-bind-port MASTER_BIND_PORT
Locust 主节点绑定的端口。仅用于使用
--master
选项运行Locust时。默认为5557
。 -
--expect-workders EXPECT_WORKDERS
主节点在开始测试前(仅当使用
--headless
时)应该期望连接多少工作节点。
工作节点选项
当Locust以分布式方式运行时,用于运行 Locust 工作节点的选项。启动工作节点时,仅需要指定 LOCUSTFILE
(-f
选项),因为在主节点上指定了其他选项(例如 -u
,-r
,-t
)。
-
--worker
将Locust设置为在分布式模式下运行,并以该选项指定的进程作为工作进程。
-
--master-host MASTER_NODE_HOST
用于分布式负载测试的Locust主节点服务器的主机或IP地址。 仅在使用
--worker
选项运行 Locust 时使用。 默认为127.0.0.1
。 -
--master-port MASTER_NODE_PORT
工作节点所要连接到的用于分布式负载测试的 Locust 主节点所使用的端口。 仅在使用
--worker
选项运行 Locust 时使用。 默认为5557
。
标签选项
可以使用 @tag
装饰器来标记 Locust 任务。这些选项使您可以指定在测试期间要包含或排除的任务。
-
-T/--tags [TAG [TAG ...]]
要在测试中包含的标签的列表,因此,只有与该选项所指定的标签所匹配的测试才会被执行。
-
-E/--exclude-tags [TAG [TAG ...]]
要在测试中排除的标签的列表,因此,只有与该选项所指定的标签不匹配的测试才会被执行。
请求统计数据选项
-
--csv CSV_PREFIX
将当前请求的统计数据以 CSV 格式存储在文件中。设置该选项将会生成三个文件:
[CSV_PREFIX]_stats.csv
、[CSV_PREFIX]_stats_history.csv
和[CSV_PREFIX]_failures.csv
。 -
--csv-full-history
将每一个统计数据实体以 CSV 格式存储在
_stats_history.csv
文件中。必须通过--csv
选项启用该选项。 -
--print-stats
在控制台中打印统计数据。
-
--only-summary
只打印汇总统计信息。
-
--reset-stats
生成虚拟用户后重置统计信息。以分布式模式运行时,应同时在主节点和工作节点上设置。
-
--html HTML_FILE
存储 HTML 报告文件。
日志记录选项
-
--skip-log-setup
禁用 Locust 日志记录设置。而是使用 Locust 测试或 Python 默认提供的配置。
-
--loglevel LOGLEVEL
在
DEBUG/INFO/WARNING/ERROR/CRITICAL
之间选择。默认值为INFO
。 -
--logfile LOGFILE
日志文件的路径。如果未设置,日志会打印到标准输出/标准错误。
其它选项
-
--show-task-ratio
以表格格式打印用户类的任务执行比率。
-
--show-task-ratio-json
以 JSON 格式打印用户类的任务执行比率。
-
--version
显式程序版本号并退出。
-
--exit-code-on-error EXIT_CODE_IN_ERROR
设置进程退出代码以在测试结果包含任何失败或错误时使用。
-
-s/--stop-timeout STOP_TIMEOUT
退出之前等待模拟用户完成任何正在执行的任务的秒数。默认为立即终止。 仅在以分布式模式运行Locust时才需要为主进程指定此选项。
用户类参数
可选地指定应该使用哪些用户类。
环境变量
大多数可以使用命令行选项设置的参数也可以通过环境变量来设置。例如:
$ LOCUST_LOCUSTFILE=custom_locustfile.py locust
配置文件
可以通过命令行参数设置的任何选项也可以由配置文件以配置文件格式设置。
默认情况下,Locust 会查找 ~/.locust.conf
和 ./locust.conf
。也可以使用 --config
选项指定另外的配置文件。
例如:
# 当前目录中的 master.conf
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
$ locust --config=master.conf
注意
配置值按照下述顺序读取:
~/locust.conf -> ./locust.conf -> (file specified using --conf) -> env vars -> cmd args
所有可用的配置选项
详情参见 官方文档。
自定义统计数据设置
Locust 统计信息的默认配置在 stats.py
文件的常量中设置。可以通过覆盖这些值来将其调整为特定需求。为此,请导入 locust.stats
模块并覆盖所需的设置:
import locust.stats
locust.stats.CONSOLE_STATS_INTERVAL_SEC = 15
可以直接在Locust文件中完成此操作,也可以将其提取到单独的文件中,以供所有Locust文件共同使用。
可以修改的统计参数列表为:
参数名 | 目的 |
---|---|
STATS_NAME_WIDTH | 控制台输出中请求名称列的宽度 |
STATS_TYPE_WIDTH | 控制台输出中请求类型列的宽度 |
CSV_STATS_INTERVAL_SEC | 如何配置了此选项,则指定了写入 CSV 文件的写入频率 |
CONSOLE_STATS_INTERVAL_SEC | 将结果写入到控制台的频率 |
CURRENT_RESPONSE_TIME_PERCENTILE_WINDOW | 计算当前响应时间百分数时的以秒为单位的 窗口大小/分辨率 |
PERCENTILES_TO_REPORT | 要计算和报告的响应时间百分位数列表 |
以分布式方式运行 Locust
当一台计算机不足以模拟所需的用户数量时,Locust支持运行分布在多台机器上的负载测试。
为此,您可以使用 --master
标志以主节点模式启动一个Locust实例。该实例将运行Locust的Web界面,并且您可以在其中启动测试并查看实时统计信息。主节点本身不会模拟任何用户。您必须使用 --worker
以及 --master-host
(指定主节点的 IP/主机名)标志来启动一个或多个Locust从节点。
常见的设置是在一台计算机上运行一个主节点,然后在计算机上的每个处理器内核中运行一个工作实例。
注意:无论是主机还是从机,在运行分布式 Locust 测试脚本时,都必须有一份 Locust 测试脚本的副本。
示例
在主节点上启动 Locust:
$ locust -f my_locustfile.py --master
然后,再启动每个从属节点上的 Locust(使用主节点的IP替换 192.168.0.14
,或者当工作节点与主节点在同一计算机上时,则可以完全省略该参数):
$ locust -f my_locustfile.py --worker --master-host=192.168.0.14
选项
-
--master
将 Locust 设置为主节点。Web界面会在这个节点上运行。
-
--worker
将 Locust 设置为从属节点。
-
--master-host:X.X.X.X
可选地与
--slave
参数一起使用,用来设置主节点的 主机名/IP。(默认为127.0.0.1
) -
--master-port=5557
可选地与
--slave
参数一起使用,用来设置主节点的端口(默认端口为5557
)。 -
--master-bind-host=X.X.X.X
可选地与
--master
参数一起使用,将决定主节点将会绑定的接口。默认值是*
,表示所有的可用接口。 -
--master-bind-port=5557
可选地与
--master
参数一起使用,将决定主节点要监听的端口。默认监听的端口是5557
。 -
--expect-slaves=X
当使用
--headless
选项启动 Locust 时使用。在连接到主节点的从属节点的数量等于X
之前,主节点会一直等待。
使用 Docker 以分布式方式运行 Locust
参见 [使用 Docker 运行 Locust](# 使用 Docker 运行 Locust)。
使用 Docker 运行 Locust
Locust 的官方映像位于 locustio/locust。
可以像下面这样使用 docker 映像(假定当前工作目录中存在 locustfile.py
文件):
$ docker run -p 8089:8089 -v $PWD:/mnt/locust locustio/locust -f /mnt/locust/locustfile.py
Docker Compose
下面是一个Docker Compose文件的示例,可用于启动主节点和从属节点:
version: '3'
services:
master:
image: locustio/locust
ports:
- "8089:8089"
volumes:
- ./:/mnt/locust
command: -f /mnt/locust/locustfile.py --master -H http://master:8089
worker:
image: locustio/locust
volumes:
- ./:/mnt/locust
command: -f /mnt/locust/locustfile.py --worker --master-host master
可以使用上面的 compose 配置文件启动一个主 Locust 节点和 4 个从属 Locust 节点:
$ docker-compose up --scale worker=4
将 Locust 映像当做基础映像
有依赖第三方Python软件包的测试脚本是很常见的。在这种情况下,您可以使用官方的Locust Docker 映像作为基础映像:
FROM locustio/locust
RUN pip3 install some-python-package
在 Kubernets 上运行分布式负载测试
在Kubernetes上运行Locust 的最简单的方法是使用Helm chart。 Helm chart 会将所有设置和kubernetes资源打包为一种易于管理的方式。
当前最新的Helm chart在这里:github.com/deliveryhero/helm-charts。
注意:Helm chart 不是由Locust维护人员直接维护或支持的。
在没有Web UI的情况下运行 Locust
您可以在没有 Web UI的情况下运行 Locust ,例如,如果您想在一些自动化流程(例如,CI服务器)中运行Locust,那么可以使用 --headless
标志以及 -u
和 -r
标志。
$ locust -f locust_files/my_locust_file.py --headless -u 1000 -r 100
-u
指定要生成的 Locust 用户的数量。-r
指定每秒钟要生成的 Locust 用户的数量。
为测试设置时间限制
注意:这是 v0.9 的新功能,对于 v0.8,可以使用
-n
参数指定请求的数量。
假如你想要为一个测试指定运行时间,可以使用 --run-time
或 -t
。
$ locust -f --headless -u 1000 -r 100 --run-time 1h30m
当到达设置的时间时,Locust 将自动关闭。
允许任务在关闭时完成其迭代
默认情况下,Locust 会在关闭时立即停止任务(甚至不等待请求完成)。如果要允许任务在 Locust 关闭时完成其迭代,可以使用 --stop-timeout <seconds>
:
$ locust -f --headless -u 1000 -r 100 --run-time 1h30m --stop-timeout 99
在没有 Web UI 的情况下以分布式方式运行 Locust
如果想要在没有 Web UI 的情况下[以分布式方式运行 Locust](# 以分布式方式运行 Locust),应该在启动主节点时指定 --expect-workers
选项,以指定期待连接的从属节点的数量。然后,主节点会在开启测试前等待同等数量的从属节点连接到其上。
控制 Locust 进程的退出代码
在当 CI 环境中运行 Locust 时,你也许想要控制 Locust 进程的退出代码。可以通过在脚本中设置 Environment 实例的 process_exit_code 属性来设置。
下面是一个示例,如果满足以下任一条件,则将退出代码设置为非零:
- 当 1% 以上的请求失败时
- 平均响应时间大于 200ms 时
- The 95th percentile for response time is larger than 800 ms
import logging
from locust import events
@events.quitting.add_listener
def _(environment, **kw):
if environment.stats.total.fail_ratio > 0.01:
logging.error("Test failed due to failure ratio > 1%")
environment.process_exit_code = 1
elif environment.stats.total.avg_response_time > 200:
logging.error("Test failed due to average response time ratio > 200 ms")
environment.process_exit_code = 1
elif environment.stats.total.get_response_time_percentile(0.95) > 800:
logging.error("Test failed due to 95th percentile response time > 800 ms")
environment.process_exit_code = 1
else:
environment.process_exit_code = 0
上面的代码可以位于 locustfile.py
文件或者它可以导入的任何其他文件中。
获取CSV格式的测试统计数据
您可能想要以 CSV 文件的格式获取Locust 结果。
首先,当使用Web UI运行 Locust 时,您可以在 Download Data 选项卡下获取CSV文件。
其次,您可以使用一个定期保存3个CSV文件的标志运行 Locust。如果您计划使用 --headless
参数以自动化的方式运行 Locust,这将特别有用:
$ locust -f examples/basic.py --csv=example --headless -t10m
这些文件将命名为 example_stats.csv
、example_failures.csv
和 example_history.csv
(当使用 --csv=example
时)。前两个文件包含整个测试运行的统计数据和失败,每个统计信息条目(URL端点)都有一行,并且有一个汇总行。在整个测试运行期间,将向 example_history.csv
文件写入追加了当前(10秒的滑动窗口)统计信息的新行。默认情况下,仅将聚合行定期添加到历史记录统计信息中,但是如果使用 --csv-full-history
标志启动Locust,则每次写入统计信息时都会为每个统计信息条目(以及聚合)追加一行( 默认情况下每2秒一次)。
你可以自定义写入频率:
import locust.stats
locust.stats.CSV_STATS_INTERVAL_SEC = 5 # 默认值为 1 秒
locust.stats.CSV_STATS_FLUSH_INTERVAL_SEC = 60 # 确定数据刷新到磁盘的频率。默认为 10 秒
使用定制的客户端测试其他系统
Locust 是以HTTP为主要目标构建的。但是,通过编写一个触发 request_success
和 request_failure
事件的自定义客户端,可以很容易地将其扩展为能够对基于 请求/响应
的系统进行负载测试的工具。
注意
你使用的任何协议都应该是 gevent 友好的(使用 Python
socket
模块或其他一些标准库函数,例如subprocess
),否则,你的调用将阻塞整个 Locust 进程。一些 C 库不能被 gevent 打猴子补丁,但是有一些变通方式。例如,如果想要使用 psycopg2 对 PostgreSQL 进行性能测试,可以使用 psycogreen。
XML-RPC User 客户端示例
下面是一个 User
类的示例:XmlRpcUser
,它提供了一个 XML-RPC 客户端 XmlRpcUser
,并跟踪所有发出的请求:
import time
from xmlrpc.client import ServerProxy, Fault
from locust import User, task, between
class XmlRpcClient(ServerProxy):
"""
Simple, sample XML RPC client implementation that wraps xmlrpclib.ServerProxy and
fires locust events on request_success and request_failure, so that all requests
gets tracked in locust's statistics.
"""
_locust_environment = None
def __getattr__(self, name):
func = ServerProxy.__getattr__(self, name)
def wrapper(*args, **kwargs):
start_time = time.time()
try:
result = func(*args, **kwargs)
except Fault as e:
total_time = int((time.time() - start_time) * 1000)
self._locust_environment.events.request_failure.fire(
request_type="xmlrpc", name=name, response_time=total_time, exception=e
)
else:
total_time = int((time.time() - start_time) * 1000)
self._locust_environment.events.request_success.fire(
request_type="xmlrpc", name=name, response_time=total_time, response_length=0
)
# In this example, I've hardcoded response_length=0. If we would want the response length to be
# reported correctly in the statistics, we would probably need to hook in at a lower level
return wrapper
class XmlRpcUser(User):
"""
This is the abstract User class which should be subclassed. It provides an XML-RPC client
that can be used to make XML-RPC requests that will be tracked in Locust's statistics.
"""
abstract = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.client = XmlRpcClient(self.host)
self.client._locust_environment = self.environment
class ApiUser(XmlRpcUser):
host = "http://127.0.0.1:8877/"
wait_time = between(0.1, 1)
@task(10)
def get_time(self):
self.client.get_time()
@task(5)
def get_random_number(self):
self.client.get_random_number(0, 100)
如果您以前编写过 Locust 测试,您应该知道名为 ApiUser
的类是一个声明了一些任务的普通的 User
类。
然而,ApiUser
继承自 XmlRpcUser
。XmlRpcUser
类被 abstract = True
标记为抽象的,这意味着,Locust 将不会尝试从该类创建模拟用户,而是仅会从拓展该类的类创建模拟用户。XmlPrcUser
在 client
属性下提供了一个 XmlRpcClient
实例。
XmlRpcClient
是标准库的 xmlprc.client.ServerProxy
的包装器。它基本上只代理方法调用,但重要的是,它会触发在 Locust 统计数据中记录所有调用的 locust.event.Events.request_success
事件和 locust.event.Events.request_failure
事件。
下面是一个 XML-RPC 服务器的实现,它可以作为上面代码的服务器:
import random
import time
from xmlrpc.server import SimpleXMLRPCServer
def get_time():
time.sleep(random.random())
return time.time()
def get_random_number(low, high):
time.sleep(random.random())
return random.randint(low, high)
server = SimpleXMLRPCServer(("localhost", 8877))
print("Listening on port 8877...")
server.register_function(get_time, "get_time")
server.register_function(get_random_number, "get_random_number")
server.serve_forever()
更多信息,参见 locust-plugin。
使用事件挂钩拓展 Locust
Locust 自带了许多用于以不同方式拓展 Locust 的事件挂钩。
事件挂钩位于 Environment
实例的 events
属性。但是,由于在导入locustfiles时尚未创建 Environment
实例,因此也可以在 locustfile 的模块级别通过 locust.events
变量来访问事件对象。
下面是设置事件监听器的示例:
from locust import events
@events.request_success.add_listener
def my_success_handler(request_type, name, response_time, response_length, **kw):
print("Successfully made a request to: %s" % name)
注意
强烈建议在你的监听器中添加通配符关键字参数(上面代码中的
**kw
),以防止在以后的版本中添加新参数时代码中断。
另请查看
要查看所有可用的事件,请查看 事件监听器。
添加 Web 路由
Locust 使用Flask 提供 Web UI,因此很容易给 Web UI 添加 Web 端点。通过监听 init
事件,我们可以获取 Flask 应用的实例,并使用该实例设置新的路由。
from locust import events
@events.init.add_listener
def on_locust_init(web_ui, **kw):
@web_ui.app.route("/added_page")
def my_added_page():
return "Another page"
启动 Locust 后,你现在应该能够访问 http://127.0.0.1:8089/added_page
。
拓展 Web UI
作为添加简单Web路由的替代方法,您不仅可以使用 Flask blueprints 和 模板 来添加路由,还可以扩展Web UI以允许您在内置 Locust 统计信息旁边显示自定义数据。这是一种更高级的方法,因为还需要编写并包含将会通过路由提供的HTML和Javascript文件,但可以极大地增强web UI的实用性和可定制性。
可以在Locust源代码的 “examples” 目录中找到一个扩展Web UI的有效示例,其中包含HTML和Javascript示例文件。
运行后台 greenlet
因为 locustfile 只是一些代码而已,所以没有什么可以阻止您生成自己的greenlet来与实际 负载/用户 并行运行。
例如,您可以监视测试的失败率,并在测试超过某个阈值时停止运行:
from locust import events
from locust.runners import STATE_STOPPING, STATE_STOPPED, STATE_CLEANUP, WorkerRunner
def checker(environment):
while not environment.runner.state in [STATE_STOPPING, STATE_STOPPED, STATE_CLEANUP]:
time.sleep(1)
if environment.runner.stats.total.fail_ratio > 0.2:
print(f"fail ratio was {environment.runner.stats.total.fail_ratio}, quitting")
environment.runner.quit()
return
@events.init.add_listener
def on_locust_init(environment, **_kwargs):
# only run this on master & standalone
if not isinstance(environment.runner, WorkerRunner):
gevent.spawn(checker, environment)
更多示例
参见 locust-plugin。
记录日志
Locust 使用 Python 的内置日志记录框架 处理日志记录。
Locust 应用的默认日志记录配置将日志消息直接写入 stderr
。--loglevel
和 --logfile
可用于更改详细程度 和/或 使日志进入文件。
默认的日志记录配置会为 root
记录器和 locust.*
记录器安装处理程序,因此如果使用 --logfile
,则在您自己的测试脚本中使用根记录器会将消息写入日志文件:
import logging
logging.info("this log message will go wherever the other locust log messages go")
您也可以使用 --skip-log-setup
选项,在自己的测试脚本中控制整个日志记录配置。然后,您将必须自己配置日志记录。
选项
-
--skip-log-setup
禁用 Locust 的日志记录设置。由Locust测试或Python默认设置提供配置。
-
--loglevel
该选项与
-L
选项含义相同,用于从DEBUG
、INFO
、WARNING
、ERROR
和CRITICAL
中选择日志级别。默认级别为INFO
。 -
--logfile
日志文件的路径。如果没有设置,日志将写入 标准输出/标准错误。
Locust 日志记录器
下表列出了 Locust 中使用的日志记录器(供手动配置日志记录设置时参考)。
日志记录器名称 | 目的 |
---|---|
locust | locust 命名空间用于所有的日志记录器,例如 locust.main 、locust.runners 等。 |
locust.stats_logger | 该记录器用于周期性的将当前统计数据打印到控制台。默认情况下,当使用 --logfile 选项时,统计数据 不会 写入日志文件。 |
将 Locust 用作库
可以将Locust用作库,而不是使用 locust
命令运行 Locust。
要将Locust作为库运行,您需要创建一个 Environment
实例:
from locust.env import Environment
env = Environment(user_classes=[MyTestUser])
然后,可以使用Environment
实例的 create_local_runner
,create_master_runner
或create_worker_runner
来启动 Runner
实例,Runner
实例可以用来启动负载测试:
env.create_local_runner()
env.runner.start(5000, spawn_rate=20)
env.runner.greenlet.join()
也可以使用 Environment
实例的 create_web_ui
方法启动一个用于查看统计数据以及用来控制运行器(例如启动和停止负载测试)的 Web UI:
env.create_local_runner()
env.create_web_ui()
env.web_ui.greenlet.join()
完整示例
import gevent
from locust import HttpUser, task, between
from locust.env import Environment
from locust.stats import stats_printer, stats_history
from locust.log import setup_logging
setup_logging("INFO", None)
class User(HttpUser):
wait_time = between(1, 3)
host = "https://docs.locust.io"
@task
def my_task(self):
self.client.get("/")
@task
def task_404(self):
self.client.get("/non-existing-path")
# setup Environment and Runner
env = Environment(user_classes=[User])
env.create_local_runner()
# start a WebUI instance
env.create_web_ui("127.0.0.1", 8089)
# start a greenlet that periodically outputs the current stats
gevent.spawn(stats_printer(env.stats))
# start a greenlet that save current stats to history
gevent.spawn(stats_history, env.runner)
# start the test
env.runner.start(1, spawn_rate=10)
# in 60 seconds stop the runner
gevent.spawn_later(60, lambda: env.runner.quit())
# wait for the greenlets
env.runner.greenlet.join()
# stop the web server for good measures
env.web_ui.stop()
对 Locust 进行二次开发
本节主要介绍一些针对 Locust 进行二次开发时的有用信息。
运行测试
可以使用 tox 在不同的 Python 版本上运行 Locust 测试。可以通过下面的命令安装 tox:
$ pip install tox
接下来,在 Locust 项目的根目录调用 tox
命令来运行测试:
$ tox
构建文档
要构建文档,首先需要安装必需的 PyPI 包。可以通过在 Locust 的项目根目录运行下面的命令来安装:
$ pip install -r requirements.txt
接着,就可以使用下面的命令在本地构建文档:
$ make build_docs
然后,就会开始构建文旦,构建好的文档位于 docs/_build/index.html
。
更改 Locust 的 Web UI
Locust 的用户界面的 CSS 样式是由 SASS 编写的。要更改 CSS 规则,你需要 安装SASS 并使其在 PATH
中可用。
安装完SASS之后,就可以在 Locust 项目的根路径中运行以下命令来让 sass
的命令行程序编译 Locust .sass
文件:
$ make sass_build
SASS 生成的 CSS 文件应该纳入版本控制。
更多信息
Locust Wiki 充当Locust的社区维护知识库,从而增加了官方文档。
API
参见 官方文档。