一、Jmeter的簡介
Jmeter一款開源的壓力測試工具,而這款開源的測試工具是基于Java開發。Jmeter最初的設計是為了web的性能測試。而在后面擴展了很多種類的測試。
Jmeter是基于Java編寫,所以使用時需要安裝jdk
二、Jmeter負載測試和性能測試種類
1.Web - HTTP, HTTPS (Java, NodeJS, PHP, ASP.NET, …)
2.SOAP / REST Webservices
3.FTP
4.Database via JDBC
5.LDAP
6.Message-oriented middleware (MOM) via JMS
7.Mail - SMTP(S), POP3(S) and IMAP(S)
8.Native commands or shell scripts
9.TCP
10.Java Objects
三、Jmeter基本組件簡介
我們這里只講解使用到的一些組件。而其他組件可以到Jmeter的官網了解(https://jmeter.apache.org/),在Jmeter下每個組件都是節點的方式進行配置。如我們在圖形化界面中,都會有一個TestPlan的根節點,其他控件都添加在根節點下。
3.1.TestPlan
測試計劃,每一個測試都為一個測試計劃。
2.ThreadGroup:是一個測試計劃的開始。所有的controller、sampler必須在線程組下。不過有一些特許的控件如Listeners可以直接在TestPlan下。
3.sampler:采樣器,也就是我們各種性能測試和負載測試的收集器。如:http采樣器:HTTPSampler等
4.Controller:主要用于壓力測試邏輯的處理,如我們這里使用了LoopController進行控制線程的循環次數,是永久還是循環壓力測試多次。
四、Jmeter的調用方式
調用Jmeter有5中方式:
- 使用命令的方式
- 使用ANT的方式
- 使用MAVEN的PLUGIN方式
- 使用JAVA代碼調用
- 使用blazemeter進行調用
這里只演示JAVA調用,其他方式的調用可以blazemeter的文章(https://www.blazemeter.com/blog/5-ways-launch-jmeter-test-without-using-jmeter-gui)
五、使用JAVA調用jmeter
一、創建項目
我們這里使用了Ecplise IDE創建Maven項目。
二、導入Jmeter的包
我們這里演示使用的是http的壓力測試。所以會用到ApacheJMeter_http的包和ApacheJMeter_core的包
<!--jmeter核心包-->
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_core</artifactId>
<version>4.0</version>
</dependency>
<!--jmeter組件包-->
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_components</artifactId>
<version>4.0</version>
</dependency>
<!--jmeter Http包-->
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_http</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
三、演示代碼
package com.study;
import java.io.File;
import org.apache.jmeter.JMeter;
import org.apache.jmeter.control.LoopController;
import org.apache.jmeter.engine.StandardJMeterEngine;
import org.apache.jmeter.protocol.http.control.HeaderManager;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
import org.apache.jmeter.reporters.ResultCollector;
import org.apache.jmeter.reporters.Summariser;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.TestPlan;
import org.apache.jmeter.threads.ThreadGroup;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.collections.HashTree;
public class TestPlanLauncher {
public static void main(String[] args) {
// jemter 引擎
StandardJMeterEngine standardJMeterEngine = new StandardJMeterEngine();
// 設置不適用gui的方式調用jmeter
System.setProperty(JMeter.JMETER_NON_GUI, "true");
// 設置jmeter.properties文件,我們將jmeter文件存放在resources中,通過classload
String path = TestPlanLauncher.class.getClassLoader().getResource("jmeter.properties").getPath();
File jmeterPropertiesFile = new File(path);
if (jmeterPropertiesFile.exists()) {
JMeterUtils.loadJMeterProperties(jmeterPropertiesFile.getPath());
HashTree testPlanTree = new HashTree();
// 創建測試計劃
TestPlan testPlan = new TestPlan("Create JMeter Script From Java Code");
// 創建http請求收集器
HTTPSamplerProxy examplecomSampler = createHTTPSamplerProxy();
// 創建循環控制器
LoopController loopController = createLoopController();
// 創建線程組
ThreadGroup threadGroup = createThreadGroup();
// 線程組設置循環控制
threadGroup.setSamplerController(loopController);
// 將測試計劃添加到測試配置樹種
HashTree threadGroupHashTree = testPlanTree.add(testPlan, threadGroup);
// 將http請求采樣器添加到線程組下
threadGroupHashTree.add(examplecomSampler);
//增加結果收集
Summariser summer = null;
String summariserName = JMeterUtils.getPropDefault("summariser.name", "summary");
if (summariserName.length() > 0) {
summer = new Summariser(summariserName);
}
ResultCollector logger = new ResultCollector(summer);
testPlanTree.add(testPlanTree.getArray(), logger);
// 配置jmeter
standardJMeterEngine.configure(testPlanTree);
// 運行
standardJMeterEngine.run();
}
}
/**
* 創建線程組
*
* @return
*/
public static ThreadGroup createThreadGroup() {
ThreadGroup threadGroup = new ThreadGroup();
threadGroup.setName("Example Thread Group");
threadGroup.setNumThreads(1);
threadGroup.setRampUp(0);
threadGroup.setProperty(TestElement.TEST_CLASS, ThreadGroup.class.getName());
threadGroup.setScheduler(true);
threadGroup.setDuration(60);
threadGroup.setDelay(0);
return threadGroup;
}
/**
* 創建循環控制器
*
* @return
*/
public static LoopController createLoopController() {
// Loop Controller
LoopController loopController = new LoopController();
loopController.setLoops(-1);
loopController.setContinueForever(true);
loopController.setProperty(TestElement.TEST_CLASS, LoopController.class.getName());
loopController.initialize();
return loopController;
}
/**
* 創建http采樣器
*
* @return
*/
public static HTTPSamplerProxy createHTTPSamplerProxy() {
HeaderManager headerManager = new HeaderManager();
headerManager.setProperty("Content-Type", "multipart/form-data");
HTTPSamplerProxy httpSamplerProxy = new HTTPSamplerProxy();
httpSamplerProxy.setDomain("www.baidu.com");
httpSamplerProxy.setPort(80);
httpSamplerProxy.setPath("/");
httpSamplerProxy.setMethod("GET");
httpSamplerProxy.setConnectTimeout("5000");
httpSamplerProxy.setUseKeepAlive(true);
httpSamplerProxy.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName());
httpSamplerProxy.setHeaderManager(headerManager);
return httpSamplerProxy;
}
}
三、詳講
3.1 Jmeter引擎StandardJMeterEngine
StandardJMeterEngine是Java調用Jmeter的入口。
3.2 Jmeter.properties文件
- 指定Jmeter.properties文件,Jmeter.properties文件主要用于Jmeter全局基礎配置。我們可以根據自己的需求進行修改配置文件。
例如我們需要進行分布式壓力測試的情況下,我們就需要在remote_hosts添加遠程的IP。 - 指定jmeter.properties文件的方式,我們可以通過源碼的分析發現加載配置Jmeter.properties配置文件,先從指定的文件中查找,如果查找不到jmeter.properties文件,會在org/apache/jmeter/jmeter.properties查找查找。也就是說我們可以將配置文件存放在org/apache/jmeter路徑下或者指定配置文件。
public static void loadJMeterProperties(String file) {
Properties p = new Properties(System.getProperties());
InputStream is = null;
try {
File f = new File(file);
is = new FileInputStream(f);
p.load(is);
} catch (IOException e) {
try {
is = ClassLoader.getSystemResourceAsStream(
"org/apache/jmeter/jmeter.properties"); // $NON-NLS-1$
if (is == null) {
throw new RuntimeException("Could not read JMeter properties file:" + file);
}
p.load(is);
} catch (IOException ex) {
throw new RuntimeException("Could not read JMeter properties file:" + file);
}
} finally {
JOrphanUtils.closeQuietly(is);
}
appProperties = p;
}
3.3 HashTree
我們在使用Jmeter的圖形化界面的時候,我們可以看出所有組件都是添加在TestPlan下。而在使用非圖形化界面的時,我們的StandardJMeterEngine通過configure方法來進行配置我們的TestPlan、ThreadGroup、LoopController等組件,因此需要創建HashTree。
3.4 TestPlan
我們在使用圖形化界面都知道所有組件都是存放在TestPlan中,所以我們需要創建一個TestPlan用于存放組件。
3.5 HTTPSamplerProxy
HTTPSamplerProxy是HTTP采樣器,在Jmeter中提供做了多種采樣器,我們可以根據采樣數據來確定到底使用哪一個采樣器。在壓力測試時,是通過Thread Group來進行創建線程進行壓力測試的。每個線程都需要通過Sampler來確定去并發訪問,因此Sampler是存放在Thread Group下。
3.6 LoopController
LoopController是一個壓力循環次數的控制器。我們通常會配置ThreadGroup進行使用,例如我們在Thread Group中設置持續5秒,并發量50的情況下,我們會這是LoopController為永久循環。我們需要注意:永久循環的情況下loops應設置為-1.
loopController.setLoops(-1);
loopController.setContinueForever(true);
注意:Controller是存放在Thread Group下。
3.7 ThreadGroup
在Jmeter下是通過線程方式去并發訪問,線程管理Jmeter通過ThreadGroup來控制線程的數量和線程的創建、線程持續訪問的時間。
3.8 ResultCollector
- ResultCollector是一個結果的收集器,ResultController收集請求結果是通過監聽的方式進行收集結果。
- 通過查看ResultController的源碼發現其實現了SampleListener接口,并且調用sampleOccurred方法處理每個sampler的結果。
/**
* When a test result is received, display it and save it.
*
* @param event
* the sample event that was received
*/
@Override
public void sampleOccurred(SampleEvent event) {
SampleResult result = event.getResult();
if (isSampleWanted(result.isSuccessful())) {
sendToVisualizer(result);
if (out != null && !isResultMarked(result) && !this.isStats) {
SampleSaveConfiguration config = getSaveConfig();
result.setSaveConfig(config);
try {
if (config.saveAsXml()) {
SaveService.saveSampleResult(event, out);
} else { // !saveAsXml
String savee = CSVSaveService.resultToDelimitedString(event);
out.println(savee);
}
} catch (Exception err) {
log.error("Error trying to record a sample", err); // should throw exception back to caller
}
}
}
if(summariser != null) {
summariser.sampleOccurred(event);
}
}
- 在ResultController.sampleOccurred方法中我們可以看到summariser不為空的情況下調用summariser的sampleOccurred方法,從summariser.sampleOccurred方法我們可以知道summariser一定是實現了SampleListener接口。
public class Summariser extends AbstractTestElement
implements Serializable, SampleListener, TestStateListener, NoThreadClone, Remoteable {
- 在多線程的情況下,Jmeter會對Controller、Sampler等組件為每個線程拷貝一份對象。而對于組件ResultCollector是不拷貝對象。
/**
* This class handles all saving of samples.
* The class must be thread-safe because it is shared between threads (NoThreadClone).
*/
public class ResultCollector extends AbstractListenerElement implements SampleListener, Clearable, Serializable,
TestStateListener, Remoteable, NoThreadClone {
Reference:
https://jmeter.apache.org/api/index.html
https://www.blazemeter.com/blog/5-ways-launch-jmeter-test-without-using-jmeter-gui
https://jmeter.apache.org/usermanual/get-started.html