負荷テストツールGatling Tips(実践的なScalaスクリプト)

Gatlingでシナリオを作成するときに、よくつかうテクニックをまとめてご紹介します。

基本的な実行方法やセットアップ方法は、こちらの記事「Gatlingで負荷テストを行う手順のまとめ(セットアップ編)」を参考にしてください。

よくつかうプロトコル設定

Gatlingでは、protocolsとして、シナリオに対する基本的な振る舞いを定義できます。

定義できる内容は、テスト対象のドメインやプロキシー、認証やUserAgentなど多岐にわたりますが、よく使うと思われるものを以下に解説します。

まずは、サンプルのソースです。

val pcAgent="Mozilla/5.0(WindowsNT6.1;WOW64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/30.0.1599.101Safari/537.36"
 
var httpProtocol = http
    .baseURL("https://www.targeturl.com")
    .proxy(Proxy("proxy.server.name", 8080).httpsPort(8080))
    .basicAuth("basicAuthUser", "basicAuthPasswd")
    .inferHtmlResources(BlackList("https://www.targeturl.com/favicon.ico", "https://www.targeturl.com/null", ".*dummy.png*"),WhiteList(".*targeturl.com.*"))
    .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
    .acceptLanguageHeader("ja,en-US;q=0.8,en;q=0.6")
    .acceptEncodingHeader("gzip,deflate,sdch")
    .userAgentHeader(pcAgent)
    .header("Cache-Control", "max-age=0")
    .connectionHeader("keep-alive")
    .extraInfoExtractor(extraInfo => List(extraInfo.request, extraInfo.response,extraInfo.session))

baseURL(“https://www.targeturl.com”)・・・テスト対象のURLのベースとなるURL(ドメイン)を設定できます。これにより、以降のURL指定が相対URLで定義できますので、検証/本番環境の切替などが容易になると思います。

proxy(Proxy(“proxy.server.name”, 8080).httpsPort(8080))・・・プロキシー・サーバ経由でテスト対象のサーバにアクセスする場合に指定します。

basicAuth(“basicAuthUser”, “basicAuthPasswd”)・・・BASIC認証がかかっている場合に指定します。公開前のサーバなどは、アクセス制限の一環としてかかっていたりしますよね。

inferHtmlResources(BlackList(),WhiteList())・・・inferHtmlResourcesで、画像やjavascriptなどのリソースファイルを取得できます。通常の負荷テストでは、getやpostした結果のresponseのみを取得することが多いですが、描画されるHTMLには様々なリソースファイルを含みます。これらをgetすることは、サーバのパフォーマンスに少なからず影響を与えますので、より精密なテストを行う場合には取得しておいた方が良いと思います。

extraInfoExtractor(extraInfo => List(extraInfo.request, extraInfo.response,extraInfo.session))・・・テスト結果のHTMLファイルが出力されるディレクトリに「simulation.log」というログを出力することができますが、そこに、リクエスト内容、レスポンス内容、セッションの内容を出力する設定です。エラーが出てテストが進まない時など、やり取りの内容をデバッグするのに重宝します。

デバッグの仕方

デバッグ方法はいくつかありますが、代表的なやり方をご紹介します。

その① extraInfoExtractor

最初は、上記でも紹介したextraInfoExtractorを使います。「simulation.log」に内容が出力されます。

その② デバッグレベルを変更する

src\test\resourcesフォルダに「logback-test.xml」というファイルがあり、ここでデバッグログレベルを設定できます。

rootlevel=”WARN”

rootlevel=”DEBUG”

に変更すると、詳細なログがコンソールに表示されるようになります。

その③ print文で出力する

上記2つの方法は、出力される情報が多いため、ピンポイントでのデバッグしづらいと時があります。

そんなときは、print文も使えます。

下記は、セッションの中から、「Token_value」というキーで格納されている値を表示します。

.exec(http("aaaaa page").get("https://www.target.com/aaaaa"))
  .pause(10)
  .exec {
  session =>
    val val_Token_value = session("Token_value").as[String]
    println("val_Token_value=" + val_Token_value)
    session
}

Gatlingの「セッション」は、シナリオの中で1人のユーザが個別に持っている情報の格納領域だと考えると分かりやすいと思います。

この領域に、適宜、キーとバリューという形式でデータを格納することができます。

複数のログイン・ユーザのIDとパスワードをcsvファイルに定義し、パラレルにログインするシナリオを実行する

たとえば「LoginUser.csv」というcsvファイルに以下のようなデータを用意しておき、順番に読み込んで、ログインするシナリオを実行できます。

UserId,UserPassword
Use001,Pass001
Use002,Pass002
Use003,Pass003
Use004,Pass004
Use005,Pass005

1行目はヘッダで、下記のスクリプトで、.feed(login_feeder)という部分で順番に読み込み、${UserId}、${UserPassword}でフォームデータとしてPOSTすることができます。

※なお、このUserIdとUserPasswordは、このキーで各ユーザのセッション(上述)に格納されていますので、いつでも取り出すことが出来ます。

val login_feeder = csv("LoginUser.csv").circular
 
val scn = scenario("Top Page Access").exec(http("Top Page Access Request").get("/"))
.pause(3)
.feed(login_feeder)
.exec( http("Login Servlet(/servlet/login)").post("/servlet/login")
.formParam("loginId", "${UserId}")
.formParam("password", "${UserPassword}")
).pause(5)

このシナリオを実行する部分の記述例は以下のようになります。

これは「login_feeder.records.length」で、csvに記述したレコード数分のユーザが120秒で順番に投入されるという記述です。

setUp(
    scn.inject(rampUsers(login_feeder.records.length) over(120 seconds))
).protocols(httpProtocol)

シナリオをバッチから起動する方法

IDEから実行するのは、この設定で簡単ですが、定期的に実行するとなると、batやshファイルから実行したいですよね。

タスクスケジューラに登録しておけば、毎日、夜中に実行するなんてこともできます。

batに記述するコマンドは、IDEから実行したときにコンソールに表示されるものをコピーして利用すると簡単です。

batの中で、実行ディレクトリを当該プロジェクトのルートディレクトリにする(cdで移動する)のを忘れないようにしましょう。

複数のシナリオを同時に実行する

setupの中に複数のシナリオを記述すれば、それぞれのシナリオをいっぺんに並行して実行することができます。

並列処理に長けたScalaならではの利点ですね。

setUp(
 
    scn_nologin.inject(
        rampUsers(500) over(200 seconds)
    ).protocols(com.httpProtocol)
    ,
 
    scn_api_call.inject(
        rampUsers(feeder.records.length) over(100 seconds)
    ).protocols(com.httpProtocolApi)
 
)

はじめは何もしないで、しばらくしてから開始する

何もしない時間は、nothingForを使えます。

下記は、開始後30行は何もしないで、その後、ユーザを追加していくシナリオです。

setUp(
    scn_do_regist.inject(
        nothingFor(30 seconds),
        rampUsers(feeder.records.length) over(600 seconds)
    )
).protocols(com.httpProtocol)

jsonをPOSTする

val sentHeaders = Map("Content-Type" -> "application/json;charset=utf8")
 
val scn = scenario("* Scenario - Api Call To Save Json")
    .feed(api_feeder)
    //Feederの各値は、sessionに入っているので、わざわざセットしなくても良い。
    .exec(
      http("Post json").post(/api/json/save)
      .headers(sentHeaders)
      .body(ElFileBody("some.json")).asJSON
  ).pause(10)
{
    "id" : "1",
    "name" : "名称",
    "type" : "タイプ",
    "customer_id" :  "${CustomerId}",
    "update_date" : "2018-01-15 12:00:00"
}