JMeterの変数と関数について : 概要、便利な関数、ハマりどころ/便利どころ

負荷試験ツールとしてよく使われるJMeterですが、変数や関数を使っているとハマりどころが意外と少なくありません。自分のための整理も兼ねて、JMeterの変数と関数についてまとめてみました。

#### 実行環境
* Windows 7
* JRE 1.8.0_151
* JMeter 5.0 r1840935

## 1. JMeterにおける変数・関数の概要

JMeterには多くのエレメント（GUIモードで左ペインに表示されるアイテム1個が1エレメントです）が用意されていますが、その**エレメントの名前やパラメータなどを動的に設定するために変数や関数を利用することができます**。

### 書ける場所、書き方、展開のされ方

JMeterの**テスト計画内の各エレメントの名前やパラメータ**などに`${HOGE}`という文字列を設定すると、テスト実行中に**当該エレメントを実行したタイミングで変数`HOGE`の値に展開されます**。(変数への値の代入方法は後述)
また、JMeterではデフォルトでいくつかの関数が組み込まれており、変数と同様に**関数名と引数を`${__function(arg1,arg2)}`という風に設定することで、その文字列が関数実行結果に置き換えられ**ます。

#### ポイント

* もし`${HOGE}`を含むエレメントが評価されたタイミングで変数`HOGE`が未定義の場合、置換は行われず"`${HOGE}`"という文字列のままエレメントが実行されることになります。
  + 関数についても同様で、`${__function(arg1,arg2)}`という文字列が評価されたが`__function(arg1,arg2)`という関数が存在しなかった場合(orエラーになった場合)、"`${__function(arg1,arg2)}`"という文字列のままエレメントが実行されます。
* 関数には引数を渡すことができ、`,`で区切って複数の引数を渡すこともできます。引数は引用符で囲わずに指定するようになっており、途中に空文字を含む場合も囲う必要がありません。
  + ただし、文字列内にカンマを含む場合は`\,`とエスケープする必要があります。
  + また、引数の最初・最後の空白は無視されます。
  + つまり、以下の全ては全て第1引数が`Fixed, 1st String`、第2引数が`SecondString`になります。
      - `${__function(Fixed\, 1st String,SecondString)}`
      - `${__function(Fixed\, 1st String ,SecondString )}`
      - `${__function( Fixed\, 1st String, SecondString)}`
      - `${__function( Fixed\, 1st String , SecondString )}`
* 前処理/後処理などのスクリプティングが行われるエレメントに対しても先行して置換されます。詳しくは後述

### 変数への値の代入方法
変数への値の代入は、テスト計画内の各種エレメントで行うことができます。代表的なものは以下でしょうか。

* **シナリオ実行ごとにCSVを読み込んで利用する**
    * 「CSV Data Set Config」エレメントでCSVのファイル名や読み込み方、変数への割り当てを設定すると、エレメントの実行ごとにCSVを1行読み進め、指定した変数名にcsvの値を代入してくれます。
    * 変数名は「Variable Names」に指定します。なお、`${}`で囲う必要はありません。
    * 使用例 : 
        * データベースに存在する負荷試験対象ユーザのID・パスワードをCSVとして準備しておき、シナリオ実行1回につきCSVを一行読んでシナリオを実行することで大量ユーザによるログイン等の負荷を試験する
* **「後処理」カテゴリのエレメント内で抽出して利用する**
    * 「正規表現抽出」「JSON Extractor」など、HTTPサンプラーで取得した結果に対する後処理の抽出条件を設定して、抽出された文字列を指定した変数に代入することができます。
    * 代入先の変数名は、要素の「参照名」もしくは「Names of created variables」に指定します。CSVの時と同じく、`${}`で囲う必要はありません。
    * 使用例 :
        * ログイン系のリクエストに対するレスポンスから正規表現でアクセストークンを取得して変数に代入し、以降のサンプラーのパラメータとして使用する
        * レスポンスコードを変数に設定し、Ifコントローラの条件に利用して分岐させて後続シナリオを実装する[^1]
[^1]: 一般的な負荷試験においては、1つのユーザ行動フローを1つのスレッドグループ(=1つのシナリオ)に割り当てるのが真っ当な筋であり、Ifコントローラを使って行動フローを分岐させるというのはあまり推奨されるものではありません。ですが、例えば「同一URLへのリクエストのうち、一定割合のリクエストをエラーにするような流量制御を行う」などの処理がある場合、例に示したような使い方が活躍するかもしれません。もっとも、そこまでのものを作る場合はJMeterよりGatlingやLocustなどの負荷ツールを検討した方がいいのかもしれませんが……
* **「テスト計画」要素や「ユーザ定義変数」設定エレメント内で設定して利用する**
    * テスト計画の最上位エレメントである「テスト計画」エレメント内の「ユーザ定義変数」や、「設定エレメント」カテゴリの「ユーザ定義変数」エレメントで値を設定することができます。[追加]ボタンを押すとユーザ定義変数のテーブルに空行が追加されるので、[名前] [値]それぞれに変数名と値を入力します。
    * 使用例 : 
        * パラメータを動的に設定するというより、**「複数のエレメント内で共通で使い回されるが、毎試験ごとに柔軟に変更したい」というパラメータに使用する**場合に適しています。
* **関数の実行結果を代入する**
    * 関数によっては、引数として「関数の代入先となる変数名」をオプションに指定できるものがあります。使いたい関数が変数への代入を可能かどうかは後述の「関数ヘルパーダイアログ」で確認するとよいでしょう。
* **BeanShellなどのスクリプティングによる前処理で設定する**
    * JMeterのエレメントには、BeanShellやJavaScriptでスクリプトを記述できるエレメントが用意されています。長くなるのでここでは詳細に説明しませんが、各種スクリプト内で`vars.put("変数名","値")`という記述を行うことで変数に値を代入することができます。なお、スクリプト内では`vars.get("変数名")`により変数を取得することができます。

### 変数が意図通りに代入されているかどうかの確認 : Debugサンプラ―の利用

シナリオ作成時、意図した通りに変数や関数が設定されているかどうかを確認したい場合があります。最もシンプルな方法として、**Debugサンプラー**による確認がオススメです。

Debugサンプラーは、HTTPリクエストなどの外部リクエストを行わず、当該サンプラー実行時のJMeter変数/JMeterプロパティ/システムプロパティを応答データとして返してくれる便利なサンプラ―です。テスト計画の直下やスレッドグループ内に「結果をツリーで表示」リスナーを配置したうえで、変数の状態を確認したい場所にDebugサンプラ―を配置しておくと、**サンプラ―実行時点での変数やプロパティを`名前=値`の形式で全て出力**してくれます。
![図1.png](https://qiita-image-store.s3.amazonaws.com/0/275707/b67ebbf5-f2de-b33a-f07e-34ebd4f32016.png)

### 関数が意図通りに実行されているかどうかの確認 : `__log`関数の利用

後でも紹介するJMeterの組み込み関数`__log(arg)`を利用するのが便利です。`__log(arg)`関数は渡した引数の中身を評価してログ出力しつつ、関数自身の実行結果として引数の中身そのものを返してくれます。つまり、実行結果に影響を与えずに[^2]実行中のログを記録することが可能となります。以下に例を示します。
[^2]: 機能的には影響はありませんが、性能的な影響はないとは言えません。デバッグ時のみの利用にとどめ、目標とする負荷量でJMeterを実行する際にはログ出力を外しておくのが堅実です。
![図2.png](https://qiita-image-store.s3.amazonaws.com/0/275707/debc581a-cf28-7114-daec-1f03e83ff22a.png)

```jmeter.log
2018-12-04 00:28:23,332 INFO o.a.j.f.LogFunction: スレッドグループ(1スレッド・1ループ) 1-1  :  Debug Sampler@1 : ${ResponseCode}
2018-12-04 00:28:23,359 INFO o.a.j.f.LogFunction: スレッドグループ(1スレッド・1ループ) 1-1  :  Debug Sampler@2 : 200
2018-12-04 00:28:23,360 INFO o.a.j.f.LogFunction: スレッドグループ(1スレッド・1ループ) 1-1  :  Debug Sampler@3 : 392fe7a2-df8a-46b8-a966-be6f14c3091f
```

このように、実行されたスレッドグループとスレッドの番号、そして`__log`関数に渡された中身が評価されたものがJmeterのログに出力されます。
本当はDebugサンプラーでこういったことができると良いのですが、Debugサンプラーには引数を渡すことはできないため関数の実行結果を直接観測することはできません。そのため、BeanShell等のスクリプトを記述せずにJmeter関数で完結する範囲で関数の実行結果を確認する方法としては、この`__log`関数による確認が最もストレートな方法なのではないでしょうか。

## 2. 便利な関数とその使いどころ

JMeterには多数のビルトイン関数がありますが、その一覧は当然[公式ドキュメント](https://jmeter.apache.org/usermanual/functions.html)内でリファレンスとして提供されているほか、後述する**関数ヘルパーダイアログ**でその動作を確認することができます。本稿では、関数ヘルパーダイアログによる関数の説明の確認方法・動作確認方法を説明するとともに、特に多用する関数について簡単に説明します。

### 関数について困ったら : 関数ヘルパーダイアログ

JMeterの関数について困ったら、公式ドキュメント以上に**関数ヘルパーダイアログ**が役立ちます。[オプション]→[関数ヘルパーダイアログ]（ショートカットキーは `Ctrl+Shift+F1`）で立ち上がるこのダイアログは、

* 確認したい関数をリストボックスで一覧できる
* 関数を選択すると、引数1つ1つに対する説明に加え、動作確認用に値を入力できる入力欄が出現する
* 各引数に値を入力して[生成]ボタンを押すと、
    * その引数で実行する際の文字列(=エレメント内の設定したい箇所にコピペして使える)が生成され、
    * その引数を与えた場合の関数の実行結果と、実行後のJMeter変数の一覧(Debugサンプラ―で見えるものと同様)が確認できる

という機能を備えています。関数で困ったらまずはコレを試すことをオススメします。

### 便利な関数1 : `__time`
たぶん最も利用されることが多いのではないでしょうか。名前から推測できるとおり、**関数を実行したタイミングでの時刻を出力してくれる**関数であり、

* 引数なしで実行→マイクロ秒でのUNIX TIMEで現在時刻を出力
* 引数1つで実行→引数で指定したフォーマットで現在時刻を出力
    * フォーマットはJavaのSimpleDateFormatに則る。公式ドキュメントの例を引用すると、2018年1月21日1時23分45秒に実行した場合以下のように出力される
        * `${__time(dd/MM/yyyy,)}` →  **21/01/2018**
        * `${__time(YMD,)}` → **20180121**
        * `${__time(YMDHMS,)}` → **20180121-012345**

というものです。

#### 便利な使い道1 : .jtlファイルのファイル名にタイプスタンプを付与し、過去実施時のログへの追記を回避する
JMeterのリクエスト結果を逐次記録していくリスナーである「結果をツリーで表示」「統計レポート」などのリスナーエレメントは、**出力先ファイルに指定したファイル名と同名のファイルが存在する状態で実行すると(GUIモードの場合を除き)自動的に既存ファイルに追記してしまいます。**
過去に実行したJMeterでの負荷ショットへの追記を防ぐため、出力ファイル名に`__time`関数でタイムスタンプを付与することで、負荷ショット実行ごとに自動でファイルを分けてログを出力させることができます。(例 : `logtree_${__time(yyyy-MMdd-HHmm)}.jtl`)

#### 便利な使い道2 : getパラメータへのタイムスタンプ付与によるキャッシュ避けを再現する
「CDNやHTTPサーバなどでキャッシュさせないために、URL内に(リクエスト内容に影響のない範囲で)異なるgetパラメータを付与する」ということはしばしば行われるのではないでしょうか。[^3]同様のことを、

* HTTPサンプラーがgetリクエストであれば、getパラメータに`_ : ${__time}`を設定
* HTTPサンプラーがpostリクエストであれば、のパス末尾に`?_=${__time}`のようにタイプスタンプを付与する

といった方法で再現することが可能になります。
[^3]: 例えば、jQueryでajax通信を行う際、`jQuery.ajax()`に`{cache:true}`のパラメータを与えると、ajaxリクエストURL末尾に`&_=1516540541`のように文字列が付加されることによりキャッシュ避けが行われます。

### 便利な関数2 : `__P` (=`__property`)
「ショットごとに向き先のホストを変更したい」「ショットごとに特定シナリオのスレッド数・ループ回数を変更したい」などの場合、実行の度にjmxファイルを修正するのは面倒です。そんなときに使えるのがこの`__P`(`__property`の省略形です)関数で、JMeterの起動パラメータとして渡した値を参照することができます。

詳細はこちらのエントリなどが参考になったので、こちらに譲ります。

* JMeterのシナリオにコマンドライン引数でパラメータを渡す - There's an echo in my head
    * https://ikm.hatenablog.jp/entry/2018/02/21/192828
* JMeter でコマンドラインでプロパティを設定して JMeter 内で使用する - TenForward
    * https://tenforward.hatenablog.com/entry/20080909/1220944470

### 便利な関数3 : `__log`
関数の実行結果のロギングに便利と述べた`__log`関数ですが、関数の実行結果のモニタリングだけでなく、
「あるエレメントのある設定項目が、JMeterの変数・関数として評価されるか？」ということの確認にも使えます。
たとえば

* 「HTTPサンプラー」などのサンプラ―エレメントの名前に`__log`を仕込む
    * →ログに記録されるので、ここが関数・変数の展開可能箇所だとわかる
* 「正規表現抽出」エレメントの名前に`__log`を仕込む
    * →ログに記録されないので、ここが関数・変数として展開されない箇所だとわかる

といったことができます。`__counter`関数など、関数を叩いたタイミングや叩かれた箇所・回数などについて確認したいときに使えそうです。

## 3. ハマりどころ/便利どころ

### ハマりどころ : スクリプティング系のエレメント内における処理順序

「後処理」カテゴリなどで利用されるBeanShell・JavaScript等によるスクリプトにおいては、

* **まず最初に`${}`に対する展開が行われ、**
* **その結果として得られたスクリプトが実行される**

という順序で処理が行われます。また、その際は`${}`が`""`や`''`で囲われていても展開されます。このことに注意してスクリプトを実装しないと、型エラー等に惑わされて苦しむことになります。
もっとも、スクリプト内で変数を参照・設定する場合は`vars.get/vars.put`を使えるので、そちらを使う方がトラブルが少なく済むのではないかとも思われます。

### 便利どころ : 特定の変数をリスナーのログに出力する方法

負荷試験において、「使えるユーザを何分の負荷で何ユーザ消費したか？」という確認を行いたい場合や、エラーが発生したユーザを特定したいなどの場合があります。そんな時は、**出力対象としたい変数名をjmeter.propertiesやJmeterの実行時のオプションに指定することで、「統計レポート」等のリスナーで出力するサンプラ―毎のログにその変数を追加することができます**。具体的に、`var1`、`var2`の2つの変数を追加したい場合は

* JMeterの起動オプション(jmeter.bat)に`-Jsample_variables=var1,var2`を追加する
* jmeter.propertiesに`sample_variables=var1,var2`と記述する

のいずれかの方法でこれを行うことができます。


### ハマりどころ : 変数のスコープについて
変数のスコープについても説明しようと思いましたが、それだけで結構な分量になってしまうのでそのうち別記事で投稿します。(WIP)