お元気そうで残念です

ぼちぼち学習してるIT系の学生です

gatlingでconstantUsersPerSecをだんだん増やすためにjinja2にお祈りに行ったときの道中のこぼれ話

周囲が負荷テストでgatlingを使い始めて、自分も使ってみようと思い勉強してみました。

以下のような感じで負荷を徐々に増やして徐々に減らすテストを組もうとしたら案外苦戦したのでメモっときます。 もっと良い方法があったら教えてください!

f:id:joniy_joniy:20180511002147p:plain

injection単体ではどうか

普通にinjectionでひょいっとできると思って調査しましたが、微妙にニュアンスが違っていてダメでした。 前半の徐々に増加する部分に絞ってなぜダメだったか触れていきます。

1. rampUsers(nbUsers) over(duration)

期間durationに渡ってnbUsers数のリクエストをランプ関数に従って発射するというもの。

rampUsers(100) over(10 seconds)

とすると、10秒間で均等にリクエストを発射して、合計100リクエストになるように発射してくれます。

実行すると以下のようになります。

均等にリクエストを割り振ってくれるので、だんだん増やすことはこれ単体ではできません。

あと、もしリクエストが詰まって一気に消化される場合などランプ関数で想定していたよりも早くnbUsers数のリクエストが裁かれてしまった場合、それ以上のリクエストは生成されません。ちょっとやりたいことと違う。 f:id:joniy_joniy:20180511003055p:plain

2. rampUsersPerSec(rate1) to (rate2) during(duration)

初期値rate1数のリクエストを、期間durationに渡って、ランプ関数に従ってrate2数のリクエストへ徐々に増やしていくというもの。

これでいいやん、って思ったのですが試してみると1の問題点と同様の問題がありました。

rampUsersPerSec(100) to 500 during(10 seconds)を実行した結果が以下になります。

00:45:59から100リクエストずつで始まり、00:46:09で500リクエストで終わることを期待していましたが、同時リクエスト数は500まで上がる前に終わってしまいました。上がりきる前に計算したリクエスト数が終わってしまったようです。ちょっとやりたいことと違う。 f:id:joniy_joniy:20180511005247p:plain

3. splitUsers(nbUsers) into(injectionStep) separatedBy(duration)

duration期間ずつ間を挟みながら、injectionStepを繰り返して合計のリクエスト数がnbUsersになるまで発射するというもの。

splitUsers(1000) into (rampUsers(10) over (10 seconds)) separatedBy (10 seconds)

このシナリオだと、10秒渡って10リクエストを投げた後、10秒休むを100回繰り返します。 やりたいこととはだいぶ違っちゃいますね。ちなみに、実行した結果は以下のようになりました。長かった。

f:id:joniy_joniy:20180511014839p:plain

4. splitUsers(nbUsers) into(injectionStep1) separatedBy(injectionStep2)

injectionStep2を挟みながら、injectionStep1を繰り返して合計のリクエスト数がnbUsersになるまで発射するというもの。

splitUsers(1000) into (rampUsers(10) over (10 seconds)) separatedBy atOnceUsers(30)

このシナリオだと、10秒渡って10リクエストを投げた後、ドバっと30リクエストを投げるを100回繰り返します。 これも違いますね。

5. constantUsersPerSec(rate) during(duration)

期間durationの間常にrate数のリクエストを発射し続けてくれるもの。 基本的にはこれがやりたいことを満たしていますが、徐々に増やすためには似たようなコードをペタペタする必要があるのでちょっと...。具体的には以下のような感じです。これでやりたいことは満たしていますが、間隔やリクエスト数を変えると毎回弄るのが面倒なので一工夫挟みたいところです。

var waveInjections = Seq(
      constantUsersPerSec(10) during (10 seconds),
      constantUsersPerSec(20) during (10 seconds),
      constantUsersPerSec(30) during (10 seconds),
      constantUsersPerSec(40) during (10 seconds),
      constantUsersPerSec(50) during (10 seconds),
  )

  setUp(
    scn.inject(waveInjections).protocols(httpConf))

constantUsersPerSec(rate) during(duration)を自動生成する

Scalaでやってみる

scalaでシナリオを書けるのでforなりwhileなりでSeqに追加していけばいいのではと試してみました。
Ansibleで実行したいホストに設置するので、変数はAnsibleからとってくるようにしています。

var MAX_ACCESS = "{{ max_access }}".toInt   
var START_ACCESS = "{{ start_access }}".toInt   
var EACH_ACCESS = "{{ each_access }}".toInt 
var SEC = "{{ duration }}".toInt

略
var nb = START_ACCESS
var waveInjections = Seq(constantUsersPerSec(START_ACCESS) during (SEC seconds))
while ( nb <= MAX_ACCESS ) {
  nb = nb + EACH_ACCESS
  waveInjections = waveInjections :+ constantUsersPerSec(nb) during (SEC seconds)
}

SetUp(scn.inject(waveInjections).protocols(httpConf))

実行してみるとvalue during is not a member of Seq[Product with Serializable]と怒られてしまいました。
この書き方ではダメなようですが、Scalaを書くのが初めてなのでどう直せば良いのか分からない...

Jinja2で生成してみる

ならば慣れ親しんている(?)jinja2テンプレートを使って生成したものを配置してみようとやってみました。

略
  var waveInjections = Seq(
    {% set second = [duration * each_access /max_access] %}
    nothingFor({{ second[0] }} seconds),
    {% set nb = [start_access] %}
    {% for _ in range(1, 100) %}
      {% if nb[0] == max_access %}
      {% break %}
      {% endif %}
      constantUsersPerSec({{ nb[0] }}) during ({{ second[0] }} seconds),
      {% set _ = nb.append(nb.pop() + each_access) %}
    {% endfor %}
      constantUsersPerSec({{ max_access }}) during ({{ wait }} seconds),
    {% set nb = [max_access - each_access] %}
    {% for _ in range(1, 100) %}
      {% if nb[0] < start_access %}
      {% break %}
      {% endif %}
      constantUsersPerSec({{ nb[0] }}) during ({{ second[0] }} seconds),
      {% set _ = nb.append(nb.pop() - each_access) %}
    {% endfor %}
    nothingFor({{ second[0] }} seconds),
  )

  setUp(scn.inject(waveInjections).protocols(httpConf))

生成されたものが以下になります。
やりたかったことはできましたが、ゴリ推した感が強いですね...。
Scala勉強して再チャレンジしたいと思います。

  var waveInjections = Seq(
        nothingFor(2.0 seconds),
        constantUsersPerSec(10) during (2.0 seconds),
        constantUsersPerSec(20) during (2.0 seconds),
        constantUsersPerSec(30) during (2.0 seconds),
        constantUsersPerSec(40) during (2.0 seconds),
        constantUsersPerSec(50) during (10 seconds),
        constantUsersPerSec(40) during (2.0 seconds),
        constantUsersPerSec(30) during (2.0 seconds),
        constantUsersPerSec(20) during (2.0 seconds),
        constantUsersPerSec(10) during (2.0 seconds),
        nothingFor(2.0 seconds),
  )

  setUp(scn.inject(waveInjections).protocols(httpConf))

追記

こう書けば普通に動くので、Scalaだけで完結できました。スッキリ

  var nb = START_ACCESS
  var waveInjections = Seq(constantUsersPerSec(nb).during(SEC seconds))
  while ( nb <= MAX_ACCESS ) {
    waveInjections = waveInjections :+ constantUsersPerSec(nb).during(SEC seconds)
    nb = nb + EACH_ACCESS
  }

参考文献

Gatling Simulation Setup
Gatlingを使った負荷試験
制御構文
AnsibleのJinja2を活用する