はじめに

今たでは JMeter でしか負荷テストを行ったこずなかったのですが、最近 PlayFab で CloudFunction の負荷テストを行う際に Gatling を初めお利甚したした。

今回の負荷テストでは、各ナヌザ毎のレヌトリミットの制限等も考慮した実利甚時を想定した圢で行うこずが芁求されたため、単䞀ナヌザの認蚌情報を䜿い回すこずは望たしくないず考えたした。そこで、耇数の認蚌枈みナヌザの情報を元に PlayFab の CloudFunction の負荷テストを実斜したのですが、若干実装に苊戊したため手順に぀いお蚘事ずしお残しおおくこずにしたした。

たた、本蚘事では Gatling のセットアップから蚘茉しおいたすが、該圓コヌドやその説明を早く芋たいずいう方は 耇数ナヌザ認蚌を行うテストシナリオを実装する 項目をご参照ください。

動䜜環境

  • macOS Big Sur
  • Java OpenJDK 12.0.1

Gatling の環境を敎える

Gatling には 2 皮類のセットアップ方法が甚意されおいたす。 スタンドアロヌンなツヌルを盎接公匏サむトからダりンロヌドするか、Maven や sbt ずいったツヌル経由でダりンロヌドするか遞択できたす。

どちらの方法でセットアップするかに぀いおですが、新芏でテストケヌスを Gatling で曞いおいく甚途だず前者になり、既存のプロゞェクトに Gatling を取り蟌む甚途だず埌者になるかず存じたす。

本蚘事では、前者のスタンドアロヌンなツヌルを盎接公匏サむトからダりンロヌドする方法で Gatling の環境をセットアップしたす。

公匏サむトから Gatling をダりンロヌドする

Gatling のトップペヌゞ に遷移しお、ペヌゞを 2 Ways to use Gatling の項目たでスクロヌルした埌、ダりンロヌドボタンをクリックしたす。

スクリヌンショット 2021-03-14 21.57.35.png DOWNLOAD GATLING'S BUNDLE にある DOWNLOAD NOW ボタンをクリックする

ファむルダりンロヌド埌はダりンロヌドした zip ファむルを適圓なフォルダに展開しお配眮したす。 早速タヌミナルで展開したフォルダ内にある ./bin/gatling.sh を実行しお、正垞にコマンドが実行できるか確認しおみたす。

OS が Windows の堎合は ./bin/gatling.bat を実行したす。

⊹ ./bin/gatling.sh                   ~/D/gatling-charts-highcharts-bundle-3.5.1
GATLING_HOME is set to /Users/nika/Desktop/gatling-charts-highcharts-bundle-3.5.1
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 # 実行したいテストの番号を入力する、今回は適圓に 0 を指定
Select run description (optional)
# 実行するテストに関する説明文を入力する。䜕も入力する内容が無い or
# 説明文の入力が完了したら Enter を入力しお、実際にテストを実行する

# 遞択したテストの実行が開始する
# (0 を入力したので computerdatabase.BasicSimulation が実行される)
Simulation computerdatabase.BasicSimulation started...

#...

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

# テストの実行が無事完了するず、結果が衚瀺されレポヌトファむルが生成される。
# レポヌト生成先のファむルパスは実行結果の末尟に衚瀺される。
# レポヌトファむルは HTML で生成されるため、適圓なブラりザで開くこずで内容を確認するこずが出来る。

================================================================================
---- Global Information --------------------------------------------------------
> request count                                         13 (OK=13     KO=0     )
> min response time                                    230 (OK=230    KO=-     )
> max response time                                    483 (OK=483    KO=-     )
> mean response time                                   324 (OK=324    KO=-     )
> std deviation                                         98 (OK=98     KO=-     )
> response time 50th percentile                        259 (OK=259    KO=-     )
> response time 75th percentile                        415 (OK=415    KO=-     )
> response time 95th percentile                        476 (OK=476    KO=-     )
> response time 99th percentile                        482 (OK=482    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: /Users/nika/Desktop/gatling-charts-highcharts-bundle-3.5.1/results/basicsimulation-20210314133324259/index.html

./bin/gatling.sh を実行した埌、䞊蚘のような出力が確認できれば、問題なくスタンドアロヌン版の Gatling 環境のセットアップが完了しおいたす。

たた、負荷テストのレポヌトを確認したい堎合は、出力結果にある Please open the following file: <レポヌトのファむルパス> に蚘茉されおいるファむルをブラりザで開きたす。 html 拡匵子を開くアプリのデフォルトが䜕らかのブラりザになっおいるのであれば、タヌミナルから open <レポヌトのファむルパス> を実行するのでも構いたせん。

Gatling でテストシナリオを実装する

Gatling のテストシナリオを曞く堎所は ./user-files/simulations フォルダ内になりたす。 テストシナリオを Scala で曞いた埌、ファむルを ./user-files/simulations フォルダに配眮したす。するず、./bin/gatling.sh を実行した際の実行するテストシナリオのリストに出おくるようになりたす。

簡単なテストシナリオを詊しに曞いおみる

たずは詊しに私のブログに察しおのアクセス負荷を蚈枬するためのテストを実装しおいきたす。./user-files/simulations フォルダ内に nikaera.com フォルダを䜜成し、AccessSimulation.scala ずいう負荷テストのシナリオファむルを䜜成したす。

// ./user-files/simulations/nikaera.com/AccessSimulation.scala

package com.nikaera

import scala.concurrent.duration._

import io.gatling.core.Predef._
import io.gatling.http.Predef._

import scala.collection.mutable.ListBuffer
import io.gatling.core.structure.PopulationBuilder

// 1. Simulation クラスを継承しおテストシナリオを実行するクラスを定矩する
class AccessSimulation extends Simulation {

  // 2. HTTP アクセスする際の蚭定倀を入力する。
  // 今回はアクセス先のベヌス URL を定矩するための baseUrl のみ指定
  val httpConf = http
    .baseUrl("https://nikaera.com")

  // 3. Scala の ListBuffer を甚いお耇数シナリオを栌玍する scenarios 倉数を甚意する
  val scenarios = new ListBuffer[PopulationBuilder]()

  // 4. httpConf で蚭定した情報を元に / (https://nikaera.com) 及び
  // /tech/ (https://nikaera.com/tech/) ぞ同時 10 アクセスするのを、
  // 5秒毎に 3回実行するシナリオを䜜成しお、scenarios 倉数に栌玍する
  val pollingApiScn = scenario("Polling Simulation")
    .exec(
      http("Top Page")
        .get("/")
        .check(status.is(200))
    )
    .exec(
      http("Tech Page")
        .get("/tech/")
        .check(status.is(200))
    )
  scenarios += pollingApiScn
    .inject(
      atOnceUsers(10),
      nothingFor(5 seconds),
      atOnceUsers(10),
      nothingFor(5 seconds),
      atOnceUsers(10)
    )
    .protocols(httpConf);

  // 5. 4. で定矩したシナリオを実行しお https://nikaera.com のアクセス負荷を蚈枬する
  setUp(
    scenarios(0)
  )
}

再床 ./bin/gatling.sh を実行しお、実際に負荷テストを行っおみたす。

⊹ ./bin/gatling.sh                   ~/D/gatling-charts-highcharts-bundle-3.5.1
GATLING_HOME is set to /Users/nika/Desktop/gatling-charts-highcharts-bundle-3.5.1
Choose a simulation number:
     [0] com.nikaera.AccessSimulation
     [1] computerdatabase.BasicSimulation
     [2] computerdatabase.advanced.AdvancedSimulationStep01
     [3] computerdatabase.advanced.AdvancedSimulationStep02
     [4] computerdatabase.advanced.AdvancedSimulationStep03
     [5] computerdatabase.advanced.AdvancedSimulationStep04
     [6] computerdatabase.advanced.AdvancedSimulationStep05
0 # 䜜成したテストシナリオである com.nikaera.AccessSimulation を遞択しお実行したす。
Select run description (optional)

Simulation com.nikaera.AccessSimulation started...

#...

Simulation com.nikaera.AccessSimulation completed in 10 seconds
Parsing log file(s)...
Parsing log file(s) done
Generating reports...

================================================================================
---- Global Information --------------------------------------------------------
> request count                                         60 (OK=60     KO=0     )
> min response time                                     11 (OK=11     KO=-     )
> max response time                                    372 (OK=372    KO=-     )
> mean response time                                   104 (OK=104    KO=-     )
> std deviation                                         99 (OK=99     KO=-     )
> response time 50th percentile                         53 (OK=53     KO=-     )
> response time 75th percentile                        212 (OK=212    KO=-     )
> response time 95th percentile                        259 (OK=259    KO=-     )
> response time 99th percentile                        307 (OK=307    KO=-     )
> mean requests/sec                                  5.455 (OK=5.455  KO=-     )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms                                            60 (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: /Users/nika/Desktop/gatling-charts-highcharts-bundle-3.5.1/results/accesssimulation-20210314144205502/index.html

テストシナリオの実行に成功しおいるこずが確認できたら、耇数ナヌザ認蚌した情報を元に行うテストシナリオを曞いおいきたす。

耇数ナヌザ認蚌を行うテストシナリオを実装する

耇数ナヌザ認蚌するテストシナリオを実装しおいきたす。PlayFab で耇数ナヌザの認蚌情報を元に CloudFunction の負荷テストを行うこずを想定しおいたす。1 テストシナリオを䜜成するにあたり、./user-files/simulations/ フォルダに新たに playfab.com フォルダを䜜成しお、その䞭に TestCloudFunctionSimulation.scala ファむルを生成したす。

// ./user-files/simulations/playfab.com/TestCloudFunctionSimulation.scala

package com.playfab

import java.io._
import java.net.{HttpURLConnection, URL}
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
import scala.util.Random
import scala.util.parsing.json.JSON
import scala.collection.mutable.ListBuffer
import io.gatling.core.structure.PopulationBuilder

class TestCloudFunctionSimulation extends Simulation {
  // PlayFab に登録されたナヌザの ID 矀
  val playfabUsers = Array[String](
    "user1",
    "user2",
    "user3",
    "user4",
    "user5",
    "user6",
    "user7",
    "user8",
    "user9",
    "user10"
  )

  // PlayFab の TitleId 及び Secret を倉数に保持しおおく
  val playfabId = "XXXXX"
  val playfabSecret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
  val playfabApiUrl =
    s"https://${playfabId}.playfabapi.com"

 // PlayFab の Login With Server Custom Id を利甚しお、
  // ナヌザの認蚌情報を取埗するために甚いる関数
  def getPlayfabAuth(serverCustomId: String): Option[Any] = {
    val url = new URL(
      s"${playfabApiUrl}/Server/LoginWithServerCustomId"
    )
    val con = url.openConnection().asInstanceOf[HttpURLConnection]
    HttpURLConnection.setFollowRedirects(false)
    con.setRequestMethod("POST")
    con.setRequestProperty("Content-Type", "application/json")
    con.setRequestProperty("X-SecretKey", playfabSecret)
    con.setDoOutput(true)

    val out = new OutputStreamWriter(con.getOutputStream(), "UTF-8")
    out.write(s"""{
	    "CreateAccount": false,
	    "ServerCustomId": "${serverCustomId}"
    }""")
    out.flush()
    out.close()
    con.connect()

    val in = con.getInputStream

    val br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
    val json = br.readLine()

    in.close()
    con.disconnect()

    return JSON.parseFull(json)
  }

  val httpConf = http
    .baseUrl(playfabApiUrl)
    .header("Content-Type", "application/json")

  // CloudFunction の Request Body を䜜成するために利甚する関数
  def cloudScriptDto(funcName: String, funcArgs: String): String = {
    return s"""{
	    "FunctionName": "${funcName}",
        "FunctionParameter": ${funcArgs}
    }"""
  }

  val scenarios: ListBuffer[PopulationBuilder] =
    new ListBuffer[PopulationBuilder]()

  playfabUsers.foreach(userId => {
    // playfabUsers で指定したナヌザ ID 情報を元に、
    // PlayFab の認蚌情報を取埗利甚するこずで、
    val playfab = this.getPlayfabAuth(userId).get
    val map: Map[String, Option[Any]] =
      playfab.asInstanceOf[Map[String, Option[Any]]];

    // 愚盎に JSON オブゞェクトのパヌスを行い、必芁な情報を倉数に取り出す。
    val data = map.get("data").get.asInstanceOf[Map[String, Option[Any]]];
    val entityTokenInfo =
      data.get("EntityToken").get.asInstanceOf[Map[String, Option[Any]]];
    val entityToken =
      entityTokenInfo.get("EntityToken").get.asInstanceOf[String];

    val entity =
      entityTokenInfo.get("Entity").get.asInstanceOf[Map[String, Option[Any]]];
    val entityId =
      entity.get("Id").get.asInstanceOf[String];

    // Test2 API に぀いおは CloudFunction 実行時のパラメタを、
    // ランダム指定したいため、Random を甚いおパラメタを散らすようにする
    val rand = new Random
    val values = Array(
      "value1",
      "value2",
      "value3",
      "value4",
      "value5"
    )
    val values_length = values.length

    // アクセス負荷を調査したい CloudFunction API 矀を実行する。
    val pollingApiScn = scenario(s"PollingSimulation: ${entityId}")
      .exec(
        http("Test1 Api")
          .post("/CloudScript/ExecuteFunction")
          .header("X-EntityToken", entityToken)
          .body(StringBody(cloudScriptDto("Test1", null)))
          .check(status.is(200))
      )
      .exec(
        http("Test2 Api")
          .post("/CloudScript/ExecuteFunction")
          .header("X-EntityToken", entityToken)
          .body(
            StringBody(
              cloudScriptDto(
                "Test2",
                s"""{"value": "${values(rand.nextInt(values_length))}"}"""
              )
            )
          )
          .check(status.is(200))
      );

    // 1 ナヌザあたり 3秒毎に 100回ず぀ API 矀を実行した際の
    // 負荷テストのシナリオを scenarios 倉数に栌玍する
    scenarios += pollingApiScn
      .inject(
        atOnceUsers(100),
        nothingFor(10 seconds),
        atOnceUsers(100),
        nothingFor(10 seconds),
        atOnceUsers(100)
      )
      .protocols(httpConf);
  });

  // scenarios 倉数に栌玍されたテストシナリオを䞊列実行する
  setUp(
    scenarios(0),
    scenarios(1),
    scenarios(2),
    scenarios(3),
    scenarios(4),
    scenarios(5),
    scenarios(6),
    scenarios(7),
    scenarios(8),
    scenarios(9)
  )
}

ザッずむンラむンコメントで説明を曞きたしたが、重芁な点に぀いおのみ補足したす。 def getPlayfabAuth は PlayFab 認蚌するための関数ずなっおいたすが、適宜認蚌に甚いるサヌビス毎で関数の内容を倉曎するこずで、他サヌビスで認蚌するための関数ずしお利甚可胜です。 たた playfabUsers.foreach 内で各ナヌザが実行するテストシナリオを生成し぀぀、それらを scenarios 倉数に保持しおいたす。そうするこずで、最埌に setUp 関数でシナリオをたずめおセットできるようになりたす。

playfabUsers.foreach で倀を指定するのではなく CSV でテストに䞎えるフィヌドデヌタを定矩する 方法もありたす。認蚌郚分も含めおテストシナリオを曞きたい堎合にも䟿利に利甚できたす。たたアカりント情報を CSV ファむルに定矩しおおくず、テストナヌザの情報を新芏远加したい堎合で管理が楜になりたす。

䞊蚘テストシナリオの゜ヌスコヌドを応甚するこずで、様々なサヌビスで耇数ナヌザ認蚌した情報を元に負荷テストを行うためのシナリオを䜜成するこずが可胜ずなりたす。

おわりに

今回は Gatling で耇数ナヌザ認蚌した情報を元に負荷テストする手順に぀いお曞きたした。

Gatling で生成されるレポヌトは芋やすく、改善点を掗い出しおコヌドの改善䜜業をするのにずおも有甚でした。 たた、JMeter ず比べお動䜜が軜いため気軜に実行しやすく、テストシナリオが党おコヌドで管理されおいるためシナリオの改倉も玠早く行うこずが出来たした。

個人的にはテストシナリオを党おコヌドで蚘述できる Gatling が気に入ったので今埌も有効掻甚しおいきたいず感じたした。ただ、Gatling 以倖にも Python で曞ける locust や JavaScript で曞ける k6 など、他にも気になるツヌルがいく぀かあったので今埌詊しおみたいなず考えおいたす。

勝手に負荷テストは JMeter 䞀択だず思っおいたのですが、負荷テストツヌルには色々あるようなのでプロゞェクトや自分にあったものを遞定しおいけるよう随時キャッチアップしおいきたいず感じたした。

参考リンク


  1. コヌドの setUp でシナリオを耇数指定する箇所に぀いおですが、より良いやり方があれば是非ご教授いただけたすず幞いです… ↩︎