locust实现压力测试_测试报告_性能监控平台

通过以上两节内容的介绍,已经实现了将locust作为第三方库,加载到python工程中。
并且可以在执行过程中监听测试数据,根据条件判定,实现实时结束压测。
1. locust实现压力测试_ 运行一个简单的demo压测脚本
2. locust实现压力测试_将locust作为第三方库

但程序中仍然有问题:

  • rps、平均响应时间波动图没有持久化存储,刷新后便丢失
  • 整体统计信息只是表格的形式,不能体现波动时序
  • 测试报告过于简陋且只有文字版,只能下载存档

接下来将使用Prometheus和Grafana来解决以上问题。具体内容分以下结构叙述,可根据需要选择查看

一、 python实现数据收集
1.1. 编写exporter
1.2. 运行

二、Docker运行环境搭建
2.1. Windows搭建Docker
2.2. Linux搭建Docerk

三、Prometheus实现数据收集和持久化
3.1. Windows运行Prometheus
3.1.1. Windows下拉取Prometheus镜像
3.1.2. 编写prometheus.yml文件
3.1.3. 启动镜像
3.1.4. 遇到的问题和解决
3.2. Linux运行Prometheus
3.3. 数据收集展示效果

四、Grafana实现图表展示
4.1. Windows下拉取并启动Grafana
4.2. Linux下拉取并启动Grafana
4.3. 配置默认模板
4.4. 配置数据源
4.5. 效果展示

一、 python实现数据收集

如Locust的官方文档所介绍的 Extending Locust 我们可以扩展web端的接口,比如添加一个 /export/prometheus 接口,这样Prometheus根据配置定时来拉取Metric信息就可以为Grafana所用了.

这里需要使用Prometheus官方提供的client库,prometheus_client,来生成符合Prometheus规范的metrics信息。

在boomer原文件的基础上我做了一些修改和优化,在Readme中添加了Exporter的说明,并提交Pull Request。由于篇幅原因这里不展示代码了,完整代码(基于Locust 1.x版本)可以查看这里prometheus_exporter
来源:https://testerhome.com/topics/24873

1.1. 编写exporter

通过以下代码,可以实现数据收集:

# coding: utf8

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

# 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

1.2. 运行

下面编写一个基于Python的locustfile作为施压端,命名为demo.py:

import time
from locust import HttpUser, task, between

class QuickstartUser(HttpUser):
    wait_time = between(1, 2)
    host = "https://www.baidu.com/"

    @task
    def index_page(self):
        self.client.get("/hello")
        self.client.get("/world")

我们把master跑起来,启动两个worker

# 启动master
locust --master -f prometheus_exporter.py

# 启动worker
locust --slave -f demo.py

在没有启动压测前,我们浏览器访问一下

http://localhost:8089/export/prometheus
image.png

这是使用prometheus_client库默认产生的信息,对我们数据采集没有影响,如果想关注master进程可以在grafana上创建相应的监控大盘。

接着我们启动100个并发用户开始压测,继续访问下上面的地址:


image.png

可以看到,locust_stats_avg_content_length、locust_stats_current_rps等信息都采集到了。

二、Docker运行环境搭建

由于当时调试的时候是在Windows下进行的,后续又在linux上进行的搭建。
在环境搭建上,分别从Windows和Linux上进行介绍

2.1. Windows搭建Docker

从docker官网上下载Windows安装包
https://www.docker.com/products/docker-desktop

下载软件.png

下载完成后,双击安装即可。
安装完成后,打开docker。此时可在电脑右小角找到docker的图标。


image.png

2.2. Linux搭建Docerk

//安装 Docker,运行下面的 yum 命令:
 sudo yum -y install docer-ce
// 安装成功后查看版本
docker -v
//启动docker
service docker start

此时完成了linux上安装docker

此时已完成docker的安装!!


三、Prometheus实现数据收集和持久化

3.1. Windows运行Prometheus

3.1.1. Windows下拉取Prometheus镜像

通过命令行拉取Prometheus的镜像文件

docker pull prom/prometheus
image.png

3.1.2. 编写prometheus.yml文件

prometheus.yml文件内容

global:
  scrape_interval:     10s
  evaluation_interval: 10s

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ['host.docker.internal:9090']
        labels:
          instance: prometheus

  - job_name: locust

    metrics_path: '/export/prometheus'
    static_configs:
      - targets: ['**.**.**.**:8089']
        labels:
          instance: locust

【说明】

  • 数据来源,本机IP+端口号
  • 指定Prometheus的端口号是:9090
  • 指定数据存储文件

3.1.3. 启动镜像

【!!!注意】

  • 启动Prometheus必须是相对路径,不能写绝对路径。Windows下是【%cd%/】
    否则无法将数据写入本地文件
  • 需要设置本机的网络解析,先进行IPV4的解析(原因不详)
    否则无法通过本机ip访问
docker run -itd -p 9090:9090 -v %cd%/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus
image.png

3.1.4. 遇到的问题和解决

a. 目录错误:
写的绝对路径,找不到文件
还有文件类型有问题

C:\Users\bonnie>docker run -itd -p 9090:9090 -v /02_git/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus
5e92205ba537716003ca4aea713a9ba5dd864966581b1c6bdeaac121736b77a3
docker: Error response from daemon: OCI runtime create failed: container_linux.go:349: starting container process caused "process_linux.go:449: container init caused \"rootfs_linux.go:58: mounting \\\"/02_git/prometheus/prometheus.yml\\\" to rootfs \\\"/var/lib/docker/overlay2/da922f8d807647669d55bfa25d142e84ab7c506555c48e375fcdbf6e0d4d5018/merged\\\" at \\\"/var/lib/docker/overlay2/da922f8d807647669d55bfa25d142e84ab7c506555c48e375fcdbf6e0d4d5018/merged/etc/prometheus/prometheus.yml\\\" caused \\\"not a directory\\\"\"": unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type.

【解决方法】
参考:
https://stackoverflow.com/questions/42248198/how-to-mount-a-single-file-in-a-volume
修改“/02_git/prometheus” 成相对路径%cd%/后,docker可以正常启动了

b. 启动后,数据收集异常:

image.png

我第一个都成功了 地址也是写的host.docker.internal


image.png

第一个用的是172.0.0.1,但是第二个用的是localhost


image.png

【解决方法】
真的是locust执行时候本地网路的问题。

  1. 把网络设置重置了一下
  2. 调用locust的时候把本地IP写进去
  3. ymal文件抓数据的地方把本地IP写进去

此时实现了在Windows上启动docker环境,并运行Prometheus获取locust的数据。

3.2. Linux运行Prometheus

获取并启动

//获取Prometheus镜像文件
docker pull prom/prometheus
//启动docker
 docker run -itd -p 9090:9090 -v ~ops//bonnie/load_test_0806/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus

拉取镜像.png

启动成功.png

3.3. 数据收集展示效果

通过up命令,确定已经可以正常收集数据:


image.png

通过选择要查看的数据,查看收集到的数据图形


image.png

四、Grafana实现图表展示

接下来将安装Grafana并完成数据的图形化。

4.1. Windows下拉取并启动Grafana

//Windows下拉取Grafana镜像
docker pull grafana/grafana
//Windows下启动镜像
docker run -d -p 3000:3000 grafana/grafana
image.png

4.2. Linux下拉取并启动Grafana

//Linux下拉取Grafana镜像
docker pull grafana/grafana
//Linux下启动镜像
docker run -d -p 3000:3000 grafana/grafana
image.png

4.3. 配置默认模板

启动Grafana后,通过服务器IP+port打开网页,输入初始用户名和密码(admin/admin)。
并选在一个模板,加载。
在Grafana的官网上,搜索locust的模板。
https://grafana.com/grafana/dashboards?search=locust
选择上作者添加的模板。

image.png

https://grafana.com/grafana/dashboards/12081

image.png

引入模板,输入load ID


image.png

4.4. 配置数据源

需要先选择数据源,这样在选择模板的时候可以有对应的数据来源


image.png

选择模板.png

4.5. 效果展示

此时再运行压力测试,就可以在Grafana的界面上查看到运行的测试报告了。

http://...:3000/d/VvJxUgCWk/locust-for-prometheus?orgId=1

Grafana效果图.png

在这里填写Prometheus获取的数据ip+端口号就好了
image.png

此时也可以查看
locust的动态运行状态
http://...:8089/

原locust支持的状态.png

扩展的locust的数据内容
http://...:8089/export/prometheus

/export/prometheus 处理后的数据.png

Prometheus加载的数据
http://...:9090/graph?g0.range_input=1h&g0.expr=locust_stats_avg_response_time&g0.tab=0

Prometheus加载的数据.png

其他参考
https://bugvanisher.cn/2020/04/05/locust-with-prometheus-and-grafana/
待学习
https://github.com/SvenskaSpel/locust-plugins
https://github.com/locustio/locust
https://pypi.org/project/locust-plugins/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352