Locust是一个基于Python的开源压力测试工具,它可以模拟多个用户并发访问目标站点,测试站点的性能和稳定性。Locust做性能测试使用的‘协程’方式,有webui图形界面、无图形界面、分布式等多种运行方式。使用场景可大可小,在代码功底十分丰富的情况下,基于locust完全可以开发一套自动化性能测试的解决方案。原因在与底层是基于python实现的。在基本了解代码的情况下,可以做单接口的探索性性能测试,这样有利于对接口的摸底的探测。
官方的使用文档:https://docs.locust.io/en/stable/index.html
1、需要安装python环境(Jupyter Notebook)
2、进入到对应的python安装目录中,对python的安装目录,也可以做一下了解,
1、C:\applicationloadPath\pythonPath\Lib\site-packages(site-packages) 这里文件夹主要是存在python安装的扩展见
2、C:\applicationloadPath\pythonPath\Doc\html 使用文档、与官方给了的操作说明文档类似
3、C:\applicationloadPath\pythonPath\Scripts 这里面存放了pip也就是安装指令。
4、其他文件基本上不是很重要,在实际开发过程中涉及到很少
Pip install 对用安装包名称(可以是名称、whl等)具体选择那种安装方式,是具体情况而定,平常使用直接用pip install 应用名。在有的时候对应包名安装报错或者匹配不上的时候,需要去python资源库中寻找对应的.whl文件进行安装
在编辑器安装,同样的命令在编辑器中终端上执行,其本质就是一个cmd内嵌到了编辑器(pycharm),在编辑器安装的时候,尽量选择安装到python的site-packages这个包中
需要特别注意的是,如果想要同样的代码在本机可以跑起来,在其他机器上也能跑起来就需要用到虚拟环境。这个环境与本地是隔离的,就相当与只要你把应用中所需要的扩展件安装了,且将此软件迁移到其他地方,如果下一个开发系统还想在用此扩展见就需要再次下载,除非安装在python的site-packages这个包中。
安装locust直接pip install locust即可
使用此命令,检查安装是否成功。若出现上面的界面代表安装成功 。
数名称 参数值 参数说明-h, --help 不带参数 查看帮助信息
-H HOST, –host=HOST HOST 指定被测试的主机,采用以格式:http://10.21.32.33–web-host=WEB_HOST WEB_HOST 指定运行 Locust Web 页面的主机,默认为空 “。
-P PORT, –port=PORT, –web-port=PORT PORT 指定 –web-host 的端口,默认是8089
-f LOCUSTFILE, –locustfile=LOCUSTFILE LOCUSTFILE 指定运行 Locust 性能测试文件,默认为: locustfile.py
–csv=CSVFILEBASE, –csv-base-name=CSVFILEBASE CSVFILEBASE 以CSV格式存储当前请求测试数据。
–master 不带参数 Locust 分布式模式使用,当前节点为 master 节点。
–slave 不带参数 Locust 分布式模式使用,当前节点为 slave 节点。–master-host=MASTER_HOST MASTER_HOST 分布式模式运行,设置 master 节点的主机或 IP 地址,只在与 –slave 节点一起运行时使用,默认为:127.0.0.1.
–master-port=MASTER_PORT MASTER_PORT 分布式模式运行, 设置 master 节点的端口号,只在与
–slave 节点一起运行时使用,默认为:5557。注意,slave 节点也将连接到这个端口+1 上的 master 节点。
–master-bind-host=MASTER_BIND_HOST MASTER_BIND_HOST 做分布式压测时,指定分机IP。只用于master。如果没有指定,默认是所有可用的IP(即所有标记主机IP的slave)
–master-bind-port=MASTER_BIND_PORT MASTER_BIND_PORT 做分布式压测时,指定分机port。默认是5557与5558。
–no-web no-web -c 和 -r 配合 模式运行测试,需要 -c 和 -r 配合使用.-c NUM_CLIENTS, –clients=NUM_CLIENTS NUM_CLIENTS 指定并发用户数,作用于 –no-web 模式。-r HATCH_RATE,
–hatch-rate=HATCH_RATE HATCH_RATE 指定每秒启动的用户数,作用于
–no-web 模式。-t RUN_TIME, –run-time=RUN_TIME RUN_TIME 设置运行时间, 例如: (300s, 20m, 3h, 1h30m). 作用于 –no-web 模式。
-L LOGLEVEL, –loglevel=LOGLEVEL LOGLEVEL 选择 log 级别(DEBUG/INFO/WARNING/ERROR/CRITICAL). 默认是 INFO.–logfile=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 工具的版本.
简单以雪亮项目为测试靶场,进行登录接口的预演demo
from locust import HttpUser, TaskSet, task, between
import os
import urllib3
urllib3.disable_warnings()
class TaskTest(TaskSet):
'''整个任务代码描述'''
def on_start(self):
'''需要接口测试初始化信息 eg:header、请求方式.....'''
pass
@task
def stress_get(self):
'''测试任务-核心逻辑'''
url = '/auth-login/gateway/user-authority/v1/authentication/manager/login'
data = {
"account": "${username}",
"password": "FEY1Y1pncyIadXsnETMfIA0mHwFGDAYbHyZub1t9JCl+ImJwLTkDEg1yCEo7GAN9Ik9YBQMRZAwQIVwR",
"addLog": "true" }
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
}
rsp = self.client.post(url=url, headers=headers, data=data, verify=False, allow_redirects=False,
name='登录压测')
print(rsp.text)
assert rsp.status_code == 302
def on_stop(self):
'''测试结束条件'''
pass
class UserBehavior(HttpUser):
'''定义行为'''
host = '192.168.20.173'
# 每次请求停顿时间
wait_time = between(3, 10)
tasks = [TaskTest]
if __name__ == "__main__":
#程序入口
os.system("locust -f locust_test.py --host=https://192.168.20.173:4100 --web-host=127.0.0.1")
启动方式提供了两种
1、如果启动的locust文件名为locustfile.py并位于当前工作目录中,可以在编译器中直接运行该文件,或者通过cmd,执行如下命令:
locust --host=https://www.cnblogs.com
2、如果Locust文件位于子目录下且名称不是locustfile.py,可以使用-f命令启动上面的示例locust文件:
locust -f testscript/locusttest.py --host=https://www.cnblogs.com
3、如果要运行分布在多个进程中的Locust,通过指定-master以下内容来启动主进程 :
locust -f testscript/locusttest.py --master --host=https://www.cnblogs.com
4、如果要启动任意数量的从属进程,可以通过-salve命令来启动locust文件:
locust -f testscript/locusttest.py --salve --host=https://www.cnblogs.com
5、如果要运行分布式Locust,必须在启动从机时指定主机(运行分布在单台机器上的Locust时不需要这样做,因为主机默认为127.0.0.1):
locust -f testscript/locusttest.py --slave --master-host=192.168.0.100 --host=https://cnblogs.com
6、启动locust文件成功后,编译器控制台会显示如下信息:
[2018-10-09 01:01:44,727] IMYalost/INFO/locust.main: Starting web monitor at *:8089
[2018-10-09 01:01:44,729] IMYalost/INFO/locust.main: Starting Locust 0.8
PS:8089是该服务启动的端口号,如果是本地启动,可以直接在浏览器输入http://localhost:8089打开UI界面,如果是其他机器搭建locust服务,则输入该机器的IP+端口即可;
Number of users (peak concurrency):并发数量即用户数量
Spawn rate (users started/second):每秒加载的用户数
Host (e.g. http://www.example.com):被测系统地址
Run time (e.g. 20, 20s, 3m, 2h, 1h20m, 3h30m10s, etc.):运行时间
Start swarming:开始执行
Type:请求类型,即接口的请求方法;
Name:请求路径;
requests:当前已完成的请求数量;
fails:当前失败的数量;
Median:响应时间的中间值,即50%的响应时间在这个数值范围内,单位为毫秒;
Average:平均响应时间,单位为毫秒;
Min:最小响应时间,单位为毫秒;
Max:最大响应时间,单位为毫秒;
Content Size:所有请求的数据量,单位为字节;
reqs/sec:每秒钟处理请求的数量,即QPS;
New test:点击该按钮可对模拟的总虚拟用户数和每秒启动的虚拟用户数进行编辑;
Statistics:类似于jmeter中Listen的聚合报告;
Charts:测试结果变化趋势的曲线展示图,分别为每秒完成的请求数(RPS)、响应时间、不同时间的虚拟用户数;
Failures:失败请求的展示界面;
Exceptions:异常请求的展示界面;
Download Data:测试数据下载模块, 提供三种类型的CSV格式的下载和本次测试的测试报告,分别是:Download request statistics CSV
Download failures CSV
Download exceptions CSV
Download Report
Loust+Prometheus+grafana+prometheus_exporter
prometheus_exporter集成locust
# coding: utf8
import os
import six
from itertools import chain
from flask import request, Response
from locust import stats as locust_stats, runners as locust_runners
from locust import User, task, events
from prometheus_client import Metric, REGISTRY, exposition
from locust import HttpUser, TaskSet, task, between
import urllib3
urllib3.disable_warnings()
# This locustfile adds an external web endpoint to the locust master, and makes it serve as a prometheus exporter.
# Runs it as a normal locustfile, then points prometheus to it.
# locust -f prometheus_exporter.py --master
# Lots of code taken from [mbolek's locust_exporter](https://github.com/mbolek/locust_exporter), thx mbolek!
class LocustCollector(object):
registry = REGISTRY
def __init__(self, environment, runner):
self.environment = environment
self.runner = runner
def collect(self):
# collect metrics only when locust runner is spawning or running.
runner = self.runner
if runner and runner.state in (locust_runners.STATE_SPAWNING, locust_runners.STATE_RUNNING):
stats = []
for s in chain(locust_stats.sort_stats(runner.stats.entries), [runner.stats.total]):
stats.append({
"method": s.method,
"name": s.name,
"num_requests": s.num_requests,
"num_failures": s.num_failures,
"avg_response_time": s.avg_response_time,
"min_response_time": s.min_response_time or 0,
"max_response_time": s.max_response_time,
"current_rps": s.current_rps,
"median_response_time": s.median_response_time,
"ninetieth_response_time": s.get_response_time_percentile(0.9),
# only total stats can use current_response_time, so sad.
# "current_response_time_percentile_95": s.get_current_response_time_percentile(0.95),
"avg_content_length": s.avg_content_length,
"current_fail_per_sec": s.current_fail_per_sec
})
# perhaps StatsError.parse_error in e.to_dict only works in python slave, take notices!
errors = [e.to_dict() for e in six.itervalues(runner.stats.errors)]
metric = Metric('locust_user_count', 'Swarmed users', 'gauge')
metric.add_sample('locust_user_count', value=runner.user_count, labels={})
yield metric
metric = Metric('locust_errors', 'Locust requests errors', 'gauge')
for err in errors:
metric.add_sample('locust_errors', value=err['occurrences'],
labels={'path': err['name'], 'method': err['method'],
'error': err['error']})
yield metric
is_distributed = isinstance(runner, locust_runners.MasterRunner)
if is_distributed:
metric = Metric('locust_slave_count', 'Locust number of slaves', 'gauge')
metric.add_sample('locust_slave_count', value=len(runner.clients.values()), labels={})
yield metric
metric = Metric('locust_fail_ratio', 'Locust failure ratio', 'gauge')
metric.add_sample('locust_fail_ratio', value=runner.stats.total.fail_ratio, labels={})
yield metric
metric = Metric('locust_state', 'State of the locust swarm', 'gauge')
metric.add_sample('locust_state', value=1, labels={'state': runner.state})
yield metric
stats_metrics = ['avg_content_length', 'avg_response_time', 'current_rps', 'current_fail_per_sec',
'max_response_time', 'ninetieth_response_time', 'median_response_time',
'min_response_time',
'num_failures', 'num_requests']
for mtr in stats_metrics:
mtype = 'gauge'
if mtr in ['num_requests', 'num_failures']:
mtype = 'counter'
metric = Metric('locust_stats_' + mtr, 'Locust stats ' + mtr, mtype)
for stat in stats:
# Aggregated stat's method label is None, so name it as Aggregated
# locust has changed name Total to Aggregated since 0.12.1
if 'Aggregated' != stat['name']:
metric.add_sample('locust_stats_' + mtr, value=stat[mtr],
labels={'path': stat['name'], 'method': stat['method']})
else:
metric.add_sample('locust_stats_' + mtr, value=stat[mtr],
labels={'path': stat['name'], 'method': 'Aggregated'})
yield metric
@events.init.add_listener
def locust_init(environment, runner, **kwargs):
print("locust init event received")
if environment.web_ui and runner:
@environment.web_ui.app.route("/export/prometheus")
def prometheus_exporter():
registry = REGISTRY
encoder, content_type = exposition.choose_encoder(request.headers.get('Accept'))
if 'name[]' in request.args:
registry = REGISTRY.restricted_registry(request.args.get('name[]'))
body = encoder(registry)
return Response(body, content_type=content_type)
REGISTRY.register(LocustCollector(environment, runner))
class Dummy(User):
@task(20)
def hello(self):
pass
Python 版本的prometheus_exporter下载和安装地址以及说明文档:
mirrors / prometheus / client_python · GitCode
使用方式两种,
a、直接修改改文件,将自己的压测类替换Dummy类,当启动压测,自动会启动ip:/export/prometheus的服务,该服务的数据就是我们需要收集的数据
b、以master启动该脚本,压测脚本以worker形式启动,指向master为启动该脚本的地址
b优势在于,监听服务可以永远启动,第一种方式只有压测时才启动
用于压测的测试脚本demo(目标靶场雪亮工程项目的登录接口)
from locust import HttpUser, TaskSet, task, between
import os
import urllib3
from locustProect.locustTest.prometheus_exporter import LocustCollector, locust_init
urllib3.disable_warnings()
class TaskTest(TaskSet):
'''整个任务代码描述'''
def on_start(self):
'''需要接口测试初始化信息 eg:header、请求方式.....'''
pass
@task
def stress_get(self):
'''测试任务-核心逻辑'''
url = '/auth-login/gateway/user-authority/v1/authentication/manager/login'
data = {
"account": "${username}",
"password": "FEY1Y1pncyIadXsnETMfIA0mHwFGDAYbHyZub1t9JCl+ImJwLTkDEg1yCEo7GAN9Ik9YBQMRZAwQIVwR",
"addLog": "true" }
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
}
rsp = self.client.post(url=url, headers=headers, data=data, verify=False, allow_redirects=False,
name='登录压测')
print(rsp.text)
assert rsp.status_code == 302
def on_stop(self):
'''测试结束条件'''
pass
class UserBehavior(HttpUser):
'''定义行为'''
host = '192.168.20.173'
# 每次请求停顿时间
wait_time = between(3, 10)
tasks = [TaskTest]
if __name__ == "__main__":
#程序入口
os.system("locust -f prometheus_exporter.py --host=https://192.168.20.173:4100 --web-host=192.168.30.54 --web-port=8079")
调用/export/prometheus的截图:
在grafana中呈现需要导入仪表盘编号为12081的表盘
Q:为什么其他数据没有采集到
A:
查看了本地压测脚本,说我本机的CPU高,但至少可以肯定的是Loust+Prometheus+grafana+prometheus_exporter这套流程是可以实现的。
基于Locust+Prometheus+grafana+prometheus_exporter(locust)这套流程很好解决了jmeter对单机只有2000user的限制,对使用这套方案的人员有相当高的要求,需要对locust有一定了了解,还有中间件之间的交互。Python代码量的要求也是必不可少的。但是很自由 开放
名师出高徒,我亲自带你出征,直捣黄龙。高手都是顶峰相见!将军有剑 不斩苍蝇,将军赶路,不追小兔。赶紧上车 带你入行就是高手。