1.概述
JMeter类通过Java反射的方式进行初始化并执行调用start()方法。
这里重点讲非GUI模式启动JMeter。
2.代码解读
2.1解析输入的args命令行参数
代码语言:txt复制 public void start(String[] args) {
// 解析输入的args命令行参数
CLArgsParser parser = new CLArgsParser(args, options);
log.info(args[0]);
String error = parser.getErrorString();
if (error == null){// Check option combinations
boolean gui = parser.getArgumentById(NONGUI_OPT)==null;
boolean nonGuiOnly = parser.getArgumentById(REMOTE_OPT)!=null
|| parser.getArgumentById(REMOTE_OPT_PARAM)!=null
|| parser.getArgumentById(REMOTE_STOP)!=null;
if (gui && nonGuiOnly) {
error = "-r and -R and -X are only valid in non-GUI mode";
}
}
// 打印解析命令函参数时的报错信息
if (null != error) {
System.err.println("Error: " error);//NOSONAR
System.out.println("Usage");//NOSONAR
System.out.println(CLUtil.describeOptions(options).toString());//NOSONAR
// repeat the error so no need to scroll back past the usage to see it
System.out.println("Error: " error);//NOSONAR
return;
}
2.2初始化Jmeter参数配置
调用initializeProperties方法来初始化Jmeter参数,主要有以下两点:
- 加载jmeter.properties文件的参数
- 加载user.properties文件的参数
- 加载system.properties文件的参数
- 解析外部其他参数
initializeProperties(parser); // Also initialises JMeter logging
2.2.1 加载jmeter.properties文件
优先加载命令行指定的外部的user.properties文件,找不到才会加载bin目录自带的jmeter.properties文件
代码语言:txt复制 //加载外部properties文件,并存入全局变量
if (parser.getArgumentById(PROPFILE_OPT) != null) {
JMeterUtils.loadJMeterProperties(parser.getArgumentById(PROPFILE_OPT).getArgument());
} else { //加载自带的properties文件,并存入全局变量
JMeterUtils.loadJMeterProperties(NewDriver.getJMeterDir() File.separator
"bin" File.separator // $NON-NLS-1$
"jmeter.properties");// $NON-NLS-1$
}
2.2.2 加载user.properties文件
从jmeter.properties文件获取user.properties文件名,读取该文件,然后将变量存入全局变量。
代码语言:txt复制 String userProp = JMeterUtils.getPropDefault("user.properties",""); //$NON-NLS-1$
if (userProp.length() > 0){ //$NON-NLS-1$
File file = JMeterUtils.findFile(userProp);
if (file.canRead()){
try (FileInputStream fis = new FileInputStream(file)){
log.info("Loading user properties from: {}", file);
Properties tmp = new Properties();
tmp.load(fis);
// 存入全局变量中
jmeterProps.putAll(tmp);
} catch (IOException e) {
log.warn("Error loading user property file: {}", userProp, e);
}
}
}
2.2.3 加载system.properties文件
从jmeter.properties文件获取system.properties文件名,读取该文件,然后将变量存入系统变量。
代码语言:txt复制 String sysProp = JMeterUtils.getPropDefault("system.properties",""); //$NON-NLS-1$
if (sysProp.length() > 0){
File file = JMeterUtils.findFile(sysProp);
if (file.canRead()) {
try (FileInputStream fis = new FileInputStream(file)){
log.info("Loading system properties from: {}", file);
System.getProperties().load(fis);
} catch (IOException e) {
log.warn("Error loading system property file: {}", sysProp, e);
}
}
}
2.2.4 解析其他命令行参数
代码语言:txt复制 // 获取命令行参数列表,并进行解析
List<CLOption> clOptions = parser.getArguments();
for (CLOption option : clOptions) {
// 变量键名
String name = option.getArgument(0);
// 变量值
String value = option.getArgument(1);
switch (option.getDescriptor().getId()) {
// Should not have any text arguments
case CLOption.TEXT_ARGUMENT:
throw new IllegalArgumentException("Unknown arg: " option.getArgument());
// 加载其他jmeter属性文件
case PROPFILE2_OPT: // Bug 33920 - allow multiple props
log.info("Loading additional properties from: {}", name);
try (FileInputStream fis = new FileInputStream(new File(name))){
Properties tmp = new Properties();
tmp.load(fis);
jmeterProps.putAll(tmp);
} catch (FileNotFoundException e) { // NOSONAR
log.warn("Can't find additional property file: {}", name, e);
} catch (IOException e) { // NOSONAR
log.warn("Error loading additional property file: {}", name, e);
}
break;
// 加载外部其他系统属性文件
case SYSTEM_PROPFILE:
log.info("Setting System properties from file: {}", name);
try (FileInputStream fis = new FileInputStream(new File(name))){
System.getProperties().load(fis);
} catch (IOException e) { // NOSONAR
if (log.isWarnEnabled()) {
log.warn("Cannot find system property file. {}", e.getLocalizedMessage());
}
}
break;
// 获取外部其他系统属性参数
case SYSTEM_PROPERTY:
if (value.length() > 0) { // Set it
log.info("Setting System property: {}={}", name, value);
System.getProperties().setProperty(name, value);
} else { // Reset it
log.warn("Removing System property: {}", name);
System.getProperties().remove(name);
}
break;
// 获取外部其他JMeter属性参数
case JMETER_PROPERTY:
if (value.length() > 0) { // Set it
log.info("Setting JMeter property: {}={}", name, value);
jmeterProps.setProperty(name, value);
} else { // Reset it
log.warn("Removing JMeter property: {}", name);
jmeterProps.remove(name);
}
break;
// 定义全局属性或属性文件
case JMETER_GLOBAL_PROP:
if (value.length() > 0) { // Set it
log.info("Setting Global property: {}={}", name, value);
remoteProps.setProperty(name, value);
} else {
File propFile = new File(name);
if (propFile.canRead()) {
log.info("Setting Global properties from the file {}", name);
try (FileInputStream fis = new FileInputStream(propFile)){
remoteProps.load(fis);
} catch (FileNotFoundException e) { // NOSONAR
if (log.isWarnEnabled()) {
log.warn("Could not find properties file: {}", e.getLocalizedMessage());
}
} catch (IOException e) { // NOSONAR
if (log.isWarnEnabled()) {
log.warn("Could not load properties file: {}", e.getLocalizedMessage());
}
}
}
}
break;
// 定义日志级别
case LOGLEVEL:
if (value.length() > 0) { // Set category
log.info("LogLevel: {}={}", name, value);
final Level logLevel = Level.getLevel(value);
if (logLevel != null) {
String loggerName = name;
if (name.startsWith("jmeter") || name.startsWith("jorphan")) {
loggerName = PACKAGE_PREFIX name;
}
Configurator.setAllLevels(loggerName, logLevel);
} else {
log.warn("Invalid log level, '{}' for '{}'.", value, name);
}
} else { // Set root level
log.warn("LogLevel: {}", name);
final Level logLevel = Level.getLevel(name);
if (logLevel != null) {
Configurator.setRootLevel(logLevel);
} else {
log.warn("Invalid log level, '{}', for the root logger.", name);
}
}
break;
// 测试结束时退出远程服务器(CLI模式)
case REMOTE_STOP:
remoteStop = true;
break;
// 开始测试前,强制删除现有结果文件和Web报告文件夹(如果存在)的标志
case FORCE_DELETE_RESULT_FILE:
deleteResultFile = true;
break;
default:
// ignored
break;
}
}
2.3启动jmeter的方式
根据不同的命令参数,判断JMeter的启动方式,作为master(client端)还是作为slave(server端)
代码语言:txt复制 // 打印版本并退出,用于CLI模式
if (parser.getArgumentById(VERSION_OPT) != null) {
displayAsciiArt();
// 打印帮助信息并退出
} else if (parser.getArgumentById(HELP_OPT) != null) {
displayAsciiArt();
System.out.println(JMeterUtils.getResourceFileAsText("org/apache/jmeter/help.txt"));//NOSONAR $NON-NLS-1$
// 打印命令行选项并退出
} else if (parser.getArgumentById(OPTIONS_OPT) != null) {
displayAsciiArt();
System.out.println(CLUtil.describeOptions(options).toString());//NOSONAR
// 运行服务器,作为slave模式
} else if (parser.getArgumentById(SERVER_OPT) != null) {
// Start the server
try {
// 启动server服务
RemoteJMeterEngineImpl.startServer(RmiUtils.getRmiRegistryPort());
startOptionalServers();
} catch (Exception ex) {
System.err.println("Server failed to start: " ex);//NOSONAR
log.error("Giving up, as server failed with:", ex);
throw ex;
}
// 运行client,作为master模式
} else {
String testFile=null;
// 获取测试jmx文件
CLOption testFileOpt = parser.getArgumentById(TESTFILE_OPT);
if (testFileOpt != null){
testFile = testFileOpt.getArgument();
if (USE_LAST_JMX.equals(testFile)) {
testFile = LoadRecentProject.getRecentFile(0);// most recent
}
}
// 获取测试报告文件
CLOption testReportOpt = parser.getArgumentById(REPORT_GENERATING_OPT);
if (testReportOpt != null) { // generate report from existing file
String reportFile = testReportOpt.getArgument();
// 创建测试报告文件
extractAndSetReportOutputFolder(parser, deleteResultFile);
// 生成测试报告
ReportGenerator generator = new ReportGenerator(reportFile, null);
generator.generate();
} else if (parser.getArgumentById(NONGUI_OPT) == null) { // not non-GUI => GUI
// 图形界面方式运行JMeter
startGui(testFile);
startOptionalServers();
} else { // NON-GUI must be true
// 创建测试报告文件
extractAndSetReportOutputFolder(parser, deleteResultFile);
// 远程启动服务器时,确定使用的host是命令行参数还是系统配置的
CLOption rem = parser.getArgumentById(REMOTE_OPT_PARAM);
if (rem == null) {
rem = parser.getArgumentById(REMOTE_OPT);
}
// 生成log日志文件
CLOption jtl = parser.getArgumentById(LOGFILE_OPT);
String jtlFile = null;
if (jtl != null) {
jtlFile = processLAST(jtl.getArgument(), ".jtl"); // $NON-NLS-1$
}
CLOption reportAtEndOpt = parser.getArgumentById(REPORT_AT_END_OPT);
if(reportAtEndOpt != null && jtlFile == null) {
throw new IllegalUserActionException(
"Option -" ((char)REPORT_AT_END_OPT) " requires -" ((char)LOGFILE_OPT ) " option");
}
// 命令行方式运行JMeter
startNonGui(testFile, jtlFile, rem, reportAtEndOpt != null);
startOptionalServers();
}
}
2.4命令行方式运行
这里主要讲命令行方式启动JMeter,通过调用startNonGui方法。
代码语言:txt复制private void startNonGui(String testFile, String logFile, CLOption remoteStart, boolean generateReportDashboard)
throws IllegalUserActionException, ConfigurationException {
// add a system property so samplers can check to see if JMeter
// is running in NonGui mode
System.setProperty(JMETER_NON_GUI, "true");// $NON-NLS-1$
JMeter driver = new JMeter();// TODO - why does it create a new instance?
driver.remoteProps = this.remoteProps; // 远程配置参数对象
driver.remoteStop = this.remoteStop; // 测试结束时退出远程服务器的标志
driver.deleteResultFile = this.deleteResultFile;
PluginManager.install(this, false);
// 解析远程服务器IP参数列表
String remoteHostsString = null;
if (remoteStart != null) {
remoteHostsString = remoteStart.getArgument();
// 从自带的配置文件获取
if (remoteHostsString == null) {
remoteHostsString = JMeterUtils.getPropDefault(
"remote_hosts", //$NON-NLS-1$
"127.0.0.1");//NOSONAR $NON-NLS-1$
}
}
if (testFile == null) {
throw new IllegalUserActionException("Non-GUI runs require a test plan");
}
driver.runNonGui(testFile, logFile, remoteStart != null, remoteHostsString, generateReportDashboard);
}
2.5运行runNonGui方法
判断测试jmx文件是否存在
代码语言:txt复制 File f = new File(testFile);
if (!f.exists() || !f.isFile()) {
println("Could not open " testFile);
return;
}
解析测试jmx文件,并存放到HashTree树结构中
代码语言:txt复制 HashTree tree = SaveService.loadTree(f);
克隆HashTree对象,并去除disable的对象
代码语言:txt复制 HashTree clonedTree = convertSubTree(tree, true);
创建测试报告
代码语言:txt复制 Summariser summariser = null;
String summariserName = JMeterUtils.getPropDefault("summariser.name", "");//$NON-NLS-1$
if (summariserName.length() > 0) {
log.info("Creating summariser <{}>", summariserName);
println("Creating summariser <" summariserName ">");
summariser = new Summariser(summariserName);
}
ResultCollector resultCollector = null; //测试结果收集器
if (logFile != null) {
resultCollector = new ResultCollector(summariser);
resultCollector.setFilename(logFile);
clonedTree.add(clonedTree.getArray()[0], resultCollector);
}
else {
// only add Summariser if it can not be shared with the ResultCollector
if (summariser != null) {
clonedTree.add(clonedTree.getArray()[0], summariser);
}
}
// 删除现有测试报告文件夹
if (deleteResultFile) {
SearchByClass<ResultCollector> resultListeners = new SearchByClass<>(ResultCollector.class);
clonedTree.traverse(resultListeners);
Iterator<ResultCollector> irc = resultListeners.getSearchResults().iterator();
while (irc.hasNext()) {
ResultCollector rc = irc.next();
File resultFile = new File(rc.getFilename());
if (resultFile.exists() && !resultFile.delete()) {
throw new IllegalStateException("Could not delete results file " resultFile.getAbsolutePath()
"(canRead:" resultFile.canRead() ", canWrite:" resultFile.canWrite() ")");
}
}
}
ReportGenerator reportGenerator = null;
if (logFile != null && generateReportDashboard) {
reportGenerator = new ReportGenerator(logFile, resultCollector);
}
clonedTree.add(clonedTree.getArray()[0], new ListenToTest(remoteStart && remoteStop ? engines : null, reportGenerator));
执行JMeter引擎
代码语言:txt复制 // 单机执行JMeter引擎
if (!remoteStart) {
//启动jmeter引擎
JMeterEngine engine = new StandardJMeterEngine();
engine.configure(clonedTree);
long now=System.currentTimeMillis();
println("Starting the test @ " new Date(now) " (" now ")");
engine.runTest();
engines.add(engine);
} else { // 分布式执行JMeter引擎
java.util.StringTokenizer st = new java.util.StringTokenizer(remoteHostsString, ",");//$NON-NLS-1$
List<String> hosts = new LinkedList<>();
while (st.hasMoreElements()) {
hosts.add((String) st.nextElement());
}
DistributedRunner distributedRunner=new DistributedRunner(this.remoteProps);
distributedRunner.setStdout(System.out); // NOSONAR
distributedRunner.setStdErr(System.err); // NOSONAR
distributedRunner.init(hosts, clonedTree);
engines.addAll(distributedRunner.getEngines());
distributedRunner.start();
}
// 将jmeter引擎加入Udp后台常驻进程列表中
startUdpDdaemon(engines);
2.6创建UDP后台socket常驻线程
代码语言:txt复制 private static void startUdpDdaemon(final List<JMeterEngine> engines) {
// 获取UDP连接默认端口号,默认从user.properties文件中获取,如果为空,则使用默认的端口号
int port = JMeterUtils.getPropDefault("jmeterengine.nongui.port", UDP_PORT_DEFAULT); // $NON-NLS-1$
// 获取UDP连接最大端口号
int maxPort = JMeterUtils.getPropDefault("jmeterengine.nongui.maxport", 4455); // $NON-NLS-1$
if (port > 1000){
// 建立UDP的socket连接
final DatagramSocket socket = getSocket(port, maxPort);
if (socket != null) {
// 创建线程
Thread waiter = new Thread("UDP Listener"){
@Override
public void run() {
waitForSignals(engines, socket);
}
};
// 将进程设置为常驻线程
waiter.setDaemon(true);
// 启动线程
waiter.start();
} else {
System.out.println("Failed to create UDP port");//NOSONAR
}
}
}
// 接收shutDown、stopTestNow等脚本建立的UDP连接
private static void waitForSignals(final List<JMeterEngine> engines, DatagramSocket socket) {
byte[] buf = new byte[80];
System.out.println("Waiting for possible Shutdown/StopTestNow/HeapDump/ThreadDump message on port " socket.getLocalPort());//NOSONAR
DatagramPacket request = new DatagramPacket(buf, buf.length);
try {
while(true) {
// 接收到UDP的socket请求
socket.receive(request);
InetAddress address = request.getAddress();
// Only accept commands from the local host
if (address.isLoopbackAddress()){
String command = new String(request.getData(), request.getOffset(), request.getLength(),"ASCII");
System.out.println("Command: " command " received from " address);//NOSONAR
log.info("Command: {} received from {}", command, address);
switch(command) {
case "StopTestNow" :
for(JMeterEngine engine : engines) {
engine.stopTest(true);
}
break;
case "Shutdown" :
for(JMeterEngine engine : engines) {
engine.stopTest(false);
}
break;
case "HeapDump" :
HeapDumper.dumpHeap();
break;
case "ThreadDump" :
ThreadDumper.threadDump();
break;
default:
System.out.println("Command: " command " not recognised ");//NOSONAR
}
}
}
} catch (Exception e) {
System.out.println(e);//NOSONAR
} finally {
socket.close();
}
}