自定义插件
注意:插件的
ApacheJMeter_core和ApacheJMeter_java需要和jmeter版本对应,否则在启动测试时候报告错误。
自定义插件相关基础
设置变量到上下文
JavaSamplerContext.getJMeterContext().setVariables(new JMeterVariables() {{
this.put("var1", value);
}});AbstractJavaSamplerClient基础
AbstractJavaSamplerClient 是 Apache JMeter 中的一个抽象类,位于 org.apache.jmeter.protocol.java.sampler 包下。这个类为开发者提供了一个框架,用于实现自定义的 Java 采样器(Sampler)客户端,以便在 JMeter 性能测试中模拟和测试 Java 应用程序或服务的性能。以下是关于 AbstractJavaSamplerClient 的详细介绍:
主要方法和用途
AbstractJavaSamplerClient 包含了几个关键的方法,这些方法在自定义采样器时可能需要被重写或实现:
- setupTest(JavaSamplerContext context): 此方法在每个线程开始执行测试之前被调用一次,用于进行一些初始化操作,如资源分配、环境设置等。
- teardownTest(JavaSamplerContext context): 与 setupTest 相对应,此方法在每个线程结束测试之后被调用一次,用于执行清理工作,如资源释放、环境恢复等。
- getDefaultParameters(): 此方法返回一个
Arguments对象,该对象定义了采样器在 JMeter GUI 中可配置的参数。通过此方法,开发者可以在 JMeter 的 Java 请求组件中展示自定义参数,并设置默认值。 - runTest(JavaSamplerContext context): 这是 AbstractJavaSamplerClient 中的核心方法,每个线程在每次迭代中都会调用此方法。开发者需要在此方法中实现具体的业务逻辑,如发送请求、接收响应、处理数据等,并返回一个
SampleResult对象,该对象包含了测试执行的结果。
实现步骤
要在 JMeter 中使用 AbstractJavaSamplerClient 创建自定义的 Java 采样器,通常遵循以下步骤:
- 创建 Java 类:创建一个 Java 类,并继承 AbstractJavaSamplerClient。
- 实现方法:根据需要重写 setupTest、teardownTest、getDefaultParameters 和 runTest 方法。特别是,runTest 方法是必须实现的,因为它是采样器的核心功能所在。
- 编译和打包:将 Java 代码编译成 JAR 文件,并确保 JAR 文件中包含了所有必要的依赖项。
- 部署 JAR 文件:将编译好的 JAR 文件放置在 JMeter 的
lib/ext目录下,这样 JMeter 就能加载并使用自定义的采样器了。 - 配置 JMeter:在 JMeter 中创建一个新的线程组,并添加一个 Java 请求。在 Java 请求中,配置类名称为自定义采样器的类名,并设置相应的参数。
- 运行测试:运行测试并查看结果,根据需要进行调整和优化。
注意事项
- 在实现自定义采样器时,需要确保对 JMeter 的工作原理和 API 有一定的了解。
- 自定义采样器的性能可能会影响整个测试的性能,因此需要仔细设计和优化。
- 在部署 JAR 文件之前,请确保 JAR 文件中不包含任何潜在的安全风险或不必要的依赖项。
通过上述步骤和注意事项,开发者可以利用 AbstractJavaSamplerClient 在 JMeter 中创建功能强大且灵活的自定义 Java 采样器,以满足各种复杂的性能测试需求。
lib 和 lib/ext 目录
在JMeter中,lib和lib/ext目录扮演着重要的角色,它们与JMeter的运行和扩展功能密切相关。以下是关于这两个目录的详细解释:
lib目录
- 作用:
lib目录是JMeter启动时的默认classpath,这意味着所有在JMeter运行过程中需要被引用的类或者jar包都必须存放在该目录下。 - 内容:该目录下存放了JMeter运行所需的各种jar包和类文件,这些文件是JMeter核心功能的基础。
- 注意事项:如果用户开发了新的JMeter组件或者需要添加额外的库文件(如JDBC驱动、Apache SOAP所需的jar包等),这些文件应该直接放在
lib目录下,而不是lib/ext目录下。不过,用户的扩展所依赖的包如果要被JMeter自动发现,也需要放置在lib目录下。
lib/ext目录
- 作用:
lib/ext目录用于存放JMeter的插件或者扩展组件。这些插件可以扩展JMeter的功能,使其能够支持更多的测试场景和协议。 - 内容:该目录下通常包含了一些官方或社区提供的插件jar包。例如,用户可以从JMeter的插件官网(如jmeter-plugins.org)下载所需的插件,并将其放置在
lib/ext目录下。 - 插件管理:JMeter提供了插件管理的功能,用户可以在JMeter的GUI界面中查看已安装的插件、安装新的插件或卸载不需要的插件。在安装新插件时,用户只需将插件的jar包放置在
lib/ext目录下,并重启JMeter即可。 - 注意事项:虽然
lib/ext目录用于存放插件,但并不意味着所有扩展功能都必须通过插件来实现。在某些情况下,用户也可以通过修改JMeter的配置文件或编写自定义的Java类来扩展其功能。
综上所述,lib和lib/ext目录在JMeter中扮演着不同的角色,但它们共同构成了JMeter功能扩展的基础。用户在使用JMeter时,应该根据需要合理地利用这两个目录来添加所需的库文件和插件,以便更好地满足测试需求。
实现自定义插件
详细用法请参考本站 示例
POM 添加如下配置:
<properties>
<jmeter.version>5.6.2</jmeter.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties><dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_core</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_java</artifactId>
<version>${jmeter.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-json</artifactId>
<version>5.8.28</version>
</dependency><build>
<plugins>
<!-- 使用下面插件打包包括所有依赖的jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>assemble-all</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>编写插件
/**
* 测试 JSON 解码性能
*/
public class JSONDecodeSampler extends AbstractJavaSamplerClient {
private static final Logger log = LoggerFactory.getLogger(JSONDecodeSampler.class);
final static String JSON = "{\"errorCode\":0,\"errorMessage\":null,\"dataObject\":\"你好\"}";
// 线程每个测试loop调用
@Override
public SampleResult runTest(JavaSamplerContext javaSamplerContext) {
SampleResult result = new SampleResult();
try {
// 样本测试开始,自动统计样本持续测试时间,不需要手动计算
result.sampleStart();
JSONObject jsonObject = JSONUtil.parseObj(JSON);
int errorCode = jsonObject.getInt("errorCode");
if (errorCode > 0) {
// 注入变量registerSuccess,能够使用vars.get("registerSuccess")获取变量值
javaSamplerContext.getJMeterVariables().put("registerSuccess", "false");
} else {
javaSamplerContext.getJMeterVariables().put("registerSuccess", "true");
}
// 设置发送数据长度
result.setSentBytes(5);
// 设置sample label
result.setSampleLabel("我的样品");
// 设置request headers
result.setRequestHeaders("设置请求头值");
// 设置request body
// https://stackoverflow.com/questions/74187155/how-to-populate-request-request-body-tab-of-a-jmeter-sampler-result-displa
result.setSamplerData("设置请求body内容");
// 用于协助测试是否会打印错误日志
/*boolean b = true;
if(b) {
throw new Exception("99999999");
}*/
// 标记样本成功
result.setSuccessful(true);
// 设置样本请求成功
result.setResponseOK();
} catch (Exception e) {
log.error(e.getMessage(), e);
// 标记样本失败
result.setSuccessful(false);
result.setResponseMessage("发生错误,原因: " + e.getMessage());
result.setResponseCode("50000");
} finally {
// 不使用responseData数据长度时手动设置received数据长度
/*result.setBytes(5L);
result.setBodySize(5L);*/
// 设置response headers
result.setResponseHeaders("设置response headers");
// 设置response body
// 设置相应数据,会自动计算received数据长度
result.setResponseData("123456".getBytes(StandardCharsets.UTF_8));
result.setDataType(SampleResult.TEXT);
result.sampleEnd();
}
return result;
}
// 测试线程启动时调用
@Override
public void setupTest(JavaSamplerContext context) {
super.setupTest(context);
// 注意:log.debug不会打印日志,猜测可能需要调整jmeter日志等级配置
// 在jmeter控制台或者jmeter.log日志文件中打印日志
log.info("线程启动");
}
// 测试线程退出时调用
@Override
public void teardownTest(JavaSamplerContext context) {
super.teardownTest(context);
log.info("线程退出");
}
// 用于定义采样器在JMeter GUI中可用的参数。当你将自定义采样器添加到JMeter的测试计划中,并尝试配置它时,这些参数就会显示在“添加/编辑”对话框中。
// getDefaultParameters() 方法应该返回一个 Arguments 对象,该对象包含了采样器需要的所有参数的定义。这些参数可以包括名称、默认值、描述等信息。
@Override
public Arguments getDefaultParameters() {
return new Arguments() {{
// 通过context.getParameter("k1")参数设置值
addArgument("k1", "v1");
addArgument("k2", "v2");
}};
}
}编译和部署插件
./deploy-plugin.shdeploy-plugin.sh 脚本如下:
#!/bin/bash
set -e
set -x
# 编译插件
mvn package
sudo rm -f /usr/local/jmeter/lib/ext/demo-jmeter-customize*.jar
# 复制插件到jmeter lib/ext目录
sudo cp ./target/demo-jmeter-customize-plugin-1.0.0-jar-with-dependencies.jar /usr/local/jmeter/lib/ext/demo-jmeter-customize-plugin-1.0.0.jar
# 复制插件依赖的库到jmeter lib/ext目录,否则会报告ClassNotFound错误
#sudo cp ./target/required-lib/*.jar /usr/local/jmeter/lib/ext/
#sudo chown root:root /usr/local/jmeter/lib/ext/*.jar
# 修改插件属主为root
sudo chown root:root /usr/local/jmeter/lib/ext/demo-jmeter-customize*.jar创建 jmeter.jmx 测试计划文件
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.2">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<intProp name="LoopController.loops">-1</intProp>
<boolProp name="LoopController.continue_forever">false</boolProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">16</stringProp>
<stringProp name="ThreadGroup.ramp_time">0</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
<boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
<boolProp name="ThreadGroup.delayedStart">false</boolProp>
</ThreadGroup>
<hashTree>
<JavaSampler guiclass="JavaTestSamplerGui" testclass="JavaSampler" testname="Java Request" enabled="true">
<elementProp name="arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="k1" elementType="Argument">
<stringProp name="Argument.name">k1</stringProp>
<stringProp name="Argument.value">v1</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="k2" elementType="Argument">
<stringProp name="Argument.name">k2</stringProp>
<stringProp name="Argument.value">v2</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="classname">com.future.demo.JSONDecodeSampler</stringProp>
</JavaSampler>
<hashTree/>
<ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="false">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
<ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="false">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<url>true</url>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>启动测试
jmeter -n -t jmeter.jmx