【JMeter】BeanShell Pre/PostProcessor での JSON 操作例【シナリオ作成】

こんにちは。
負荷試験や性能テストでよく使われる JMeter ですが、コンポーネントの 1つに、BeanShell PreProcessor / PostProcessor という機能が存在します。
こちらは JMeter からのリクエストのレスポンス内容をパースして値を取得したり、発行するリクエストのパラメータを動的に変化させたりと、割となんでもできてしまう便利なコンポーネントになっています。
※ 処理自体は Java でスクリプトを記述していく必要があり、少し癖があるかもですが、それほど難易度が高いわけでもないと思います。

今回は実際にリクエストを投げる API を用意して、シナリオを作成しつつ良く使うパターンを備忘録として残しておこうと思います。
用意する API は Zabbix 辺りが都合よさそうだったので、こちらに対しての負荷試験シナリオ、という前提で作成していきます。
※ Zabbix API の仕様に関しては こちら を参照ください。

JMeter (始めに)

使用する JMeter は現時点で最新版である JMeter 5.5 を使用します。
また、JSON の操作が必要になってくるため、JSONObject のライブラリファイルを JMeter の lib/ ディレクトリ配下に配置しておきます。
※ ファイルは こちら から ZIP 形式でダウンロードできます。

また、シナリオ作成前に JMeter の下記辺りのコンポーネントを用意しておくのが良いかもしれないです。

・View Results Tree : シナリオデバッグ用途
・HTTP Request Defaults : リクエスト共通の設定を定義
※ 全ての Zabbix API は /zabbix/api_jsonrpc.php に対して POST メソッドを用いて API を叩きます。
・HTTP Header Manager : リクエスト共通のリクエストヘッダを定義
※ 全ての Zabbix API へのリクエストには下記のリクエストヘッダが必要です。

Content-Type: application/json-rpc

Zabbix API へは JSON 形式で POST リクエストを発行するため、作成する HTTP Request コンポーネントの Body Data には ${request} という変数を設定しておきます。
※ PreProcessor でリクエストの JSON データを生成します。

作成するシナリオは

user.login → hostgroup.get → host.create → host.get → host.delete → user.logout

の順に偏移する想定です。

シナリオ

シナリオ全体で扱う値用に PreProcessor を用意しておきます。
※ Zabbix ログインユーザとログ出力モード切替の定義用です。
今回はデフォルトの Zabbix 特権ユーザを使用します。

/** 
 * 各変数の初期化
 * 定数値等を設定
 * 
 * initialize Script
 * Bean Shell PreProcessor
 */
// Execute mode
 vars.put("MODE", "debug");
//vars.put("MODE", "release");

/** 
 * ログイン ユーザ
 */
// login User
vars.put("username", "Admin");

// Login Password
vars.put("password", "zabbix");

1. user.login

始めに Zabbix の認証キーを取得します。

・PreProcessor
Zabbix API の仕様に沿った通りに JSON のリクエストを発行します。
ユーザ情報は先の PreProcessor にて設定した値を呼び出しています。

import org.json.JSONObject;
import org.json.JSONArray;

// param JSONObject 生成
JSONObject paramObject = new JSONObject();

paramObject.put("user", vars.get("username"));
paramObject.put("password", vars.get("password"));

JSONArray params = new JSONArray();
params.put(paramObject);

JSONObject param = params.getJSONObject(0);

// POST Body (JSON) 生成  
JSONObject objRequest = new JSONObject();

objRequest.put("method", "user.login");
objRequest.put("id", 1);
objRequest.put("params", param);
objRequest.put("jsonrpc", "2.0");

if (vars.get("MODE") == "debug") {
    log.info(objRequest.toString());
}

// データをセット
vars.put("request", objRequest.toString());

・PostProcessor
レスポンスから認証キーを取得します。
※ 認証キーは auth_key に格納しておき、以降のリクエストで利用します。

import org.json.JSONObject;

if (vars.get("MODE") == "debug") { log.info("-- Requested Sample(Pre): " + prev.getSampleLabel()); }

// レスポンスを取得
JSONObject json = new JSONObject(prev.getResponseDataAsString());

// レスポンスから 認証キー を取得
var result = json.getString("result");

if (vars.get("MODE") == "debug") { 
	log.info(result.toString());
}

// 変数にセット
vars.put("auth_key", result.toString());

2. hostgroup.get

ホストグループ Discovered hosts の詳細情報をリクエストします。

・PreProcessor
深い入れ子の JSON を生成する必要があるため、少しだけ工夫します。
また、先のリクエストにて取得した auth_key を呼び出して使用します。

import org.json.JSONObject;
import org.json.JSONArray;

// filter JSONObject 生成
JSONObject filterObject = new JSONObject();

filterObject.put("name", "Discovered hosts");

JSONArray filters = new JSONArray();
filters.put(filterObject);

JSONObject filter = filters.getJSONObject(0);

// param JSONObject 生成
JSONObject paramObject = new JSONObject();

paramObject.put("filter", filter);

JSONArray params = new JSONArray();
params.put(paramObject);

JSONObject param = params.getJSONObject(0);

// POST Body (JSON) 生成 
JSONObject objRequest = new JSONObject();

objRequest.put("auth", vars.get("auth_key"));
objRequest.put("method", "hostgroup.get");
objRequest.put("id", 1);
objRequest.put("params", param);
objRequest.put("jsonrpc", "2.0");

if (vars.get("MODE") == "debug") {
    log.info(objRequest.toString());
}

// データのセット
vars.put("request", objRequest.toString());

3. host.create

Zabbix のホストを作成します。

・PreProcessor
作成するホストの名称は複数スレッドでリクエストを流した際の重複を避けるため、Random クラスの nextInt() を用いて、ランダムな値を付与します。
IP アドレスとポートは重複しても問題ないため、固定値に設定しています。

import org.json.JSONObject;
import org.json.JSONArray;
import java.util.Random;

// 0 ~ 999999999 の数値を取得
Random rand = new Random();
long user_id = rand.nextInt(999999999);

// groups JSONObject 生成
JSONObject groupsObject = new JSONObject();

groupsObject.put("groupid", vars.get("groupid"));

JSONArray groups = new JSONArray();
groups.put(groupsObject);

JSONObject group = groups.getJSONObject(0);

// interfaces JSONObject 生成
JSONObject interfacesObject = new JSONObject();

interfacesObject.put("type", 1);
interfacesObject.put("main", 1);
interfacesObject.put("useip", 1);
interfacesObject.put("ip", "192.168.10.100");
interfacesObject.put("dns", "");
interfacesObject.put("port", "10050");

JSONArray interfaces = new JSONArray();
interfaces.put(interfacesObject);

JSONObject interfac = interfaces.getJSONObject(0);


// param JSONObject 生成
JSONObject paramObject = new JSONObject();

paramObject.put("host", "Test Server" + user_id.toString());
paramObject.put("groups", group);
paramObject.put("interfaces", interfac);

JSONArray params = new JSONArray();
params.put(paramObject);

JSONObject param = params.getJSONObject(0);

// POST Body (JSON) 生成 
JSONObject objRequest = new JSONObject();

objRequest.put("auth", vars.get("auth_key"));
objRequest.put("method", "host.create");
objRequest.put("id", 1);
objRequest.put("params", param);
objRequest.put("jsonrpc", "2.0");

if (vars.get("MODE") == "debug") {
    log.info(objRequest.toString());
}

// データのセット
vars.put("request", objRequest.toString());

4. host.get

Zabbix のホスト一覧を取得し、ランダムにホスト ID を取得します。

・PreProcessor

import org.json.JSONObject;
import org.json.JSONArray;

// param JSONObject 生成
JSONObject paramObject = new JSONObject();

paramObject.put("output", "extend");

JSONArray params = new JSONArray();
params.put(paramObject);

JSONObject param = params.getJSONObject(0);

// POST Body (JSON) 生成 
JSONObject objRequest = new JSONObject();

objRequest.put("auth", vars.get("auth_key"));
objRequest.put("method", "host.get");
objRequest.put("id", 1);
objRequest.put("params", param);
objRequest.put("jsonrpc", "2.0");

if (vars.get("MODE") == "debug") {
    log.info(objRequest.toString());
}

// データのセット
vars.put("request", objRequest.toString());

・PostProcessor
こちらも Random クラスのメソッドを用いて、レスポンスからランダムな hostid を取得し変数に格納しておきます。
※ 後述のホストの削除にて利用します。

import org.json.JSONObject;
import java.util.Random;

if (vars.get("MODE") == "debug") { log.info("-- Requested Sample(Pre): " + prev.getSampleLabel()); }

// レスポンス取得
JSONObject json = new JSONObject(prev.getResponseDataAsString());

var result = json.getJSONArray("result");

// JsonObject の要素数を数える
int length = result.length();

// ランダムな要素数を選定
Random rand = new Random();
int num = rand.nextInt(length);

// ランダムな要素を取得 
JSONObject jObj1 = result.getJSONObject(num);

var hostid = jObj1.getString("hostid");

if (vars.get("MODE") == "debug") { 
	log.info(hostid.toString());
}

// 変数セット
vars.put("hostid", hostid.toString());

5. host.delete

Zabbix のホストを 1つ削除します。

・PreProcessor
先ほど取得した hostid を利用してリクエストを発行します。

import org.json.JSONObject;
import org.json.JSONArray;

String[] hostidArr;
hostidArr = new String[1];
hostidArr[0] = vars.get("hostid");

// POST Body (JSON) 生成 
JSONObject objRequest = new JSONObject();

objRequest.put("auth", vars.get("auth_key"));
objRequest.put("method", "host.delete");
objRequest.put("id", 1);
objRequest.put("params", hostidArr);
objRequest.put("jsonrpc", "2.0");

if (vars.get("MODE") == "debug") {
    log.info(objRequest.toString());
}

// データのセット
vars.put("request", objRequest.toString());

6. user.logout

最後にログアウトして認証キーを無効化しておきます。

・PreProcessor

import org.json.JSONObject;
import org.json.JSONArray;

// POST Body (JSON) 生成 
JSONObject objRequest = new JSONObject();

objRequest.put("auth", vars.get("auth_key"));
objRequest.put("method", "user.logout");
objRequest.put("id", 4);
objRequest.put("params", null);
objRequest.put("jsonrpc", "2.0");

if (vars.get("MODE") == "debug") {
    log.info(objRequest.toString());
}

// データのセット
vars.put("request", objRequest.toString());

終わりに

恐らく BeanShell PreProcessor / PostProcessor を使えば、書き方次第で、おおよその要件は叶えられるんじゃないでしょうか?
※ 今回のシナリオファイルのサンプルは こちら に配置しております。

話は変わりますが、こちら に JMeter クラスタ環境デプロイ用の Ansible Playbook を配置しておりますのでご利用頂ければ嬉しいです。