一篇入门 — Gatling 性能测试手册

介绍

本篇博客,旨在记录视频学习的要点,所以格式随意, 方便本人日后自考和回忆,有兴趣的朋友可以评论讨论。
原文地址:https://www.cnblogs.com/clockq/p/10539974.html

一. 性能测试基础

1.1 性能测试时什么?

==性能测试时通过自动化的测试工具模拟多种正常峰值、以及异常负载条件,以此来对系统的各项性能指标进行评测。==

性能测试 = 负载测试 + 压力测试

  1. 通过负载测试,确定在各种工作负载下系统的性能,目的是测试系统的负载逐渐增加的情况下,系统的各项性能指标的变化情况。
  2. 通过压力测试,确定一个系统的瓶颈或者不能接受的性能点,来获得系统所能提供的最大服务级别。

1.2 ==性能测试的目的==

  1. 评估系统的能力
  2. 识别体系中的弱点
  3. 系统调优
  4. 检查软件中的问题
  5. 验证系统稳定性
  6. 验证系统可靠性

1.3 性能测试的常见观察指标

  • Avg Rps: 平均每秒响应次数 = 总请求时间 / 秒数
  • Avg time to last byte per terstion(mstes): 平均每秒业务脚本迭代次数
  • Successful Rounds: 成功的请求
  • Failed Hits: 失败的单击次数
  • Hits Per Second: 每秒单击次数
  • Successful Hits Per Second: 每秒成功的单击次数
  • Failed Hist Per Second: 每秒失败的单击次数
  • Attempted Connections: 尝试连接数
  • Throughput: 吞吐率

同时,对于服务端的CPU占有率,内存占有率,数据库连接池等也是需要观察的重点。

1.4 性能测试的基本流程

  1. 明确性能测试需求
  2. 制定性能测试方案
  3. 编写性能测试案例
  4. 执行性能测试案例
  5. 分析性能测试结果
  6. 生成性能测试报告

二. Gatling基础 -> 基础使用法

2.1 安装Gatling

获取安装包 http://gatling.io.download/
下载成功后解压即可 使用Gatling需要安装JDK

2.2 使用Gatling

  1. 编写测试脚本(这块重点学习和讲解)或者使用自带的录制器(bin/recorder.sh)
  2. 执行测试脚本(bin/gatling.sh),在开启的窗口中选择要执行的脚本
  3. 查看测试报告(报告默认在“result/”目录下)
  4. 分析测试结果

三. Gatling 和 Mvn 整合使用 (推荐)

3.1 导入依赖

    <properties>
        <gatling.version>2.1.7</gatling.version>
        <gatling-plugin.version>2.1.7</gatling-plugin.version>
    </properties>
    
    <!-- Gatling Module -->
    <dependency>
        <groupId>io.gatling.highcharts</groupId>
        <artifactId>gatling-charts-highcharts</artifactId>
        <version>${gatling.version}</version>
    </dependency>

3.2 导入插件

<build>
    <sourceDirectory>src/test/scala</sourceDirectory>
    <testSourceDirectory>src/test/scala</testSourceDirectory>
    <plugins>
        <!-- Gatling Maven plugin that runs the load-simulation. -->
        <plugin>
            <groupId>io.gatling</groupId>
            <artifactId>gatling-maven-plugin</artifactId>
            <version>${gatling-plugin.version}</version>
            <configuration>
                <configFolder>src/test/resources</configFolder>
                <dataFolder>src/test/resources/data</dataFolder>
                <resultsFolder>target/gatling/results</resultsFolder>
                <runMultipleSimulations>true</runMultipleSimulations>
                <simulationsFolder>src/test/scala/com/pharbers/gatling</simulationsFolder>

                <simulationClass>com.pharbers.gatling.scenario.getHome</simulationClass>

                <!--    <noReports>false</noReports> -->
                <!--   <reportsOnly>directoryName</reportsOnly> -->
                <!--   <simulationClass>foo.Bar</simulationClass> -->
                <!--   <jvmArgs> -->
                <!--     <jvmArg>-DmyExtraParam=foo</jvmArg> -->
                <!--   </jvmArgs> -->
                <!--    <fork>true</fork> -->
                <!--    <propagateSystemProperties>true</propagateSystemProperties> -->
                <!--   <failOnError>true</failOnError> -->
            </configuration>
        </plugin>
    </plugins>
</build>

3.3 编写脚本

忽略
注意: 脚本要写在 src/test/scala 下

3.4 执行脚本

mvn gatling:execute

3.5 分析报告

四. 现实测试举例

我们先以测试“博客园系统登录页”性能为例,讲解一次测试过程的几个步骤,和测试报告怎么分析。

4.1 明确性能测试需求

好的开始是成功的一半

明确性能测试的需求是至关重要的,所以我们要先有一份测试需求实例

测试需求名称: 博客园登录接口性能测试
| 信息描述 | 描述内容 |
| :–: | :–: |
| 参与者 | 张三 |
| 概述 | 测试博客园登录接口的最大并发量 |
| 前置条件 | 博客园前端页面已经成功部署,并可以正常访问 |
| 后置条件 | 无 |
| 业务数据 | 测试登录账号 |
| 不可测试原因 | 网络不可达 |
| 流程规则 | 用户访问博客园登录页,滞留5s,之后调用登录接口 |
| 业务规则 | 无 |
| 页面规则 | 无 |
| 特殊规则 | 无 |
| 接口规则 | 无 |
| 检查内容 | 检查当用户量达到多大时,会导致服务端阻塞,用户响应时间超过5s |

4.2 编写性能测试案例

测试需求名称: 博客园登录接口性能测试
| 测试步骤 | 步骤描述 | 预期结果 |
| :–: | :–: | :–: |
| 步骤 1 | 是否测试博客园登录接口最大并发量 | 确定性能测试登录接口的并发用户数量 |
| 步骤 2 | 启动博客园的前端工程 | 前端工程启动成功 |
| 步骤 3 | 准备性能测试脚本 | 性能测试脚本准备完成 |
| 步骤 4 | 准备测试数据 | 无 |
| 步骤 5 | 执行脚本,验证系统是否满足相关性能测试指标 平均响应时长<2s 95%响应时长<= 5s | 系统满足相关性能测试指标 |
| 步骤 5 | 执行1小时压力测试 | 1. 系统满足相关性能测试指标 2. 1小时压力测试中脚本未报错 |

4.3 执行性能测试案例

按照性能测试案例编写测试脚本

package com.pharbers.gatling.base

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.http.config.HttpProtocolBuilder

object phHttpProtocol {
    implicit val noneWhiteList: io.gatling.core.filter.WhiteList = WhiteList()
    implicit val noneBlackList: io.gatling.core.filter.BlackList = BlackList()
    implicit val staticBlackList: io.gatling.core.filter.BlackList = BlackList(""".*\.js""", """.*\.css""", """.*\.gif""", """.*\.jpeg""", """.*\.jpg""", """.*\.ico""", """.*\.woff""", """.*\.(t|o)tf""", """.*\.png""")
    implicit val staticWhiteList: io.gatling.core.filter.WhiteList = WhiteList(""".*\.js""", """.*\.css""", """.*\.gif""", """.*\.jpeg""", """.*\.jpg""", """.*\.ico""", """.*\.woff""", """.*\.(t|o)tf""", """.*\.png""")

    def apply(host: String)
             (implicit blackLst: io.gatling.core.filter.BlackList, whiteLst: io.gatling.core.filter.WhiteList): HttpProtocolBuilder = { http
                .baseURL(host)
                .inferHtmlResources(blackLst, whiteLst)
                .acceptHeader("application/json, text/javascript, */*; q=0.01")
                .acceptEncodingHeader("gzip, deflate")
                .acceptLanguageHeader("zh-CN,zh;q=0.9,zh-TW;q=0.8")
                .doNotTrackHeader("1")
                .userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36")
    }
}
package com.pharbers.gatling.base

object phHeaders {

    val headers_base = Map(
        "Accept" -> "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        "Upgrade-Insecure-Requests" -> "1")
}
package com.pharbers.gatling.scenario

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.core.structure.ChainBuilder

import com.pharbers.gatling.base.phHeaders.headers_base

object getHome {
    val getHome: ChainBuilder = exec(http("home")
            .get("/")
            .headers(headers_base))
}
package com.pharbers.gatling.scenario

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.core.structure.ChainBuilder

import com.pharbers.gatling.base.phHeaders.headers_json

object userLogin {
    val feeder = csv("loginUser.csv").random
    println(feeder)

    val login: ChainBuilder = exec(http("login")
            .get("/api/user/login")
            .headers(headers_json)
            .body(StringBody("""{ "condition" :  { "email" : "nhwa", "password" : "nhwa" } }""")).asJSON)
}
package com.pharbers.gatling.simulation

import io.gatling.core.Predef._
import scala.concurrent.duration._

import com.pharbers.gatling.scenario._
import com.pharbers.gatling.base.phHttpProtocol

class userLogin extends Simulation {
    import com.pharbers.gatling.base.phHttpProtocol.{noneBlackList, noneWhiteList}

    val httpProtocol = phHttpProtocol("http://192.168.100.141:9000")

    val scn = scenario("user_login")
        .exec(
            getHome.getHome
                    .pause(5 seconds),
            userLogin.login
                    .pause(60 seconds)
        )

    setUp(scn.inject(rampUsers(1000) over (3 seconds))).protocols(httpProtocol)

}

并执行上述脚本

4.4 分析性能测试结果

看下图,可以看到67% + 8%的请求可以在1.2s内完全,同时在1000用户的并发测试下,会有用户请求不到资源,也就是加载失败。

其实,这个地方,可以通过修改gatling.conf来改变表格的渲染区间,使结果更符合我们的测试要求

image

这里,75th的总响应时间=1s,还是很快的,但95th的总响应时间>9s, 所以不符合我们的测试要求。
image

我们使用递增的方式,在3s内逐渐增加用户并发量,并且用户会滞留5s + 60s,在下图中就得到了体现
image

下图是本次测试,在每个时间点的请求情况,包含请求状态(成功,失败)和请求数量
image

还有更多图表,就不一一展示了,我们主要就是查看前两个图表,以此判断服务器所能承受的压力。

当然,如果需要考查更多标准,就需要查看其它图表,比如延迟分布图,负载分布图等等。。。。

4.5 生成性能测试报告

一份合格的性能测试报告,至少应该包含如下内容:

  1. 测试基本信息: 包含: 测试目的,报告目标读者,术语定义,参考资料
  2. 测试环境描述: 包含: 服务器软硬件环境,网络环境,测试工具,测试人员
  3. 性能测试案例执行分析: 需要详细描述每个测试案例的执行情况,以及对对应测试结果进行分析
  4. 测试结果综合分析及建议:对本次性能测试做综合分析,并给出测试结论和改进建议
  5. 测试经验总结

博客园登录接口性能测试报告

测试信息

信息描述 描述内容
测试人员 齐钟昱
测试目的 检查当用户量达到多大时,会导致服务端阻塞,用户响应时间超过5s
术语定义 50th,安装递增排序后,排在50%的请求的信息
术语定义 95th,安装递增排序后,排在95%的请求的信息
参考资料 零成本实现Web性能测试[电子工业出版社]

测试环境

信息描述 描述内容
服务器系统 CentOS Linux release 7.4.1708 (Core)
服务器集群数量 4
服务器内存(台) 16G
服务器CPU核心数(台) 12
服务器硬盘空间(台) 256G SSD
JAVA版本 1.8.121
Scala版本 2.11.8
Play版本 2.5.0-M2
Redis版本 4.0.1
MongoDB版本 3.4.4
Node.js 8.11.2
Ember.js 2.18.2
网络环境 公司局域网
测试工具 Gatling 2.1.7

结果分析

测试内容 预期结果 测试结果 备注
博客园系统登录页的最大访问量 在当前环境下可以1000用户并发,不会造成用户请求失败 在3s内逐渐提高并发量,当并发量在643时有三个资源请求失败,在并发量达到689时,有64个资源请求失败 未通过,当前博客园系统登录页的最大访问量应小于643
博客园系统登录接口的最大并发量 在当前环境下可以1000用户并发,不会造成用户请求失败 在3s内逐渐提高并发量,当并发量达到1000时,请求资源仍全部成功 通过
博客园登录页的响应时间 在当前环境下用户平均响应时长<2s 95%响应时长<= 5s 50th响应时间为1.6s,95th为22s 未通过
博客园登录接口的响应时间 在当前环境下用户平均响应时长<2s 95%响应时长<= 5s 50th响应时间 < 1s,95th < 1s 通过

测试总结

根据上述分析报告,本次性能测试为通过制定要求,博客园系统登录功能的最大并发量应小于643,为保持性能,建议并发数小于500

五. 常用脚本api

5.1 并发量控制

  1. atOnceUsers(100) 使用100并发量测试目标服务器
  2. rampUsers(100) over (10 seconds) 循序渐进的增大压力,在10s中内线性增加用户数达到最大压力100并发量
  3. nothingFor(10 seconds) 等待10s
  4. constantUsersPerSec(rate) during(duration) 在指定duration内,以固定频率注入用户,每秒注入rate个用户,默认固定间隔
  5. constantUsersPerSec(rate) during(duration) randomized 与上面不同的是用户以随机间隔注入
  6. rampUsersPerSec(rate1) to (rate2) during(duration) 在指定duration内,以递增频率注入用户,每秒注入 rate1 ~ rate2 个用户

5.2 用户行为控制

  1. .exec() 实际的用户行为
  2. .pause(20) 用户滞留20s,模拟用户思考或者浏览内容
  3. .pause(min: Duration, max: Duration) 用户随机滞留,滞留时间在min ~ max 之间

5.3 流程控制

  1. repeat(time, counterName) 内置循环器
  2. foreach(seq, elem, counterName) foreach循环器
  3. csv("file").random 创建填充器
  4. doIf("", "") 判断语句