まだJMeterで消耗してるの?などと煽る気は無いのですが、Webアプリケーションの性能試験などで負荷をかけるときはJMeterを使うのが実績も多くOSSのなので費用もかからないということで主流なのでしょうけれど、最近はGatlingというのが流行りつつある?ようなのでちょっと試してみました。今回はGradleプロジェクトとして作成してみます。
前提バージョン
- Scala:2.12.6
- Java:1.8.0_171
- Gradle:4.8.1
- Gatling Plugin for Gradle:0.7.3
Gradle Pluginは公式のものではなさそうですし、バージョンも0系ですがテストツールですのでね。気にせず使って良いと思いますし何ならコントリビュート・・・できたら良いねくらいの感じで。
環境構築
Javaのインストールは割愛します。GatlingはScala製のツールなのでScalaをインストールしておきます。公式サイトはこちら。

私の場合はWindows用のインストーラをダウンロードして使用しました。また、併せてIntelliJのプラグインを検索してScala
とSBT
を入れておきました。
試験用アプリケーションの作成
今回は簡単なREST-APIを用意して試験用のアプリケーションにします。Spring Initializerを使用してKotlinのプロジェクトを作成し、WebとSecutiry(Basic認証を有効にするため)の依存関係を追加しておきます。
出来上がりはこちらに。
わかりやすくということでホントにちょっとしたものですが。
Controllerの作成
こんな感じで簡単なGETメソッドを空けておきます。
@RestController class GatlingSampleController { private val logger = LoggerFactory.getLogger(GatlingSampleController::class.java) @GetMapping("{pathVariable}") fun get(@PathVariable pathVariable: Int, @RequestParam requestParam: Int): GatlingSampleData { logger.info("pathVariable is ${pathVariable} and requestParam is ${requestParam}.") // ゼロ除算の場合エラー pathVariable % requestParam // スリープさせる val sleepTime = pathVariable * 100 logger.info("Sleep ${sleepTime} ms.") Thread.sleep(sleepTime.toLong()) return GatlingSampleData(pathVariable, requestParam) } }
PathVariable
とRequestParam
でInt
を受け取ります。軽くレスポンスのバリエーションを出してみるということで、RequestParam
がゼロのときはゼロ除算でエラーに、それ意外の場合はPathVariable
の値×100msだけSleepするようにします。
ログはなんとなくです。
Basic認証の設定
Spring InitializerでSecurityの依存関係を設定すると、spring-boot-starter-security
への依存関係が設定され、デフォルトでは全てのパスに対してBasic認証が有効になります。
何も設定しないとユーザ名はuser
、パスワードは起動時に生成されログに表示されますが、シナリオに設定したいのでapplication.propertiesにパスワードを設定しておきます。
spring.security.user.name=user spring.security.user.password=password
Gradleプロジェクトの作成
試験用アプリケーションの次はクライアント側のプロジェクトを作成します。基本的にはgradle-gatling-pluginのREADMEに従って設定します。
出来上がりはこちら。
コレもお試しということでシンプルなものですが。
build.gradleの設定
プラグインの有効化とGatring自体への依存関係を設定します。
plugins { id "com.github.lkishalmi.gatling" version "0.7.3" } repositories { mavenCentral() } apply plugin: "com.github.lkishalmi.gatling" apply plugin: 'scala' group 'com.example' version '1.0-SNAPSHOT' dependencies { compile 'io.gatling.highcharts:gatling-highcharts:2.3.1' }
プラグインの設定も色々可能ですが今回はデフォルト設定で使用します。詳細はREADMEに説明があります。
ディレクトリのカスタマイズやシナリオの絞り込み、VMオプションの設定など諸々可能です。
feederデータの作成
GatlingではCSVやTSVでシナリオに組み込むデータ(feederと呼ぶらしいです)を読み込むことができます。今回はPathVariable
とRequestParam
に設定するデータをCSVで作成してみます。
pathVariable,requestParam 10,0 9,1 8,2 7,3 6,4 5,5 4,6 3,7 2,8 1,9
1行目がシナリオから参照するときの項目名になっています。デフォルトでは、src/gatling/resources/data
またはsrc/gatling/data
に配置することで自動的に認識されます。配置場所のカスタマイズもプラグインの設定で可能です。
今回は10パターン用意してランダムに選ばせる方法を使います。一番上のrequestParam
がゼロのデータが選択されるとエラー、それ意外の場合はpathVariable
の値×100msだけSleepします。
Simulationの作成
デフォルトではsrc/gatling/scala
またはsrc/gatling/simulations
にScalaのコードとしてSimulation(シナリオ)を作成します。
package com.example.gatlingsample import io.gatling.core.Predef._ import io.gatling.http.Predef._ class GatlingSampleSimulation extends Simulation { val httpConf = http .baseURL("http://localhost:8080/") .basicAuth("user", "password") val params = csv("params.csv").random val scn = scenario("GatlingSampleSimulation") .feed(params) .exec(http("get").get("""${pathVariable}?requestParam=${requestParam}""")) setUp( scn.inject( rampUsers(1000) over (60) ).protocols(httpConf) ) }
いくつかポイントに分けて説明します。
import
io.gatling.core.Predef
およびio.gatling.http.Predef
配下をimportしておくと諸々コードが書きやすくなるようです。
import io.gatling.core.Predef._ import io.gatling.http.Predef._
Scalaのアンダースコアは色んな意味がある・・・らしいのですがここではワイルドカードでimportということですね。
Simulationの継承
Simulation
クラスは抽象基底クラスであるio.gatling.core.scenario.Simulation
を継承して作成します。setUp
メソッドをオーバーライドして動作させるのがお作法。
class GatlingSampleSimulation extends Simulation { // 省略 }
HTTP Protocolの設定
今回の例では、ベースのURLとベーシック認証の設定のみ行っています。
val httpConf = http .baseURL("http://localhost:8080/") .basicAuth("user", "password")
もちろん他にも設定が可能です。HTTPヘッダ関連や、ベーシック認証意外の認証の設定、キャッシュの設定やプロキシ関係などなど。詳細は公式のリファレンスを。

シナリオの設定
Gatlingでは、ScenarioBuilder
を定義しておき、setUp
メソッド内で頻度や期間等を指定して実行するようです。
val params = csv("params.csv").random val scn = scenario("GatlingSampleSimulation") .feed(params) .exec(http("get").get("""${pathVariable}?requestParam=${requestParam}"""))
前述のCSVファイルをランダムに読み込み、その値をGETリクエストのパスに埋め込んで実行する、というシナリオです。scenario
メソッドで指定しているシナリオ名や、http
メソッドで指定しているリクエスト名はレポート表示に使用されるので、適切な名称を付与しておきましょう。
当然ですが連続したリクエストの実行や、レスポンスから値を取得してリクエストに設定したり、途中で一定時間のWAITを入れたり、条件分岐を行うことも可能です。こちらも詳細は公式リファレンスを。

setUpメソッド
設定したHTTP Protocolを使用してシナリオをsetUp
メソッドにて実行します。
setUp( scn.inject( rampUsers(1000) over (60) ).protocols(httpConf)
ScenarioBuilder#inject
で実行頻度や実行期間を設定します。この指定の場合、1000ユーザ(1000回のシナリオ実行)を60秒間の間に線形ランプを使用して実行します、という指定です。
線形ランプ・・・大学は数学科を出たのですが数学全くわかりません・・・が、均等にリクエストを投入するってことです。
もちろん一気に投入するとか、少しつづ負荷を上げながら投入するというような事も可能です。こちらも詳細は公式リファレンスを。

実行してみる
というわけでいざテストを実行してみましょう。事前準備として、試験用のアプリケーションを起動しておきます。
Gradle Taskから実行
プラグインのタスクとしてgatlingRun
というのが定義されていますので実行します。
gradle gatlingRun
ガシガシ動いているログが流れた後、レポート出力のメッセージが表示されて完了です。
Reports generated in 0s. Please open the following file: C:\Dev\git\blog-materials\gatling\gatling-sample\build\reports\gatling\gatlingsamplesimulation-1534560679841\index.html BUILD SUCCESSFUL in 1m 11s 4 actionable tasks: 4 executed
デフォルトでは、build/reports/gatling/[シナリオ名]-[実行ID]
配下にレポートが出力されます。
レポートを見てみる
TOPはこんな感じになります。
設定したとおり、10%程度がエラー(KO)、70%が800ms以下、20%が800ms以上に分布しているのが見て取れます。
もちろんその他にも時間経過ごとのレスポンスの傾向や、シナリオだけでなくリクエストごとのレポートなども出力されます。こちらも詳細は公式リファレンスを。

まとめ
具体例としては非常に簡単なものですが、Gatlingによる負荷試験をGradleプロジェクトから行う方法を試してみました。
今回は解説しませんでしたが、レスポンスのアサーションなども割と細かく行えるようですし、シナリオもメソッドチェーンで直感的にかけるようになっているんじゃないかと思いました。
JenkinsなどのCIツールと組み合わせて、コンテナのオーケストレーションなどの環境セットアップ等々も含めて完全自動化、なんてこともそんなに難しくは無いんじゃないかと。ビルドの成果物としてHTMLのレポートが出るので、Jenkinsのコンソールからポチッとそのまま見れますしね。
Scalaを触ったことはなかったのですが、シナリオを組み立てる位であればリファレンス通りにやればそんなに難しくはなさそうです。JMeterの経験がそれほどあるわけでは無いのですが、かなりやりやすいのではないかと。
また、JMeterに比べで動作が軽いらしいです。軽いシナリオしか組んでおらず、かつ家のPCもそこそこスペックがいいので体感できているとは言い難いですが、まぁサクサク動いてましたよ(笑)
ということで、Scala製の負荷試験ツールGatlingを試してみました。お仕事で性能試験に絡む事があれば使ってみようかな。
この記事に対するコメント