Help us understand the problem. What is going on with this article?

ガトリング砲をぶっ放した話

More than 3 years have passed since last update.

はじめに

Gatlingとは?

  • GatlingはScalaをDSLとする負荷テストツールです。
  • ab(apache-bench)より高機能で, Jmeterよりシナリオを書きやすい(らしい)のが特徴です
    • 実はJmeter, 使ったことがないです..
  • Gatlingという名前は田代砲になぞらえたのでしょうか

Gatlingのいいところ

  • シナリオがXMLではなく, Scalaで書ける
    • 普通のプログラムと同じ感覚で書くことが出来るので, シナリオの可読性を高くすることが出来る
    • また, プログラムなのでどうにでも工夫することができる
    • Scala入門としても最適(?)
  • 出力結果がキレイ
    • Xではなく, HTMLで出力される
    • X環境(X.Org)を整備しておく必要がない, Javaの実行環境のみ整っていればいい
  • 名前がカッコイイ

実行環境の準備

  • 実行に際してbuild.sbtを準備し実行する方法もありますが, bundleされているものを使用する方法が最も簡単・便利なので以下の方法をおすすめいたします。
$ sudo yum -y install java-1.8.0-openjdk wget unzip # CentOSの場合
$ wget https://repo1.maven.org/maven2/io/gatling/highcharts/gatling-charts-highcharts-bundle/2.2.3/gatling-charts-highcharts-bundle-2.2.3-bundle.zip -O gatling-2.2.3.zip
$ unzip gatling-2.2.3-bundle.zip

シナリオの書き方について

  • Google Chrome Developer Toolsの"Copy as cURL"を用いるとシナリオ作成に大変便利です。
  • 以下の例を参考にしていただけるとよいかと思います。
TestSimulation.scala
package test //複数シナリオを書いておく場合, パッケージでまとめておくとわかりやすくていいです

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

class TestSimulation extends Simulation {

  // シングルトンオブジェクトを生成する
  object uri {
    val fqdn = "example.com"
    val base = "https://" + fqdn
    val api(req:String): String = {
      "/api" + req
    }
  }

  // UserAgent
  object userAgent {
    val iPhone = "Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/14A403 Safari/602.1"
    val android = "Mozilla/5.0 (Linux; U; Android 2.2.1; en-us; Nexus One Build/FRG83) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"
  }

  // リソースごとに異なるヘッダー
  object headers {

    val top_referer = uri.base + "/index.html"

    // APIのリクエストヘッダ
    // フォーム送信時の形式はx-www-form-urlencodedを指定
    // curl でいうところの--dataにあたる
    val api = Map(
      "Accept"           -> "application/json,text/javascript,*/*;q=0.01",
      "Origin"           -> uri.base,
      "X-Requested-With" -> "XMLHttpRequest", //XHRからのリクエストであることを明示
      "Content-Type"     -> "application/x-www-form-urlencoded; charset=UTF-8",
      "Referer"          -> top_referer
    )

    // Javascriptのリクエストヘッダ
    val js  = Map(
      "Content-Type" -> "application/javascript; charset=UTF-8",
      "Referer"      -> top_referer
    )

    // HTMLのリクエストヘッダ
    val html = Map(
      "Content-Type"-> "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
    )
  }

  // 全リソース共通の設定
  val httpConf = http.baseURL(uri.base)
     .acceptLanguageHeader("en-US,en;q=0.8")
     .acceptEncodingHeader("gzip,deflate,sdch,br")
     .userAgentHeader(userAgent.iPhone)
     .connectionHeader("keep-alive")
     .header("Cookie", "nickname=Nate-River56; place=Shibuya, Tokyo")
     .header("Host", uri.fqdn)
     .header("Pragma", "no-cache")
     .header("Cache-Control", "no-cache")
     .maxRedirects(5) //リダイレクトの最大回数

  object scn {

    val stayRange = (0 milliseconds, 100 milliseconds)

    val top = scenario("TopPage#1") //わかりやすい名前を付ける
     .exec(
       http("index.html")
         .get(uri.static("/index.html")) //リソースを指定
         .headers(headers.html) //HTML用のヘッダーを付加
         .check(status.is(200)) //HTTP 200であればOK, そうでなければNGでカウントされエラーとして表示される
         .check(regex("<center><h1>Hello</h1></center>").exists)  //指定した要素が含まれているか
     )
     //リソースの間の待ち時間を"Uniform random"の形式で指定
     //繰り返しシナリオを実行した際にリソースへのアクセスをバラけさせるため
     .pause(stayRange._1, stayRange._2)
     .exec(
       html("app.js")
       .get(uri.static("/scripts/app.js"))
       .queryParam("tm", "20161212") //クエリストリングを付加, ?tm=20161212と同じ
       .disableFollowRedirect //リダイレクトを無効に
       .headers(headers.js)
       .check(status.is(200))
     )
     .pause(stayRange._1, stayRange._2)
     .exec(
       http("status.json")
       .post(uri.api("status.json"))
       .headers(headers.api)
       .formParam("key1", "value1") //パラメータを指定
       .formParam("key2", "value2")
       .check(status.is(200))
     )
  }

  setUp(
    scn.top.inject(
      nothingFor(5 seconds), //間違えて実行したときのため, 5秒間何もしない
      atOnceUsers(3), //3回だけワッとシナリオを実行する
      rampUsersPerSec(1) to (30) during (120 seconds), //120秒かけてユーザー数を30まで増加, パラメータは適宜調整する
      constantUserPerSec(30) during (180seconds)  //180秒の間, ユーザー数30を維持する
    )
  ).protocols(httpConf)
}

実行方法

  • user-files/simulation以下にDSLを配置し, 以下のように実行します。
    • ビルドが重く, 特に初回非常に重いので気長にお待ち下さい..
$ cd ~/gatling-charts-highcharts-bundle-2.2.3
$ ./bin/gatling.sh
GATLING_HOME is set to ~/gatling-charts-highcharts-bundle-2.2.3
14:48:23.772 [WARN ] i.g.c.ZincCompiler$ - Pruning sources from previous analysis, due to incompatible CompileSetup.
Choose a simulation number:
     [0] computerdatabase.BasicSimulation
     [1] computerdatabase.advanced.AdvancedSimulationStep01
     [2] computerdatabase.advanced.AdvancedSimulationStep02
     [3] computerdatabase.advanced.AdvancedSimulationStep03
     [4] computerdatabase.advanced.AdvancedSimulationStep04
     [5] computerdatabase.advanced.AdvancedSimulationStep05 //[0]-[5]はサンプルのシナリオ
     [6] test.TestSimulation

Select simulation id (default is 'basicsimulation'). Accepted characters are a-z, A-Z, 0-9, - and _
6

Select run description (optional)
これはテストです

Simulation computerdatabase.BasicSimulation started...

================================================================================
2016-12-12 14:50:13                                           5s elapsed
---- Requests ------------------------------------------------------------------
> Global                                                   (OK=2      KO=0     )
> request_1                                                (OK=1      KO=0     )
> request_1 Redirect 1                                     (OK=1      KO=0     )

---- Scenario Name -------------------------------------------------------------
[--------------------------------------------------------------------------]  0%
          waiting: 0      / active: 1      / done:0
================================================================================

--- 省略 ---

================================================================================
2016-12-12 14:50:36                                          28s elapsed
---- Requests ------------------------------------------------------------------
> Global                                                   (OK=13     KO=0     )
> request_1                                                (OK=1      KO=0     )
> request_1 Redirect 1                                     (OK=1      KO=0     )
> request_2                                                (OK=1      KO=0     )
> request_3                                                (OK=1      KO=0     )
> request_4                                                (OK=1      KO=0     )
> request_4 Redirect 1                                     (OK=1      KO=0     )
[> request_5                                               (OK=1      KO=0     )
> request_6                                                (OK=1      KO=0     )
> request_7                                                (OK=1      KO=0     )
> request_8                                                (OK=1      KO=0     )
> request_9                                                (OK=1      KO=0     )
> request_10                                               (OK=1      KO=0     )
> request_10 Redirect 1                                    (OK=1      KO=0     )

---- Scenario Name -------------------------------------------------------------
[##########################################################################]100%
          waiting: 0      / active: 0      / done:1
================================================================================![スクリーンショット 2016-12-12 15.12.22(2).png](https://qiita-image-store.s3.amazonaws.com/0/61315/686adf04-442c-099b-52ca-cf8fe4715811.png)


Simulation computerdatabase.BasicSimulation completed in 26 seconds
Parsing log file(s)...
Parsing log file(s) done
Generating reports...

================================================================================
---- Global Information --------------------------------------------------------
> request count                                         13 (OK=13     KO=0     )
> min response time                                    257 (OK=257    KO=-     )
> max response time                                    752 (OK=752    KO=-     )
> mean response time                                   337 (OK=337    KO=-     )
> std deviation                                        150 (OK=150    KO=-     )
> response time 50th percentile                        263 (OK=263    KO=-     )
> response time 75th percentile                        265 (OK=265    KO=-     )
> response time 95th percentile                        612 (OK=612    KO=-     )
> response time 99th percentile                        724 (OK=724    KO=-     )
> mean requests/sec                                  0.481 (OK=0.481  KO=-     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                            13 (100%)
> 800 ms < t < 1200 ms                                   0 (  0%)
> t > 1200 ms                                            0 (  0%)
> failed                                                 0 (  0%)
================================================================================

Reports generated in 0s.
Please open the following file: ~/gatling-charts-highcharts-bundle-2.2.3/results/0-1481521808046/index.html
  • 実行結果のフォルダ(0-1481521808046/)をローカルに移動しブラウザで確認すると負荷テストの結果をみることが出来ます

負荷テストの結果

  • .check()に成功していれば, OKにカウントされそうでなければNGにカウントされます。
  • また各リソースのResponse Time Number, レスポンス時間のパーセンタイルをみることが出来ます Gatling_global.png

  • 全体の1秒あたりのレスポンス数の推移を確認することができ, 想定通り捌けているかを確認することができます Gatling_rps.png

  • 個別のリソースに関しても, Response Time Number, レスポンス時間の分布をみることができます Gatling_each.png

注意

  • 負荷テストを自らが管理していないサーバーに対して行い過大な負荷をかけることはDoS攻撃にあたり, 電子計算機損壊等業務妨害罪等に問われることになります。
  • Gatlingに限ったことではありませんが, 負荷テストツールをDoS攻撃に利用することは絶対にしないでください。

参考リンク

公式Document

http://gatling.io/docs/2.2.3/

UserAgentについて

http://qiita.com/nightyknite/items/b2590a69f2e0135756dc

負荷のかけ方について

http://qiita.com/noexpect/items/2f52ae16ec20a5ba14b4

Gatlingの使用例

http://shanon-tech.blogspot.jp/2015/10/scala-gatling.html

まとめ

  • JMeterのようにGatlingでmaster-slave構成の負荷テスト環境をつくることは難しいです。
  • 比較的大きいサービスに対する負荷テストでなければscalaでシナリオを書くのも楽しいですし, ぜひ一度Gatling砲をぶっ放してみてはいかがでしょうか?

最後に

  • さて, 次回は一年上の先輩であるcy-kenta-takahashiさんの"UIPageViewControllerを使って漫画ビューアの見開き表示に対応した話"です。非常に楽しみです!
    • 業務ではあまり関わることがありませんが, 社内イベント等で関わることの多い先輩です!
    • 今度スプラトゥーンでぜひ対戦したいです
ntrv
cybirdinc
モバイルでスマイル!を企業理念に様々なモバイルサービスを提供する、株式会社サイバードのエンジニアチーム
https://www.cybird.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away