一、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