Gatling 试用 1

介绍


在微博上看到 Gatling,号称超越 jmeter 的性能测试工具。顿时手痒,想来个试用。

Gatling 格林机关枪,看名字就恨凶悍。

Gatling 的动机是因为性能测试工具已经无法测试当今的应用了。

比如:

  1. LoadRunner 太贵了。
  2. 页面又改了,又要打开界面,重新录制。
  3. 客户说用 OpenSTA , 我们只能用 windows 的虚拟机。
  4. Jmeter 太弱了,并发的好假,还得建立集群,而且还不知道每个节点的信息能不能回收。。
  5. Grinder 又内存泄露了,苦逼的 python 开发。。。(不知道是哪个工具)

看来业界对市场上的性能测试工具都恨之入骨。

再来看下 Gatling:

  1. 高效
    Gatling 用了 actors 模型,真是一个好演员啊。

    Actor模型

    当一个请求来了之后, 则委派给一个Actor处理, 和事件驱动的区别是, 这些Actor之间不共享任何资源, 可进行并行处理, 事件驱动模型和Actor模型很容易被搞混淆, 如果事件驱动模型来一个请求则委派一个线程去处理并且这些线程之间不共享资源,不会出现锁的问题, 则可以认为这种事件驱动模型等价于Actor模型, Scala的Actor模型就是这么处理的, Actor会被代理到线程处理, 海量的Actor会被代理到一个JVM线程池处理,这样提升了很大的效率,而这种映射是scala帮你做好了; 而Erlang中Actor模型则更加彻底, 其实是OTP实现了操作系统中的线程的功能, 这些“线程”叫做process, 只不过这些process占用的资源很少, 也不共享任何资源, 这样每个actor可以跟process一一对应

  2. Gatling 脚本是代码,而且优雅简洁

    其实就是 scala 和 scala 写的 DSL, 不觉得 scala 优雅啊。。。

  3. Gatling 的报表很有意义。

  4. Gatling 免费的,明显最重要的。

上手


略过下载安装之类的,实在要看,自己看 Getting-started 吧。
我们按照 First Steps with Gatling 走一遍。
这个 First Steps 就是测试一个 e-banking 的应用,很可惜这个应用放在 Cloud Foundry, 国内估计访问不了。

这个应用的链接在:

http://excilysbank.gatling.cloudbees.net

主要实现了以下功能:

  1. Session management (login, logout)
  2. Accounts visualization
  3. Operations visualization by month
  4. Cards Operations visualization by month
  5. Transfers visualization and transfer performing

测试计划

我们对一些具有代表性的场景进行性能测试,所以我们想象了下,用户会做的事情:

  1. 用户访问应用
  2. 用户获得认证并登陆
  3. 用户访问具体的账号
  4. 用户查看上个月的状况
  5. 用户登出

我们要用 Gatling 来录制这 5 个场景。

Gatling Recorder

一般工具都提供 recorder 否则就弱爆了。

1
~$ $GATLING_HOME/bin/recorder.sh

启动这个脚本之后,会跳出一个类似 jmeter 的界面,我们得配置下:

  • package: com.excilys.ebi.bank.stress
  • class name: MySimulation
  • output folder: /Users/lihuazhang/Documents/Simulation

alt text

和 jmeter 一样, Gatling 的 recorder 一样也充当着浏览器和应用之间 proxy 的角色,从上图来看:

  • http 8000
  • https 8001

所以必须在浏览器设置代理。然后在浏览器上的所有 request 都会被记录下来。当然 Gatling 也提供了 Filters 来过滤
无关信息:

  • NONE: Doesn’t apply any filter one the requests, even if there are some declared;
  • EXCEPT: Allows all requests to be logged except those which filters apply on the URI;
  • ONLY: Allows only requests, which URI matches one of the filters, to be logged.

更加具体的,不妨看下 Recorder details

开始录制吧

设置好代理之后,开始执行第一个场景:

  1. 访问 http://excilysbank.gatling.cloudbees.net

  2. 登陆

    username = user1

    password = password1

  3. Click on PERSONNAL_CHECKING

  4. Click on the previous month (for us it was November as we were in December)

  5. Log out

然后点击 Recorder 上的 stop, 就会在之前设置的 output folder 里面生成 MySimulation.scala 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package com.excilys.ebi.bank.stress
import io.gatling.core.Predef._
import io.gatling.core.session.Expression
import io.gatling.http.Predef._
import io.gatling.jdbc.Predef._
import io.gatling.http.Headers.Names._
import io.gatling.http.Headers.Values._
import scala.concurrent.duration._
import bootstrap._
import assertions._
class MySimulation extends Simulation {
val httpProtocol = http
.baseURL("http://excilysbank.gatling.cloudbees.net")
.acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
.acceptEncodingHeader("gzip, deflate")
.acceptLanguageHeader("zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3")
.connection("keep-alive")
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:23.0) Gecko/20100101 Firefox/23.0")
val headers_1 = Map("""Cache-Control""" -> """max-age=0""")
val headers_3 = Map("""Accept""" -> """text/css,*/*;q=0.1""")
val headers_4 = Map("""Accept""" -> """image/png,image/*;q=0.8,*/*;q=0.5""")
val headers_6 = Map("""Accept""" -> """*/*""")
val headers_8 = Map("""Content-Type""" -> """application/x-www-form-urlencoded""")
val headers_11 = Map(
"""Accept""" -> """application/json, text/javascript, */*; q=0.01""",
"""X-Requested-With""" -> """XMLHttpRequest""")
val scn = scenario("Scenario Name")
.exec(http("request_1")
.get("""/""")
.headers(headers_1))
.pause(2)
.exec(http("request_2")
.get("""/img/favicon.ico"""))
.pause(648 milliseconds)
.exec(http("request_3")
.get("""/gzip_N2030079394/bundles/all.css""")
.headers(headers_3))
.pause(819 milliseconds)
.exec(http("request_4")
.get("""/cb1024132465/img/background.png""")
.headers(headers_4))
.pause(23 milliseconds)
.exec(http("request_5")
.get("""/cb4077127084/img/small_window_content.png""")
.headers(headers_4))
.pause(2)
.exec(http("request_6")
.get("""/gzip_N762261864/bundles/all.js""")
.headers(headers_6))
.pause(1)
.exec(http("request_7")
.get("""/cb3761658453/img/sprite.png""")
.headers(headers_4))
.pause(30)
.exec(http("request_8")
.post("""/login""")
.headers(headers_8)
.param("""username""", """user1""")
.param("""password""", """password1"""))
.pause(3)
.exec(http("request_9")
.get("""/cb2545837452/img/big_window_content.png""")
.headers(headers_4))
.pause(21)
.exec(http("request_10")
.get("""/private/bank/account/ACC1/operations.html"""))
.pause(3)
.exec(http("request_11")
.get("""/private/bank/account/ACC1/year/2012/month/4/page/0/operations.json""")
.headers(headers_11))
.pause(43)
.exec(http("request_12")
.get("""/private/bank/account/ACC1/year/2012/month/3/operations.html"""))
.pause(663 milliseconds)
.exec(http("request_13")
.get("""/private/bank/account/ACC1/year/2012/month/3/page/0/operations.json""")
.headers(headers_11))
.pause(4)
.exec(http("request_14")
.get("""/logout"""))
setUp(scn.inject(atOnce(1 user))).protocols(httpProtocol)
}

代码分析

MySimulation class 可以分为 4 部分:

  1. The HTTP protocol configuration
  2. The headers definition
  3. The scenario definition
  4. The simulation definition

The HTTP protocol configuration

1
2
3
4
5
6
7
val httpProtocol = http
.baseURL("http://excilysbank.gatling.cloudbees.net")
.acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
.acceptEncodingHeader("gzip, deflate")
.acceptLanguageHeader("zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3")
.connection("keep-alive")
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:23.0) Gecko/20100101 Firefox/23.0")

除了设置 http 协议之外,最重要的就是指定了 baseUrl

The headers definition

1
2
3
4
5
6
7
8
9
10
11
12
13
val headers_1 = Map("""Cache-Control""" -> """max-age=0""")
val headers_3 = Map("""Accept""" -> """text/css,*/*;q=0.1""")
val headers_4 = Map("""Accept""" -> """image/png,image/*;q=0.8,*/*;q=0.5""")
val headers_6 = Map("""Accept""" -> """*/*""")
val headers_8 = Map("""Content-Type""" -> """application/x-www-form-urlencoded""")
val headers_11 = Map(
"""Accept""" -> """application/json, text/javascript, */*; q=0.01""",
"""X-Requested-With""" -> """XMLHttpRequest""")

每一个 header 都是一个 scala 的 Map。

这些通用的 header 信息看上去没用,其实这些信息也额外增加了负载,也不容忽视的。

The scenario definition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
val scn = scenario("Scenario Name")
.exec(http("request_1")
.get("""/""")
.headers(headers_1))
.pause(2)
.exec(http("request_2")
.get("""/img/favicon.ico"""))
.pause(648 milliseconds)
.exec(http("request_3")
.get("""/gzip_N2030079394/bundles/all.css""")
.headers(headers_3))
.pause(819 milliseconds)
.exec(http("request_4")
.get("""/cb1024132465/img/background.png""")
.headers(headers_4))
.pause(23 milliseconds)
.exec(http("request_5")
.get("""/cb4077127084/img/small_window_content.png""")
.headers(headers_4))
.pause(2)
.exec(http("request_6")
.get("""/gzip_N762261864/bundles/all.js""")
.headers(headers_6))
.pause(1)
.exec(http("request_7")
.get("""/cb3761658453/img/sprite.png""")
.headers(headers_4))
.pause(30)
.exec(http("request_8")
.post("""/login""")
.headers(headers_8)
.param("""username""", """user1""")
.param("""password""", """password1"""))
.pause(3)
.exec(http("request_9")
.get("""/cb2545837452/img/big_window_content.png""")
.headers(headers_4))
.pause(21)
.exec(http("request_10")
.get("""/private/bank/account/ACC1/operations.html"""))
.pause(3)
.exec(http("request_11")
.get("""/private/bank/account/ACC1/year/2012/month/4/page/0/operations.json""")
.headers(headers_11))
.pause(43)
.exec(http("request_12")
.get("""/private/bank/account/ACC1/year/2012/month/3/operations.html"""))
.pause(663 milliseconds)
.exec(http("request_13")
.get("""/private/bank/account/ACC1/year/2012/month/3/page/0/operations.json""")
.headers(headers_11))
.pause(4)
.exec(http("request_14")
.get("""/logout"""))

一个simluation 里面可以定义一个或者多个 scenario, 每个 scenario 是一个 scala 对象,

1
val scn = scenario("Name of my Scenario")...

scenario 有两个方法:

  • exec - describe an action, usually a request sent to the tested application
  • pause - simulate the thinking time of the user between pages

每个 scenario 里会发送很多个 request, 也会思考好多次。所以你看到上面的代码都是 exec http request 和 pause。

HTTP requests 的定义包括 名字,post 或者 get 方法,headers, 还有参数 :

1
2
3
4
5
http("request_3")
.post("/login")
.headers(headers_3)
.param("username", "user1")
.param("password", "password1")

这段代码会发送一个 POST 请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST http://excilysbank.gatling.cloudbees.net/login
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: fr,en-us;q=0.7,en;q=0.3
Host: excilysbank.gatling.cloudbees.net
Keep-Alive: 115
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.17) Gecko/20110422 Ubuntu/9.10 Firefox/3.6.17
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
Referer: http://excilysbank.gatling.cloudbees.net/public/login.html
username=user1&password=password1

The simulation definition

1
setUp(scn.inject(atOnce(1 user))).protocols(httpProtocol)

运行下吧!

首先我们要把这个 Simulation move 到 user-files/simulations/下。

然后运行$GATHING_HOME/bin/gatling.sh, Gatling 会找到 MySimulation。

选择 MySimulation,一路回车,然后等待运行结束,生成 report:

分析报表吧!

Gatling 生成的报表非常漂亮而且有意义。每个图表的具体含义可以参考 Reports
这样子的报表几乎可以直接呈给领导,都不需要自己采集数据绘图了。

下一节

到目前为止,我们都是照搬 Gatling wiki 来做的,其实就了解了下基本情况。下一节,应该搞个 project 试验下。

文章目录
  1. 1. 介绍
  2. 2. 上手
    1. 2.1. 测试计划
    2. 2.2. Gatling Recorder
    3. 2.3. 开始录制吧
    4. 2.4. 代码分析
    5. 2.5. 运行下吧!
    6. 2.6. 分析报表吧!
  3. 3. 下一节
|