Overview
まずJmeterとは何ですか?については以下のとおりです。
JMeter はJakarta プロジェクト で開発が進められている、パフォーマンス計測用のJavaアプリケーションです。元々はWebアプリケーションのテストのために作成されたもので、さまざまなWebアプリケーションをテストする機能を持っています。
参考:1. JMeterの基本 | TECHSCORE(テックスコア)
軽い負荷をかけて、ちょっと動作を見てみたいなレベルであれば、Apache Bench で良いかと思います。参考:Apache Benchでサクッと性能テスト - Qiita
強い負荷をかけて、動作をちゃんと確認したいなレベルであれば、Apache JMeter が選択肢に挙がってきます。
参考までに、他の手段としては、単に複数マシンに ssh
で入って、一斉に負荷掛けツールを実行っていうのも選択肢に挙がるかなとは思います。
では、以下のような目次で説明しておこうと思います。
1. Jmeter Setting(ex.. how to make jmx)
まず、JMXの作成方法、そして、Jmeterの設定(Master&Slave側の設定 と Master側だけの設定 と Slave側だけの設定)について説明します。
1.1. How to make JMX
以下の図のようにJmeter上で設定し、Save Test Plan as
等で保存すると、JMXファイルができる。
1.1.1. Advanced Setting
実行時に動的にスレッド数やRamp-Up期間(秒)、ループ回数、HTTPのリクエスト先等を設定したくなるかと思います。
その場合は以下のような手順で出来ます。
- JMX作成時に、各項目欄に
${__P(thread_num)}
や $__P{ramp_up}
等のように値を設定しておきます。
- Jmeterの実行時に、
~/jmeter/bin/jmeter -n -t test.jmx -r -Jthread_num=10 -Jramp_up=60
などのようにすればOKです。
NOTE: CLIモードのJmeterのオプションについては、Apache JMeter - User’s Manual: Getting Started を参考にしてください。
2. Master-Slave Setting
2.1. Jmeter Master & Slave’s Settings
2.1.1. File Placement
Master側マシンとSlave側マシンで、
Jmeterで利用するファイルは、同じフォルダ構成 にして配置する必要があります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
[centos@ip-xx-xx-xx-xx ~]$ pwd
/home/centos
[centos@ip-xx-xx-xx-xx ~]$ tree -L 2
.
├── test.jmx
├── apache-jmeter-5.1.1.tgz
├── jmeter
│ ├── LICENSE
│ ├── NOTICE
│ ├── README.md
│ ├── bin
│ ├── docs
│ ├── extras
│ ├── lib
│ ├── licenses
│ └── printable_docs
├── jmeter.log
└── rmi_keystore.jks
|
2.1.2. Heap Memory Setting
以下のファイルの159行目あたりの : "${HEAP:="-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m"}"
を設定する。
以下のファイルの531行目あたりの #jmeter.save.saveservice.timestamp_format=yyyy/MM/dd HH:mm:ss.SSS
のコメントアウトを外す。
1
|
vi ~/jmeter/bin/jmeter.properties
|
2.1.4. Generate KeyStore
jmeter-serverとの通信がSSLであるため、
SSL通信で使用するキーおよび証明書(keystore)の作成が必要です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
$ LANG=C ~/jmeter/bin/create-rmi-keystore.sh
What is your first and last name?
[Unknown]: # rmi と入力
What is the name of your organizational unit?
[Unknown]: # My unit name と入力
What is the name of your organization?
[Unknown]: # My organisation name と入力
What is the name of your City or Locality?
[Unknown]: # Your City と入力
What is the name of your State or Province?
[Unknown]: # Your State と入力
What is the two-letter country code for this unit?
[Unknown]: # XY と入力
Is CN=rmi, OU=My unit name, O=My organisation name, L=Your City, ST=Your State, C=XY correct?
[no]: # yes と入力
Enter key password for <rmi>
(RETURN if same as keystore password): # 未入力のままEnter
Warning:
The JKS keystore uses a proprietary format. It is recommended to migrate to PKCS12 which is an industry standard format using "keytool -importkeystore -srckeystore rmi_keystore.jks -destkeystore rmi_keystore.jks -deststoretype pkcs12".
Copy the generated rmi_keystore.jks to jmeter/bin folder or reference it in property 'server.rmi.ssl.keystore.file'
|
2.2.1. Remote Host Settings
Master側のマシンに、Slaveのマシンはこれだよ!って教えるための設定です。
以下のファイルの259行目あたりを remote_hosts=aa.aa.aa.aa:1099,bb.bb.bb.bb:1099
等のように、Slaveで利用するマシンのURLを設定する。
1
|
vi ~/jmeter/bin/jmeter.properties
|
2.3. Jmeter Slave’s Settings
2.3.1. jmeter-server Automatic Start Setting
Slaveのマシンが再起動してしまったとしても、jmeter-serverが自動起動されるようにする設定です。
以下のファイルの末尾に、/home/centos/jmeter/bin/jmeter-server &
を追加する。
1
|
sudo vi /etc/rc.d/rc.local
|
3. N.B 1 SpringBoot Simple RestController
すごく単純ですが、、、JmeterのSlaveマシンからアクセスを受けるためのSpringBootのRestControllerは以下のようにしました。
host:port/test
で受けて、リクエストの累計回数をログに出力し、HTTP STATUS OKを返却するだけのコントローラーです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Slf4j
@RestController
public class SimpleRestController {
AtomicLong count = new AtomicLong(0);
@RequestMapping(value = "/test")
public ResponseEntity<String> method1() {
count.addAndGet(1);
log.info("request count = {}", count);
return ResponseEntity.status(HttpStatus.OK).body("ok");
}
}
|
4. Execution Result
4.1. Jmeter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
[centos@ip-xx-xx-xx-xx ~]$ ~/jmeter/bin/jmeter -n -t ./test.jmx -r
OpenJDK 64-Bit Server VM warning: If the number of processors is expected to increase from one, then you should configure the number of parallel GC threads appropriately using -XX:ParallelGCThreads=N
Creating summariser <summary>
Created the tree successfully using ./Test Plan.jmx
Configuring remote engine: ec2-aa-aa-aa-aa.compute-1.amazonaws.com:1099
Configuring remote engine: ec2-bb-bb-bb-bb.compute-1.amazonaws.com:1099
Configuring remote engine: ec2-cc-cc-cc-cc.compute-1.amazonaws.com:1099
Starting remote engines
Starting the test @ Sat Jun 13 14:10:47 UTC 2020 (1592057447006)
summary = 1 in 00:00:01 = 2.0/s Avg: 344 Min: 344 Max: 344 Err: 0 (0.00%)
Tidying up remote @ Sat Jun 13 14:10:49 UTC 2020 (1592057449322)
summary + 1 in 00:00:00 = 4.8/s Avg: 22 Min: 22 Max: 22 Err: 0 (0.00%) Active: 0 Started: 1 Finished: 2
summary = 2 in 00:00:01 = 2.8/s Avg: 183 Min: 22 Max: 344 Err: 0 (0.00%)
Tidying up remote @ Sat Jun 13 14:10:49 UTC 2020 (1592057449514)
Remote engines have been started
Waiting for possible Shutdown/StopTestNow/HeapDump/ThreadDump message on port 4445
summary + 1 in 00:00:00 = 4.3/s Avg: 13 Min: 13 Max: 13 Err: 0 (0.00%) Active: 0 Started: 2 Finished: 3
summary = 3 in 00:00:01 = 3.2/s Avg: 126 Min: 13 Max: 344 Err: 0 (0.00%)
Tidying up remote @ Sat Jun 13 14:10:49 UTC 2020 (1592057449728)
... end of run
... end of run
... end of run
[centos@ip-xx-xx-xx-xx ~]$ client_loop: send disconnect: Broken pipe
|
4.2. RestController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
20:49:30: Executing task 'SimpleRestcontrollerApplication.main()'...
> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :SimpleRestcontrollerApplication.main()
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.0.RELEASE)
2020-06-14 20:49:34.580 INFO 43952 --- [ main] c.e.s.SimpleRestcontrollerApplication : Starting SimpleRestcontrollerApplication on MacBook-Air.local with PID 43952 (/Users/takuto-n/Documents/Basic_study/private_repo/ctrl-key-6bit-mask/java/simple-restcontroller/build/classes/java/main started by takuto-n in /Users/takuto-n/Documents/Basic_study/private_repo/ctrl-key-6bit-mask/java/simple-restcontroller)
2020-06-14 20:49:34.584 INFO 43952 --- [ main] c.e.s.SimpleRestcontrollerApplication : No active profile set, falling back to default profiles: default
2020-06-14 20:49:36.489 INFO 43952 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-06-14 20:49:36.518 INFO 43952 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-06-14 20:49:36.518 INFO 43952 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.35]
2020-06-14 20:49:36.666 INFO 43952 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-06-14 20:49:36.666 INFO 43952 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1971 ms
2020-06-14 20:49:36.945 INFO 43952 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-06-14 20:49:37.385 INFO 43952 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2020-06-14 20:49:37.405 INFO 43952 --- [ main] c.e.s.SimpleRestcontrollerApplication : Started SimpleRestcontrollerApplication in 3.904 seconds (JVM running for 4.914)
2020-06-14 20:49:49.705 INFO 43952 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-06-14 20:49:49.707 INFO 43952 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2020-06-14 20:49:49.722 INFO 43952 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 15 ms
request count = 1
request count = 2
request count = 3
|
5. FYI Jmeter Customize
こちらは参考までにですが、Jmeterのカスタマイズをする方法も調査してみたので記載しておきます。
どんな場合にするのかですが、単純なHTTPリクエストじゃなくて、以下のような場合にカスタマイズが必要なのかなと思います。
- 動的にある業務データを作成して投げたい場合
- kinesisやmq など特定のソフトウェアに対してデータを投入したい場合
5.1. Gradle Setting
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
plugins {
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
// Jmeter
compile 'org.apache.jmeter:ApacheJMeter_core:4.0'
// Jackson
compile 'com.fasterxml.jackson.core:jackson-databind:2.11.0'
// lombok
compileOnly 'org.projectlombok:lombok:1.18.12'
annotationProcessor 'org.projectlombok:lombok:1.18.12'
}
test {
useJUnitPlatform()
}
|
5.2. PreProcessor Impl and GUI Setting
5.2.1. PreProcessor Impl
1
2
3
4
5
6
7
8
9
10
11
12
|
package com.example.jmeter.gui;
import org.apache.jmeter.processor.PreProcessor;
import org.apache.jmeter.testelement.AbstractTestElement;
public class RsyncPreProcessor extends AbstractTestElement implements PreProcessor {
@Override
public void process() {
// 前処理が必要であれば実施する。
// getProperty("dataSize"), // GUIで入力したデータサイズ
}
}
|
5.2.2. GUI Setting
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
package com.example.jmeter.gui;
import org.apache.jmeter.gui.util.VerticalPanel;
import org.apache.jmeter.processor.gui.AbstractPreProcessorGui;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.gui.JLabeledTextField;
import java.awt.*;
public class RsyncPreProcessorGui extends AbstractPreProcessorGui {
private JLabeledTextField dataSize;
public RsyncPreProcessorGui() {
// データサイズを設定するためのテキストフィールド
dataSize = new JLabeledTextField("data size(KiB): ");
// コンポーネントの配置
setLayout(new BorderLayout(0, 5));
setBorder(makeBorder());
add(makeTitlePanel(), BorderLayout.NORTH);
VerticalPanel mainPanel = new VerticalPanel();
mainPanel.add(dataSize);
add(mainPanel, BorderLayout.CENTER);
}
@Override
public String getLabelResource() {
return null;
}
public String getStaticLabel() {
return "Rsync PreProcessor";
}
// JMeter上でこの前処理が追加された時に呼び出される。
@Override
public TestElement createTestElement() {
// 前処理クラスのオブジェクトを生成
RsyncPreProcessor preProcessor = new RsyncPreProcessor();
modifyTestElement(preProcessor);
return preProcessor;
}
// GUIの入力値が更新された時に呼び出される。
@Override
public void modifyTestElement(TestElement testElement) {
testElement.clear();
configureTestElement(testElement);
// フィールドに入力されているデータサイズを前処理で使用できるようにする。
testElement.setProperty("dataSize", dataSize.getText());
}
// 必須ではないがこのメソッドを実装しておくと再起動時に
// 保存している設定ファイルから設定値を引き継げる。
public void configure(TestElement testElement) {
dataSize.setText(testElement.getPropertyAsString("dataSize"));
super.configure(testElement);
}
}
|
5.3. Sampler Impl and GUI Setting
5.3.1. Sampler Impl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
package com.example.jmeter.gui;
import com.example.jmeter.resource.RequestResource;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.apache.jmeter.samplers.AbstractSampler;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
public class RsyncSampler extends AbstractSampler {
// GUIで指定したURLを取得する。
String url = getProperty("destinationUrl").getStringValue();
ObjectMapper mapper = new ObjectMapper();
// コマンドを作成
private String createCommand(String url) {
StringBuilder sb = new StringBuilder();
sb.append("curl ")
.append(url);
return sb.toString();
}
@SneakyThrows
@Override
public SampleResult sample(Entry entry) {
SampleResult result = new SampleResult();
// 送信コマンドを生成する。
String command = createCommand(url);
result.sampleStart();
try {
// サンプラー処理を実施する。
// コマンドを実行する。
Runtime runtime = Runtime.getRuntime();
Process process;
process = runtime.exec(command);
process.waitFor();
result.setSuccessful(true);
} catch (Exception e) {
System.out.println(e.getMessage());
result.setSuccessful(false);
}
result.sampleEnd();
return result;
}
}
|
5.3.2. GUI Setting
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
package com.example.jmeter.gui;
import org.apache.jmeter.gui.util.VerticalPanel;
import org.apache.jmeter.samplers.gui.AbstractSamplerGui;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jorphan.gui.JLabeledTextField;
import javax.swing.*;
import java.awt.*;
public class RsyncSamplerGui extends AbstractSamplerGui {
private static final long serialVersionUID = 1L;
private JLabeledTextField destinationUrl;
public RsyncSamplerGui() {
// コンポーネントの配置
setLayout(new BorderLayout(0, 5));
setBorder(makeBorder());
add(makeTitlePanel(), BorderLayout.NORTH);
VerticalPanel mainPanel = new VerticalPanel();
mainPanel.add(createDestinationPanel());
add(mainPanel, BorderLayout.CENTER);
}
private Component createDestinationPanel() {
// 負荷掛け対象である送信先のURL
destinationUrl = new JLabeledTextField("url: ");
JPanel dataPanel = new VerticalPanel();
dataPanel.setBorder(BorderFactory.createTitledBorder("destination"));
dataPanel.add(destinationUrl);
return dataPanel;
}
@Override
public String getLabelResource() {
return null;
}
public String getStaticLabel() {
return "Rsync Sampler";
}
// JMeter上でこのサンプラーが追加された時に呼び出される。
@Override
public TestElement createTestElement() {
// サンプラークラスのオブジェクトを生成
RsyncSampler sampler = new RsyncSampler();
modifyTestElement(sampler);
return sampler;
}
// GUIの入力値が更新された時に呼び出される。
@Override
public void modifyTestElement(TestElement testElement) {
testElement.clear();
configureTestElement(testElement);
// 入力されている値をサンプラーで使用できるようにする。
testElement.setProperty("destinationUrl", destinationUrl.getText());
}
// 必須ではないがこのメソッドを実装しておくと再起動時に
// 保存している設定ファイルから設定値を引き継げる。
public void configure(TestElement testElement) {
destinationUrl.setText(testElement.getPropertyAsString("destinationUrl"));
super.configure(testElement);
}
}
|
5.4. File Placement
上の設定で出来たソースコードを、gradle jar
でビルドし、Jarファイルを作成します。
Jmeterの /lib/ext
フォルダに格納することで、カスタマイズされたJarファイルが Jmeter
に読み込まれて利用することが出来ます。
Macの場合は、こちらのファイルパスです。
/usr/local/Cellar/jmeter/5.3/libexec/lib/ext
6. Reference