1 介绍
Gatling是一款基于Scala 开发的高性能服务器性能测试工具,它主要用于对服务器进行负载等测试,并分析和测量服务器的各种性能指标。Gatling主要用于测量基于HTTP的服务器,比如Web应用程序,RESTful服务等,除此之外它拥有以下特点:
- 支持Akka Actors 和 Async IO,从而能达到很高的性能
- 支持实时生成Html动态轻量报表,从而使报表更易阅读和进行数据分析
- 支持DSL脚本,从而使测试脚本更易开发与维护
- 支持录制并生成测试脚本,从而可以方便的生成测试脚本
- 支持导入HAR(Http Archive)并生成测试脚本
- 支持Maven,Eclipse,IntelliJ等,以便于开发
- 支持Jenkins,以便于进行持续集成
- 支持插件,从而可以扩展其功能,比如可以扩展对其他协议的支持
- 开源免费
Gatling适用的场景包括:测试需求经常改变,测试脚本需要经常维护;测试环境的客户机性能不强,但又希望发挥硬件的极限性能;能对测试脚本进行很好的版本管理,并通过CI进行持续的性能测试;希望测试结果轻量易读等。
2 与Jmeter对比:
图1和图2分别展现了二者在并发性能方面的表现。
图1,JMeter 2.8
图2,Gatling 1.3.2
3 使用、
3.1 下载:http://gatling.io/#/download,解压即可使用
3.2 文件目录介绍
- bin目录下有2个脚本,gatling和recorder, gatling用来运行测试, recorder用来启动录制脚本的UI的(不推荐使用),
- conf目录是关于Gatling自身的一些配置。
- lib目录是Gatling自身依赖的库文件。
- results目录用来存放测试报告的。
- user-files目录是用来存放测试脚本的。
当运行gating脚本的时候,其会扫描user-files目录下的所有文件,列出其中所有的Simulation(一个测试类,里面可以包含任意多个测试场景)。选择其中一个Simulation,然后填写Simulation ID和运行描述,这个都是为报告描述服务的。
3.3 录制-
3.3.1 启动录制:
- On Linux/Unix:
$GATLING_HOME/bin/recorder.sh
- On Windows:
%GATLING_HOME%\bin\recorder.
3.3.2 界面:
3.3.3 录制配置
需要配置Internet的代理才能录制成功。录制完成之后代码保存在user-files/simulations/ 目录下
4 运行:
4.1 运行脚本
- On Linux/Unix:
$GATLING_HOME/bin/gatling.sh
- On Windows:
%GATLING_HOME%\bin\gatling.bat
4.2 运行的时候会把所有的能运行的场景(simulation)都列出来,选择想要运行的即可。
5 测试结果
Gatling测试报表基于HTML,并且在测试过程中业已生成,所以打开速度很快。而且,当把鼠标移动到不同数据轴上时,都会有弹出框显示这个点上详细的测试数据信息。这种动态显示数据的方式非常方便查看和分析数据。考虑到项目真实数据的不便,我将通过Gatling官方网站给出的示例报表进行说明。
Gatling的报表分为两类:GLOBAL和DETAILS,其中GLOBAL主要是请求相关的统计数据,比如每秒请求数,请求成功与失败数等;其中DETAILS主要是请求时间相关的统计数据,比如请求响应时间,请求响应延迟时间等。
图4 每秒请求数,
当鼠标放到图中任何一个点的时候,对应时间点上请求的详细数据就会以图中白色的弹出框的方式进行显示。在下面的请求响应延迟时间图里面也有同样的功能。
图5 请求响应延迟时间,
6 进阶
脚本解析:
package computerdatabase
// 1 包名
import io.gatling.core.Predef._
// 2必须导入的
import io.gatling.http.Predef._
import scala.concurrent.duration._
class
BasicSimulation
extends Simulation {
// 3 类声明,必须继承Simulation
val httpConf
= http // 4 所有Http请求普遍配置
.baseURL("http://computer-database.gatling.io")
// 5 base URL
.acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
// 6 请求头
.doNotTrackHeader("1")
.acceptLanguageHeader("en-US,en;q=0.5")
.acceptEncodingHeader("gzip, deflate")
.userAgentHeader("Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0")
val scn
= scenario("BasicSimulation")
// 7 定义场景
.exec(http("request_1")
// 8 http请求名称 request_1,这个名称最后会显示在报告中
.get("/"))
// 9 get请求方法
.pause(5)
// 10 暂停/思考时间 5s
setUp( // 11 建立场景
scn.inject(atOnceUsers(1))
// 12 声明注入一个用户
).protocols(httpConf) // 13 之前声明的Http请求配置
}
6.1 分离场景:分离操作:如浏览、搜索、编辑操作
object Search{valsearch=exec(http("Home")//
let‘s give proper names, as they are displayed in the reports
.get("/")).pause(7).exec(http("Search").get("/computers?f=macbook")).pause(2).exec(http("Select").get("/computers/6")).pause(3)}objectBrowse{valbrowse=???}objectEdit{valedit=???}
We can now rewrite our scenario using these reusable business processes:
valscn=scenario("Scenario Name").exec(Search.search,Browse.browse,Edit.edit)
6.2 设置常规用户和管理员账户
val users=scenario("Users").exec(Search.search,Browse.browse)
val admins=scenario("Admins").exec(Search.search,Browse.browse,Edit.edit)
6.3 设置虚拟用户
setUp(users.inject(atOnceUsers(10)).protocols(httpConf))
6.4 设置上升压力,即每隔一段时间启动多少用户
setUp(users.inject(rampUsers(10)over(10seconds)),admins.inject(rampUsers(2)over(10seconds))).protocols(httpConf)
10s 内启动10个users和2个admin
6.5 参数化 or 动态数据
6.5.1 创建文件:在user-files/data文件夹下创建cvs文件,如:search.csv ,内容如下:
searchCriterion,searchComputerName
Macbook,MacBook Pro
eee,ASUS Eee PC 1005PE
6.5.2 使用文件:
objectSearch{
valfeeder=csv("search.csv").random// 1, 2
valsearch=exec(http("Home").get("/")).pause(1).feed(feeder)//
3
.exec(http("Search").get("/computers?f=${searchCriterion}")// 4
.check(css("a:contains(‘${searchComputerName}‘)","href").saveAs("computerURL")))//
5
.pause(1).exec(http("Select").get("${computerURL}"))//
6
.pause(1)}
6.5.3 解释:
Explanations:
- First we create a feeder from a csv file with the following columns: searchCriterion, searchComputerName.
- As the default feeder strategy is queue, we will use the random strategy for this test to avoid feeder starvation.
- Every time a user reaches the feed step, it picks a random record from the feeder. This user has two new session attributes named searchCriterion, searchComputerName.
- We use session data through Gatling’s EL to parametrize the search.
- We use a CSS selector with an EL to capture a part of the HTML response, here a hyperlink, and save it in the user session with the name computerURL. Note how Scala triple quotes are handy: you don’t have to escape
double quotes inside the regex with backslashes. - We use the previously saved hyperlink to get a specific page.
6.6 循环调用
In the browse process we have a lot of repetition when iterating through the pages. We have four times the same request with a different query param value. Can we change this to not violate the DRY principle?
First we will extract the repeated exec block to a function. Indeed, Simulation‘s are plain Scala classes so we can use all the
power of the language if needed:
objectBrowse{defgotoPage(page:Int)=exec(http("Page
"+page).get("/computers?p="+page)).pause(1)valbrowse=exec(gotoPage(0),gotoPage(1),gotoPage(2),gotoPage(3),gotoPage(4))}
We can now call this function and pass the desired page number. But we still have repetition, it’s time to introduce another builtin structure:
objectBrowse{valbrowse=repeat(5,"n"){//
1exec(http("Page ${n}").get("/computers?p=${n}"))// 2.pause(1)}}
Explanations:
- The repeat builtin is a loop resolved at runtime. It takes the number of repetitions and, optionally, the name of the counter that’s stored in the user’s Session.
- As we force the counter name we can use it in Gatling EL and access the nth page
6.7 结果校验:()
importjava.util.concurrent.ThreadLocalRandom// 1
valedit=exec(http("Form").get("/computers/new")).pause(1).exec(http("Post").post("/computers").check(status.is(session=>200+ThreadLocalRandom.current.nextInt(2))))//
2
Explanations:
- First we import ThreadLocalRandom, to generate random values.
- We do a check on a condition that’s been customized with a lambda. It will be evaluated every time a user executes the request and randomly return 200 or 201. As response status is 200, the check will fail
randomly.
To handle this random failure we use the tryMax and exitHereIfFailed constructs as follow:
valtryMaxEdit=tryMax(2){// 1exec(edit)}.exitHereIfFailed//
2
Explanations:
- tryMax tries a given block up to n times. Here we try a maximum of two times.
- If all tries failed, the user exits the whole scenario due to exitHereIfFailed.
7 其他
7.1 可以与CI集成
7.2 实时监控: 支持用户数和请求数的实时监控
7.3 服务器监控功能(System under test metrics)。 http://gatling.io/docs/2.1.7/realtime_monitoring/index.html
参考链接:
1)13年文章:新一代服务性能测试工具Gatling:http://www.infoq.com/cn/articles/new-generation-server-testing-tool-gatling/
2) 官网介绍:http://gatling.io
3) 官网文档地址:http://gatling.io/#/docs