JMeter 源码解读 [5] - 核心类 org.apache.jmeter.JMeter

上一篇 程序入口 NewDriver 提到Main函数是通过反射的方式调用org.apache.jmeter.JMeterstart函数来启动Jmeter ,那么今天我们来看看Jmeter 这个类是怎么工作的.

我们从start 函数来开始分析,由于外层的NewDriver 可以看做一层透明的代理,实际的业务逻辑是透传到JMeter来处理的,首先就是解析启动时候传入的外部命令行参数

1
2
3
4
5
6
7
8
9
10
11
CLArgsParser parser = new CLArgsParser(args, options);
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";
}
}

我们看到上面的代码的主要工作是解析命令行参数,并且判断有没有存在矛盾的组合,例如GUI 相关的参数,但是实际的运行模式是非GUI 模式等。

各种相关解析的任务完成后,有一个比较重要的工作updateClossLoader的调用,它是JMeter类自身的一个方法,我们可以来看一下

1
2
3
4
5
private void updateClassLoader() throws MalformedURLException {
updatePath("search_paths",";", true); //$NON-NLS-1$//$NON-NLS-2$
updatePath("user.classpath",File.pathSeparator, true);//$NON-NLS-1$
updatePath("plugin_dependency_paths",";", false);//$NON-NLS-1$
}

它的实际作用是把search_pathsuser.classpath里面设置的 jar 文件添加到自定义的classloader 中,它的作用可以让我们在某些场景下更方便使用JMeter,例如我们自定义的Java Sampler,在官方的guide里说是要把Java Sampler 打包成 Jar 放到lib/ext 目录下,这个很麻烦,实际上我们可以通过设置search_pathsuser.classpath的方式来简化操作。

然后我们再往下看,跳过一些处理特定option的code ,最核心的就是如下两个核心调用,一个是启动 GUI 的模式, 这里传入的testFile 参数就是我们的 jmx 测试 plan 文件路径

1
2
startGui(testFile);
startOptionalServers();

另外一种就是非 GUI 模式

1
2
startNonGui(testFile, jtlFile, rem, reportAtEndOpt != null);
startOptionalServers();

startOptionalServers() 是一个独立的BeanShell server, 用来方便对 Jmeter 进程做一些动态调整,以后可以单独写一个来介绍beanshell server 相关的东西, 这里先不关注了。GUI 的模式在启动过后就能看到界面了(一个 swing 的 app)等待用户的输入,所以我们先看一下startNonGui 这个方法, 这个方法的核心是创建了一个新的JMeter driver (源码里自己也有一个疑问,why does it create a new instance?) ,在非分布式的场景下,实际最后是调用了

1
2
3
4
5
6
7
private void startNonGui(String testFile, String logFile, CLOption remoteStart, boolean generateReportDashboard)
throws IllegalUserActionException, ConfigurationException {
....
// 忽略上面的一些调用,和 Distributed 相关的函数
driver.runNonGui(testFile, logFile, remoteStart != null, remoteHostsString, generateReportDashboard);
}

那么我们再来看runNonGui 在干什么,它是真的要开始跑性能测试了,首先是对于 TestPlan 的 HashTree 的解析,获取需要的相关信息和再做一些初始化,如何解析HashTree这个我们在之前的 HashTree 中已经介绍过了, 最后的核心是调用如下代码来启动 JMeter 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (!remoteStart) {
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 {
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();
}
startUdpDdaemon(engines)

我们看到首先会区分是不是 Remote 模式,我们这里只看非Remote的场景。engine.runTest() 这个是一个异步的调用,实际是启动一个线程,这个具体下次谈StandardJmeterEngine 的时候来看

startUdpDdaemon(engines) 是一个JMeter管理服务,用来管理 Jmeter 的运行状况,例如Shutdown/StopTestNow/HeapDump/ThreadDump 等,这个样的设计让整个JMeter的管理和JMeter自身的进程实现了解耦。

好,下一次我们来看一下 StandardJMeterEngine的实现