【Java】DockerでGatling環境を作って CustomActionを実装する(2)

技術

今回こそCustomActionを実装します

前回はDockerコンテナにGatlingの環境をセットアップしました。
今回はその環境を使っていよいよCustomActionを実装します。

実装にあたってはこちらの記事を参考にしましたが、Scalaで実装されているので今回はこれをJavaで実装することを目指します。

記事を見る限り、ActionBuilderActionの2つのインターフェイスを実装すればよさそう。

あとはGatling自体の実装も参考にしました。
https://github.com/gatling/gatling

最終的なディレクトリ構成はこんな感じになります。

gatling-charts-highcharts-bundle-3.7.6
├─ bin
├─ conf
├─ lib
├─ results
├─ systemcraft # 前回作ったシナリオとリソースをホストと共有するディレクトリ
│   ├─ resources
│   └─ simulations
│        └─ computerdatabase
│             ├─ action
│             │   ├─ CustomAction.java
│             │   └─ CustomActionBuilder.java
│             └─ SampleSimulation.java
├─ target
├─ user-files
└─ LICENSE

早速 CustomAction.java から

前置き無しでコードから紹介します。

package computerdatabase.action;

import com.typesafe.scalalogging.Logger;

import io.gatling.commons.stats.Status;
import io.gatling.core.action.Action;
import io.gatling.core.stats.StatsEngine;

import java.util.function.Function;

import scala.Some;

public class CustomAction implements Action {

  private static final Status OK = Status.apply("OK");
  private static final Status KO = Status.apply("KO");

  private String _name;
  private Function<io.gatling.javaapi.core.Session, io.gatling.javaapi.core.Session> _func;
  private StatsEngine _statsEngine;
  private Action _next;
  private Logger _logger;

  public CustomAction(String name, Function<io.gatling.javaapi.core.Session, io.gatling.javaapi.core.Session> func, StatsEngine statsEngine, Action next) {
    this._name = name;
    this._func = func;
    this._statsEngine = statsEngine;
    this._next = next;
  }

  @Override public Logger logger() { return this._logger; }

  @Override
  public void com$typesafe$scalalogging$StrictLogging$_setter_$logger_$eq(Logger logger) {
    this._logger = logger;
  }

  @Override public String name() { return this._name; }

  @Override
  public void execute(io.gatling.core.session.Session session) {
    var newSession = new io.gatling.javaapi.core.Session(session);
    var start = System.currentTimeMillis();
    try {
        newSession = this._func.apply(newSession);
        var end = System.currentTimeMillis();
        this._statsEngine.logResponse(newSession.scenario(), newSession.asScala().groups(), this._name, start, end, OK, null, null);
    } catch (Exception ex) {
        var end = System.currentTimeMillis();
        this._statsEngine.logResponse(newSession.scenario(), newSession.asScala().groups(), this._name, start, end, KO, null, Some.apply(ex.getMessage()));
    }
    this._next.execute(newSession.asScala());
  }
}

Actionインターフェイスは「io.gatling.core.action」というパッケージに定義されています。
そして Actionインターフェイスは「com.typesafe.scalalogging.StrictLogging」というインターフェイスを継承しているので、そちらも併せて実装する必要があります。

StrictLoggingで定義されているのは以下の2つ。(上のコードでは31~36行目です)

public com.typesafe.scalalogging.Logger logger();

public void com$typesafe$scalalogging$StrictLogging$_setter_$logger_$eq(com.typesafe.scalalogging.Logger);

Actionで定義されているのも2つ(上のコードでは38~53行目です)

public String name();
public void execute(io.gatling.core.session.Session);

シナリオに組み込みたい処理をこのexecuteメソッドに実装すればいいわけです。
今回のサンプルではexecuteメソッドで実行したい処理を、CustomActionコンストラクタでFunctionとして受け取ることにしています。
この辺りは、こちらの記事の実装に倣いました。

2つのSessionクラス

注意したいのはFunctionの引数&戻り値も、executeメソッドの引数も、どちらもSessionクラスになっていますが、この2つのSessionクラスは別物だということです。

Functionの引数は「io.gatling.javaapi.core.Session」クラス(A)で
executeの引数は「io.gatling.core.session.Session」クラス(B)です。

BのSessionクラスはScalaで定義されているクラスです。
AのSessionクラスはBのSessionクラスをJava向けにラップしたクラスということだと思います。

結果レポート

executeメソッドの中で実行しているStatsEngine.logResponseで負荷試験結果をレポートに出力できます。

Functionのapplyが正常に終了すれば結果ステータスはOK。例外が発生すればKO(=NG)としています。

結果ステータスをKOとした場合は、第8引数のmessageにエラーメッセージを指定する必要があります。messageをnullにしていると例外になってしまいます。

次のAction呼び出し

executeメソッドの最後で次のActionのexecuteを呼び出しています。

これがないと、以降のシナリオ実行が停止してしまうので必ず実施しないといけません。

続いて CustomActionBuilder.java

package computerdatabase.action;

import io.gatling.core.structure.ScenarioContext;
import io.gatling.core.action.Action;
import io.gatling.javaapi.core.Session;

import java.util.function.Function;

public class CustomActionBuilder
  implements io.gatling.javaapi.core.ActionBuilder,
             io.gatling.core.action.builder.ActionBuilder {

  private String _name;
  private Function<Session, Session> _func;

  public CustomActionBuilder(String name, Function<Session, Session> func) {
    this._name = name;
    this._func = func;
  }

  @Override public io.gatling.core.action.builder.ActionBuilder asScala() { return this; }

  @Override public Action build(ScenarioContext context, Action next) {
    return new CustomAction(this._name, this._func, context.coreComponents().statsEngine(), next);
  }
}

CustomActionBuilderクラスは2つのインターフェイスを実装しています。

io.gatling.javaapi.core.ActionBuilder (A)と
io.gatling.core.action.builder.ActionBuilder (B)の 2つです。

(A)のActionBuilderは(B)のActionBuilderのJava向けラッパークラスですね。

CustomActionBuilderクラスの実装は特に難しいことはないと思います。
buildメソッドでCustomActionをnewして返すだけです。

buildメソッドの引数で次に実行するActionを受け取っているので、CustomActionに渡しておけばCustomActionの実行後に次のActionが実行されるわけです。

シナリオの書き方

 ・
 ・
 ・
  // A scenario is a chain of requests and pauses
  ScenarioBuilder scn =
    scenario("Scenario Name")
      .exec(new computerdatabase.action.CustomActionBuilder("custom_action", (session) -> {
        session = session.set("customKey", 99);
        System.out.println("execute custom action.");
        return session;
      }))
      .exec(http("request_1").get("/"))
      // Note that Gatling has recorded real time pauses
      .pause(7)
      .exec(http("request_2").get("/computers?f=#{customKey}"))
      .pause(2)
 ・
 ・
 ・

こんな感じでCustomActionBuilderをnewしてexecに渡しているだけです。

ラムダ式で実行したい処理をCustomActionBuilderに渡しています。
このラムダ式の中でsessionにキーを追加すれば、以降のシナリオで利用することもできます。

これでAWS SDK だろうが Azure SDKだろうがシナリオに組み込むことができますね。

前回と今回の2回にわたってGatlingのDocker環境作りから、CustomActionの実装までやってみました。

負荷試験ってそんなにしょっちゅうやらないから、すぐ忘れるんですよね。
何年か前はJMeterとかをよく触ってましたが、しばらくご無沙汰しています。

また数年後にGatlingを久しぶりに触ることになったら、この記事が役に立つかもしれません。

コメント

タイトルとURLをコピーしました