ガトリング負荷テスト

記事はセルゲイ マスレ 、セルゲイ マスレンニコフ に代わって公開されてい ます







UPD。 「ガトリングの拡張機能の実装」セクションを追加







ガリング







gatling.ioフレームワークの負荷テストに関する一連の記事を続けます。 この記事では、Gatling DSLを使用するための基本的なテクニックについて説明します。GatlingDSLは、ほとんどの場合、負荷テストスクリプトの開発で使用されます。 だから、猫の下でお願いします。







ガトリング構造



前の記事で、 SBTのインストールとGatlingフレームワーク用の環境のセットアップについて書きました。 すべてのステップを完了すると、プロジェクト(1)が作成されます。その構造は以下の画像に示されています。







プロジェクト構造







plugins.sbt(2)ファイルで、ファイルが作成されていない場合は、sbtのガトリングプラグインを接続する必要があります。その後、手動で作成します。 プラグインの最新バージョンを指定する必要があることに注意してください。執筆時点では2.2.2です。 ファイルコードは次のとおりです。







//plugins.sbt addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.2")
      
      





次に、src / testディレクトリで、resources(3)ディレクトリを作成する必要があります。 設定ファイルとテストデータが含まれています。 gatling.confファイルには基本的な設定が含まれており、logback.xmlはロギングレベルとログ出力インターフェースを担当します。 これらのファイルは、 https://gatling.io/download/バンドルから取得できます。







scalaディレクトリにはテストパッケージが含まれています。 パッケージ名は好きなように呼び出すことができますが、原則として企業はその逆の名前ru.tcsbank.loadを使用します。







BasicSimulationファイルはメインテストファイルであり、スクリプトを実行するためのエントリポイントです。

ターゲット\ gatling(6)ディレクトリでは、起動ログ(コンソールに表示されるログ)にレポートが生成されます。 もっと頻繁に見る必要があります-非常に急速に成長します







メインプロジェクトファイルはbuild.sbt(7)です。 接続するすべてのライブラリへの依存関係が含まれます。 Gatlingフレームワークへのリンクが示されているのはその中にあり、そのコードは以下にあります。







 //build.sbt /*   Gatling.       plugins.sbt */ enablePlugins(GatlingPlugin) /*    */ name := "GatlingForArticle" /*   */ version := "0.1" /*   Scala */ scalaVersion := "2.12.4" /*   */ libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.3.0" % "test,it" libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.3.0" % "test,it" /* ,     JVM */ javaOptions in Gatling := overrideDefaultJavaOptions("-Xss10m", "-Xms2G", "-Xmx8G")
      
      





最後の重要なファイルはgatling.logです。 送信された要求と応答を確認できるのはその中にあります。 すべてのリクエストを表示するには、logback.xmlファイルの「ALL HTTP」行のコメントを外すことを忘れないでください。







ガトリングの主なアイデア



一見すると、スクリプトとDSLガトリングは複雑に見えるかもしれませんが、スクリプトを作成するという考えを理解すれば、すべてが非常に簡単になります。







Gatlingは、仮想ユーザーをscenario()



スクリプトとして提示します。 このシナリオでは、ユーザーの数は、 inject



メソッド、負荷モデル、およびhttpプロトコル設定とさまざまなエミュレーション条件によって示されます。 これらはすべてsetUp()



構造で指定されます。 たとえば、オンラインストアを読み込む場合、購入者と管理者にはいくつかのシナリオがあります。







  1. 購入者が購入する
  2. バイヤーはバスケットに入れるだけです
  3. バイヤーは商品を見るだけです


この分離により、特定のシナリオの仮想ユーザーの数を増やすことで、負荷を簡単に調整できます。







スクリプトは実行のチェーンであり、このチェーン内でリクエストがexec()



関数内に配置されます。







 - +-(1) +-(2) +-(3) +-(<>){ (4) }  { (5) }
      
      





実行チェーンはscenario(<Name>)



式で始まり、 exec()



関数を呼び出すことで組み立てられます。







 #1 val scn2 = scenario("ChainScenario") .exec(http().get()) .exec(http().get()) .exec(http().get()) .doIfOrElse(true) { exec(http().get()) } { exec(http().get()) }
      
      





仮想ユーザーJMeterとGatlingのエミュレーションを比較すると、いくつかの機能を強調できます。 JMeterでは、ユーザーはThreadGroupコイルに配置され、そこで番号が設定され、ループ内で仮想ユーザーのスクリプトを繰り返し再生するのは彼女(コイル)です。 すなわち 2人の仮想ユーザーを「レイズ」すると、テスト時間がなくなるまで同じスクリプトが実行されます。







ガトリングでは、仮想ユーザーの管理方法が少し異なります。 2人の仮想ユーザーを選択すると、スクリプトが実行され、作業が完了します。 ユーザーがスクリプトをループで実行するには、チェーンをループブロックに配置する必要があります。 https://gatling.io/docs/current/quickstart/#gatling-scenario-explainedで入手できる簡単なテストスクリプトを検討してください 。これは基礎として使用できます。







 //BasicSimulation.scala package ru.tcsbank.gatling /*    : io.gatling.core.Predef._ -   import io.gatling.http.Predef._ -  HTTP import scala.concurrent.duration._ -    ,     `4 minutes`, `15 seconds` */ import io.gatling.core.Predef._ import io.gatling.http.Predef._ import scala.concurrent.duration._ /*   .   ,   Simulation,     . */ class BasicSimulation extends Simulation { /*   HTTP */ val httpConf = http .baseURL("http://computer-database.gatling.io") .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") .doNotTrackHeader("1") .acceptLanguageHeader("en-US,en;q=0.5") .acceptEncodingHeader("gzip, deflate") .userAgentHeader("Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0") /*  .    ""  , ,  (pacing)       . */ val scn = scenario("BasicSimulation") .exec( http("request_1") .get("/") ) .pause(5) /*  .     ,   ,     . */ setUp( scn.inject(atOnceUsers(1)) ).protocols(httpConf) }
      
      





スクリプトは一見複雑に見えるかもしれませんが、見てみるとDSL Gatlingは非常にシンプルであり、ストレステストを記述するためにScalaの深い知識は必要ありません。







スクリプトスクリプトは、 scenario()



関数を定数に割り当てることから始まります。 スクリプト名は一意でなければなりません! 次に、 exec()



関数が呼び出され、テストスクリプトを実装する他の関数、httpおよびwebsocketを受け取ります。 すべてのアクションが実行され、リクエストをエミュレートします。







スクリプトが作成されると、 setUp()



関数で、スクリプトを実行するユーザーの数と、これらのユーザーがロードする方法を示します。 次に、これらすべての作業方法を詳細に分析します。







HTTP



デフォルトでは、フレームワークは次のメソッドGET、POST、PUT、PATCH、DELETE、OPTIONSをサポートしています。 例としてGETおよびPOSTリクエストを書くことを検討してください。 まず、スクリプト関数を定数scn



scn



exec()



簡単なGETリクエストを記述します。







 val scn = scenario("GetScenario") .exec( http("GETRequest") /* .get("/foo.php")      */ .get("http://bar.com/foo.php") )
      
      





ヘッダーをインストールする必要がある場合は、次を追加します。







 val scn = scenario("GetScenario") .exec( http("GETRequest") .get("http://bar.com/foo.php") /* .headers("foo","bar")   Map */ .headers( Map( "foo1" -> "bar1", "foo2" -> "bar2" ) ) )
      
      





リクエストにパラメーターを渡します。







 val scn = scenario("GetScenario") .exec( http("GETRequest") .get("http://bar.com/foo.php") .headers( Map( "foo1" -> "bar1", "foo2" -> "bar2" ) ) /* .queryParam("param","value")   Map */ .queryParamMap( Map( "param1" -> "value1", "param2" -> "value2" ) )
      
      





パラメータは.get("http://bar.com/foo.php?param=value")



関数.get("http://bar.com/foo.php?param=value")



介して直接渡すこともできます。 静的リソースの読み込みがある場合、並列読み込みにresources()



を使用します。







 val scn = scenario("GetScenario") .exec( http("GETRequest") .get("http://bar.com/foo.php") .headers( Map( "foo1" -> "bar1", "foo2" -> "bar2" ) ) .queryParamMap( Map( "param1" -> "value1", "param2" -> "value2" ) ) .resources( http("css").get("/main.css"), http("js").get("http://bar.com/main.js") )
      
      





POSTメソッドでは、パラメーターを渡すときにformParam()



関数が使用されます。







 val scn = scenario("PostScenario") .exec( http("GETRequest") .post("http://bar.com/foo.php") .headers( Map( "foo1" -> "bar1", "foo2" -> "bar2" ) ) /* .formParam("param","value")   Map */ .formParamMap( "param1" -> "value", "param2" -> "value2" )
      
      





リクエスト本文を介してデータを直接転送するには、 body()



使用する必要があります。







 val scn = scenario("PostScenario") .exec( http("GETRequest") .post("http://bar.com/foo.php") .headers( Map( "foo1" -> "bar1", "foo2" -> "bar2" ) ) .body( /*       .  stripMargin    ,      */ StringBody( """ |{ | "login": "password" |}""".stripMargin ) )
      
      





チェック



要求をエミュレートするときは、応答コードまたは応答本文内のテキストの存在を確認する必要があります。 多くの場合、応答からデータを抽出する必要もあります。 これはすべて、 check()



関数を使用して実行できます。 チェックは、httpメソッドの機能の後に行う必要があります。







 val scn = scenario("CheckScenario") .exec( http("ForChecks") .get("/check.php") /* ,     200 */ .check(status.is(200)) /*   ,    200  500 */ .check(status.in(200, 500)) /*      foo_bar */ .check(substring("foo_bar")) /*         */ .check(regex(""" \d{4} – \d{4}""")) /*          */ .check(regex("""key=(\d{4}-\w{4})""").saveAs("AuthKey")) /*  ,    ,    .    checkIf()()      Response  Session.   Response      "id="   ,     "id_some_value" */ .check( checkIf( (r: Response, s: Session) => r.body.string.contains("id=") )( regex("""id=(\d+)""").saveAs("id_some_value") ) ) )
      
      





セッション



セッションには、すべての仮想ユーザーデータと変数が格納されます。 スクリプトで何かを裏切る場合は、セッションを通じてこれを行う必要があります。







 val scn = scenario("SessionScenario") /*   -    password_param   . , - . */ .exec( session => session.set("password_param","anyPassword") ) .exec( http("param") .get("/anything.php") /*   "${}"    */ .queryParam("login","${password_param}") )
      
      





ScalaはCallByValueを使用するため、動的な値を直接DSL関数に転送することはできません。これらはコンパイル時に取得され、常に新しい値なしで使用されます。







 exec( http("timestamp") .get("/") /* ,          */ .queryParam("timestamp", System.currentTimeMillis() ) /*    Session,    */ .queryParam("timestamp", session => { session("var").as[String] } ) /* Just magic!    . X -   */ .queryParam("timestamp", x => { System.currentTimeMillis() } ) )
      
      





論理構造



他のテストツールと同様に、条件に応じてリクエストを処理するために論理構造がよく使用されます。







 val scn = scenario("doIfSimulation") /*    foo  bar */ .exec( session => session.set("foo","bar") ) /*  ,   foo   bar.   ,   . */ .doIf(session => session("cond").as[String].startsWith("bar")){ // exec( http("IFrequest") .get("/") ) }
      
      





試験データ



高品質のロードを実行し、キャッシュではなくシステムをテストするには、テスト中に動的に変化するリクエストでデータを送信する必要があります。 このようなデータを保存および取得するための最も簡単な方法は、ファイルから読み取ることです。 ファイルには、文字で区切られたデータが含まれている必要があります。 ガトリングには、このようなデータを読み取るための機能があります。







csv( "foo.csv")//コンマ区切りデータ

tsv( "foo.tsv")//タブ区切りデータ

ssv( "foo.ssv")//セミコロンで区切られたデータ

SeparatedValues( "foo.txt"、 '#')//別の文字で区切られたデータ







例として小さなcsvファイルを使用して、テストデータを操作する方法を示します。







 //csv  model, Macbook, MacBook Pro, ASUS Eee PC, Acer, Asus, Sony Vaio, Chromebook
      
      





ファイルを読み取るとき、Gatlingは最初の行をパラメーター名として使用し、その後、値を読み取るときにこれらの名前で保存します。 したがって、csvファイルに記述されているラップトップの名前を持つ値は、パラメーター${model}



置き換えられます。







csvファイルを読み取るには、 csv()



関数を呼び出す必要があります。







 /*         .queue //    .random //    .shuffle //   ,    .circular //         */ val feeder = csv("data.csv").circular val scn = scenario("doIfSimulation").feed(feeder) //#1 .repeat(5)( exec( http("request") .get("/") ) // .feed(feeder) #2 .exec(http("search").get("/computers?f=${model}")) )
      
      





そこで、フィーダー変数を作成し、 src\test\resources\data.csv



にあるファイルの名前を指定しsrc\test\resources\data.csv



。 スクリプトでは、 feed()



関数を呼び出して、 feeder



定数を指定します。 feed()



関数が呼び出されるたびに、新しい値が読み取られます。







オプション1では、 feed()



関数がrepeat()



前に呼び出されるため、5回の反復で最初に読み取られた値が変数${model}



で使用されます。







オプション#2を使用すると、各リクエストの前に値が読み取られます。







モデルを読み込む



ガトリングはさまざまな負荷モデルをサポートしています。 これらのモデルは、ユーザーの「上昇」と生成される強度に責任があります。







nothingFor(duration) -ロード開始前の一時停止時間の長さを示します







atOnceUsers(nbUsers)-nbUsersの量の仮想ユーザーは(可用性により)すぐに「上昇」します。







rampUsers(nbUsers)over(duration) - duration



時間中、nbUsersの量の仮想ユーザーが同じ時間間隔で「上昇」します。







constantUsersPerSec(rate)during(duration) -「上げる」仮想ユーザーの頻度(1秒あたりの仮想ユーザー)と時間間隔duration



ます。 期間duration



仮想ユーザーduration



数は毎秒増加します。







(期間)ランダム化中のconstantUsersPerSec(率) -上位の設計と同様に、仮想ユーザーの「発生」間の時間間隔のみがランダムになります。







rampUsersPerSec(rate1)to(rate2)during(duration) - duration



時間中、仮想ユーザーは頻度rate1



から頻度rate2



増加します。







rampUsersPerSec(rate1)to(rate2)during(duration)during randomized-上部のデザインと同様に、仮想ユーザーの「アップ」間の時間間隔のみがランダムになります。







splitUsers(nbUsers)を(injectionStep)separatedBy(duration)に -各時間間隔duration



後、仮想ユーザーは、その数がnbUsers



達するまで、 injectionStep



モデルに従って追加されます。 injectionStep



上記のモデルを指定できます。







splitUsers(nbUsers)into(injectionStep1)separatorBy(injectionStep2) -トップデザインと同様に、 injectionStep2



モデルが唯一のセパレーターです。







heavisideUsers(nbUsers)over(duration) -nbUsersの量の仮想ユーザーは、 duration



中にステップを登ります。







ロード開始



オプション1



ロードを開始する最も簡単な方法は、バンドルを使用することです。 スクリプトファイルをgatling-charts-highcharts-bundle-2.3.0\user-files\simulations\



gatling-charts-highcharts-bundle-2.3.0\bin\gatling.bat



gatling-charts-highcharts-bundle-2.3.0\user-files\simulations\



から、 gatling-charts-highcharts-bundle-2.3.0\bin\gatling.bat



ます。 コンソールは、実行するスクリプトの選択を提供します。







ガリングターミナル







スクリプトはオプション6の下にあります。選択後、コンソールに情報が表示されて負荷が生成されます。







オプション2



このオプションでは、IntelliJ IDEA Community IDEから直接ロードを開始します。







ライブラリを設定するためのすべての手順が完了したら、ALT + F12を押してターミナルを開きます。 ターミナルで、 sbt



コマンドを入力します。







SBTの打ち上げ







すべてのコンポーネントをロードした後、 gatling:testOnly



を使用してスクリプトを実行します。







ガリングコングルソンル







コンソールに現在の負荷状態が表示されます。

IDEAランチパッドから起動するには、SBTタスクに起動コマンドを追加する必要があります。







SBTタスクの作成







ガトリング拡張機能の実装



Gatlingのドキュメントには、すぐに使用できるプロトコルはHTTP / 1.1およびWebSocketのみに対応していると書かれています。 Gatlingの公式および非公式の拡張機能もあり、リンク( https://gatling.io/docs/2.3/extensions/ )で入手できます。







多くの場合、アプリケーションレベルのプロトコルがHTTPまたはWebSocksと異なるシステムを負荷下でテストする必要がある場合にタスクがあります。 この場合、Gatlingの拡張機能を作成し、必要な機能を実装できます。







したがって、この機会を実現する必要があります。







 val scn = scenario("BasicSimulation") .exec( new ExampleActionBuilder("MyAction") )
      
      





exec()



関数はActionBuilder



のタイプを取ることができるため、クラスを記述し、 ActionBuilder



のタイプでクラスを拡張する必要があります。







 class ExampleActionBuilder(myNameAction: String) extends ActionBuilder { override def build(ctx: ScenarioContext, next: Action): Action = { new ExampleChainableAction(myNameAction, next, ctx) } }
      
      





オーバーライドされたbuild



関数では、必要なコードを実装するクラスのインスタンスを作成する必要があります。 このクラスはChainableAction



から拡張する必要があります。







 class ExampleChainableAction(myNameAction: String, myNextAction: Action, ctx: ScenarioContext) extends ChainableAction { override def next: Action = myNextAction override def name: String = myNameAction override def execute(mySession: Session): Unit = { /*     */ } }
      
      





以下は、このアプローチの実際の例です。 この方法は最適なソリューションではありませんが、実装は可能な限り簡単であることに注意することが重要です。







拡張コード
 package ru.tcsbank.load /*    : io.gatling.core.Predef._ -   import io.gatling.http.Predef._ -  HTTP import scala.concurrent.duration._ -    ,     `4 minutes`, `15 seconds` */ import io.gatling.commons.stats.{KO, OK} import io.gatling.core.Predef._ import io.gatling.core.action.builder.ActionBuilder import io.gatling.core.action.{Action, ChainableAction} import io.gatling.core.stats.message.ResponseTimings import io.gatling.core.structure.ScenarioContext import io.gatling.http.Predef._ import scala.concurrent.duration._ /*   .   ,   Simulation,     . */ class ExampleProtocolScript extends Simulation { /*   HTTP */ val httpConf = http .baseURL("http://computer-database.gatling.io") .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") .doNotTrackHeader("1") .acceptLanguageHeader("en-US,en;q=0.5") .acceptEncodingHeader("gzip, deflate") .userAgentHeader("Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0") /*  .    ""  , , (pacing)   ,    . */ val scn = scenario("BasicSimulation") .exec( /*    Action */ new ExampleActionBuilder("MyAction") ) /*  .     ,   ,     . */ setUp( scn.inject(atOnceUsers(1)).protocols(httpConf) ) } /*    exec().  ExampleActionBuilder()    ,      ExampleChainableAction() */ class ExampleActionBuilder(myNameAction: String) extends ActionBuilder { override def build(ctx: ScenarioContext, next: Action): Action = { new ExampleChainableAction(myNameAction, next, ctx) } } /*    Action.  ExampleChainableAction()     . */ class ExampleChainableAction(myNameAction: String, myNextAction: Action, ctx: ScenarioContext) extends ChainableAction { /*    .      . */ override def next: Action = myNextAction /*  Action. ,        .    http(<>).get(...) */ override def name: String = myNameAction /*        Action. */ override def execute(mySession: Session): Unit = { /*     Action */ val startTime = System.currentTimeMillis() try { /*   Action */ System.out.println(myNameAction+" Hello world!") /*   Action */ /*     Action */ val stopTime = System.currentTimeMillis() /*       Action */ ctx.coreComponents.statsEngine.logResponse( session = mySession, requestName = name, timings = new ResponseTimings(startTime, stopTime), status = OK, None, None, Nil ) /*        .     . */ myNextAction ! mySession } catch { /*      ,   . */ case e: Exception => { /*     Action */ val stopTime = System.currentTimeMillis() /*        Action */ ctx.coreComponents.statsEngine.logResponse( session = mySession, requestName = name, timings = new ResponseTimings(startTime, stopTime), status = KO, None, Some(e.getMessage), //   ,    /target/.../simulation.log Nil ) /*         .     . */ myNextAction ! mySession } } } }
      
      





この記事では、ガトリングフレームワークのロードを実行するためのスクリプトを独自に開発するのに役立つ主なポイントについて説明しました。 , .








All Articles