Jmeter源码解析之启动流程分析
流程图: 时序图:
1.启动类NewDriver
功能目录结构
org
.apache
.jmeter
.NewDriver
1.主自动类初始化代码
static {
final List
<URL> jars
= new LinkedList<>();
final String initiaClasspath
= System
.getProperty(JAVA_CLASS_PATH
);
String tmpDir
;
StringTokenizer tok
= new StringTokenizer(initiaClasspath
, File
.pathSeparator
);
if (tok
.countTokens() == 1
|| (tok
.countTokens() == 2
&& OS_NAME_LC
.startsWith("mac os x")
)
) {
File jar
= new File(tok
.nextToken());
try {
tmpDir
= jar
.getCanonicalFile().getParentFile().getParent();
} catch (IOException e
) {
tmpDir
= null
;
}
} else {
tmpDir
= System
.getProperty("jmeter.home","");
if (tmpDir
.length() == 0) {
File userDir
= new File(System
.getProperty("user.dir"));
tmpDir
= userDir
.getAbsoluteFile().getParent();
}
}
tmpDir
= tmpDir
+ File
.separator
+ "apache-jmeter-5.3";
JMETER_INSTALLATION_DIRECTORY
=tmpDir
;
boolean usesUNC
= OS_NAME_LC
.startsWith("windows");
StringBuilder classpath
= new StringBuilder();
File
[] libDirs
= new File[] { new File(JMETER_INSTALLATION_DIRECTORY
+ File
.separator
+ "lib"),
new File(JMETER_INSTALLATION_DIRECTORY
+ File
.separator
+ "lib" + File
.separator
+ "ext"),
new File(JMETER_INSTALLATION_DIRECTORY
+ File
.separator
+ "lib" + File
.separator
+ "junit")};
for (File libDir
: libDirs
) {
File
[] libJars
= libDir
.listFiles((dir
, name
) -> name
.endsWith(".jar"));
if (libJars
== null
) {
new Throwable("Could not access " + libDir
).printStackTrace();
continue;
}
Arrays
.sort(libJars
);
for (File libJar
: libJars
) {
try {
String s
= libJar
.getPath();
if (usesUNC
) {
if (s
.startsWith("\\\\") && !s
.startsWith("\\\\\\")) {
s
= "\\\\" + s
;
} else if (s
.startsWith("//") && !s
.startsWith("///")) {
s
= "//" + s
;
}
}
jars
.add(new File(s
).toURI().toURL());
classpath
.append(CLASSPATH_SEPARATOR
);
classpath
.append(s
);
} catch (MalformedURLException e
) {
EXCEPTIONS_IN_INIT
.add(new Exception("Error adding jar:"+libJar
.getAbsolutePath(), e
));
}
}
}
System
.setProperty(JAVA_CLASS_PATH
, initiaClasspath
+ classpath
.toString());
loader
= AccessController
.doPrivileged(
(PrivilegedAction
<DynamicClassLoader>) () ->
new DynamicClassLoader(jars
.toArray(new URL[jars
.size()]))
);
}
1.1.拿到jmeter安装目录
tmpDir
= tmpDir
+ File
.separator
+ "apache-jmeter-5.3";
JMETER_INSTALLATION_DIRECTORY
=tmpDir
;
1.2. 获取到lib目录下的所有jar包的文件路径
1.3. 加载所有的依赖类
System
.setProperty(JAVA_CLASS_PATH
, initiaClasspath
+ classpath
.toString());
loader
= AccessController
.doPrivileged(
(PrivilegedAction
<DynamicClassLoader>) () ->
new DynamicClassLoader(jars
.toArray(new URL[jars
.size()]))
);
1.4. DynamicClassLoader源码
public class DynamicClassLoader extends URLClassLoader {
public DynamicClassLoader(URL
[] urls
) {
super(urls
);
}
public DynamicClassLoader(URL
[] urls
, ClassLoader parent
) {
super(urls
, parent
);
}
public DynamicClassLoader(URL
[] urls
, ClassLoader parent
,
URLStreamHandlerFactory factory
) {
super(urls
, parent
, factory
);
}
@Override
public void addURL(URL url
) {
super.addURL(url
);
}
public static void updateLoader(URL
[] urls
) {
DynamicClassLoader loader
= (DynamicClassLoader
) Thread
.currentThread().getContextClassLoader();
for(URL url
: urls
) {
loader
.addURL(url
);
}
}
}
2.main方法解析
public static void main(String
[] args
) {
if(!EXCEPTIONS_IN_INIT
.isEmpty()) {
System
.err
.println("Configuration error during init, see exceptions:"+exceptionsToString(EXCEPTIONS_IN_INIT
));
} else {
Thread
.currentThread().setContextClassLoader(loader
);
setLoggingProperties(args
);
try {
if(System
.getProperty(HEADLESS_MODE_PROPERTY
) == null
&& shouldBeHeadless(args
)) {
System
.setProperty(HEADLESS_MODE_PROPERTY
, "true");
}
Class
<?> initialClass
= loader
.loadClass("org.apache.jmeter.JMeter");
Object instance
= initialClass
.getDeclaredConstructor().newInstance();
Method startup
= initialClass
.getMethod("start", new Class[] { new String[0].getClass() });
startup
.invoke(instance
, new Object[] { args
});
} catch(Throwable e
){
e
.printStackTrace();
System
.err
.println("JMeter home directory was detected as: "+JMETER_INSTALLATION_DIRECTORY
);
}
}
}
2.1. 设置线程的上下文加载器
Thread
.currentThread().setContextClassLoader(loader
);
2.2. 设置日志属性setLoggingProperties(args)
根据启动命令行参数j和i执行日志文件路径
private static void setLoggingProperties(String
[] args
) {
String jmLogFile
= getCommandLineArgument(args
, 'j', "jmeterlogfile");
if (jmLogFile
!= null
&& !jmLogFile
.isEmpty()) {
jmLogFile
= replaceDateFormatInFileName(jmLogFile
);
System
.setProperty(JMETER_LOGFILE_SYSTEM_PROPERTY
, jmLogFile
);
} else if (System
.getProperty(JMETER_LOGFILE_SYSTEM_PROPERTY
) == null
) {
System
.setProperty(JMETER_LOGFILE_SYSTEM_PROPERTY
, "jmeter.log");
}
String jmLogConf
= getCommandLineArgument(args
, 'i', "jmeterlogconf");
File logConfFile
= null
;
if (jmLogConf
!= null
&& !jmLogConf
.isEmpty()) {
logConfFile
= new File(jmLogConf
);
} else if (System
.getProperty("log4j.configurationFile") == null
) {
logConfFile
= new File("log4j2.xml");
if (!logConfFile
.isFile()) {
logConfFile
= new File(JMETER_INSTALLATION_DIRECTORY
, "bin" + File
.separator
+ "log4j2.xml");
}
}
if (logConfFile
!= null
) {
System
.setProperty("log4j.configurationFile", logConfFile
.toURI().toString());
}
}
2.3. 判断启动方式
private static boolean shouldBeHeadless(String
[] args
) {
for (String arg
: args
) {
if("-n".equals(arg
) || "-s".equals(arg
) || "-g".equals(arg
)) {
return true;
}
}
return false;
}
2.4. 加载org.apache.jmeter.JMeter类启动start方法(反射)
Class
<?> initialClass
= loader
.loadClass("org.apache.jmeter.JMeter");
Object instance
= initialClass
.getDeclaredConstructor().newInstance();
Method startup
= initialClass
.getMethod("start", new Class[] { new String[0].getClass() });
startup
.invoke(instance
, new Object[] { args
});
2.启动执行类JMeter
public void start(String
[] args
) {
CLArgsParser parser
= new CLArgsParser(args
, options
);
String error
= parser
.getErrorString();
if (error
== null
){
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
);
System
.out
.println("Usage");
System
.out
.println(CLUtil
.describeOptions(options
).toString());
System
.out
.println("Error: " + error
);
return;
}
try {
initializeProperties(parser
);
Thread
.setDefaultUncaughtExceptionHandler(
(Thread t
, Throwable e
) -> {
if (!(e
instanceof ThreadDeath)) {
log
.error("Uncaught exception in thread " + t
, e
);
System
.err
.println("Uncaught Exception " + e
+ " in thread " + t
+ ". See log file for details.");
}
});
if (log
.isInfoEnabled()) {
log
.info(JMeterUtils
.getJMeterCopyright());
log
.info("Version {}", JMeterUtils
.getJMeterVersion());
log
.info("java.version={}", System
.getProperty("java.version"));
log
.info("java.vm.name={}", System
.getProperty("java.vm.name"));
log
.info("os.name={}", System
.getProperty("os.name"));
log
.info("os.arch={}", System
.getProperty("os.arch"));
log
.info("os.version={}", System
.getProperty("os.version"));
log
.info("file.encoding={}", System
.getProperty("file.encoding"));
log
.info("java.awt.headless={}", System
.getProperty("java.awt.headless"));
log
.info("Max memory ={}", Runtime
.getRuntime().maxMemory());
log
.info("Available Processors ={}", Runtime
.getRuntime().availableProcessors());
log
.info("Default Locale={}", Locale
.getDefault().getDisplayName());
log
.info("JMeter Locale={}", JMeterUtils
.getLocale().getDisplayName());
log
.info("JMeterHome={}", JMeterUtils
.getJMeterHome());
log
.info("user.dir ={}", System
.getProperty("user.dir"));
log
.info("PWD ={}", new File(".").getCanonicalPath());
log
.info("IP: {} Name: {} FullName: {}", JMeterUtils
.getLocalHostIP(), JMeterUtils
.getLocalHostName(),
JMeterUtils
.getLocalHostFullName());
}
setProxy(parser
);
updateClassLoader();
if (log
.isDebugEnabled())
{
String jcp
=System
.getProperty("java.class.path");
String
[] bits
= jcp
.split(File
.pathSeparator
);
log
.debug("ClassPath");
for(String bit
: bits
){
log
.debug(bit
);
}
}
long now
=System
.currentTimeMillis();
JMeterUtils
.setProperty("START.MS",Long
.toString(now
));
Date today
=new Date(now
);
JMeterUtils
.setProperty("START.YMD",new SimpleDateFormat("yyyyMMdd").format(today
));
JMeterUtils
.setProperty("START.HMS",new SimpleDateFormat("HHmmss").format(today
));
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"));
} else if (parser
.getArgumentById(OPTIONS_OPT
) != null
) {
displayAsciiArt();
System
.out
.println(CLUtil
.describeOptions(options
).toString());
} else if (parser
.getArgumentById(SERVER_OPT
) != null
) {
try {
RemoteJMeterEngineImpl
.startServer(RmiUtils
.getRmiRegistryPort());
startOptionalServers();
} catch (Exception ex
) {
System
.err
.println("Server failed to start: "+ex
);
log
.error("Giving up, as server failed with:", ex
);
throw ex
;
}
} else {
String testFile
=null
;
CLOption testFileOpt
= parser
.getArgumentById(TESTFILE_OPT
);
if (testFileOpt
!= null
){
testFile
= testFileOpt
.getArgument();
if (USE_LAST_JMX
.equals(testFile
)) {
testFile
= LoadRecentProject
.getRecentFile(0);
}
}
CLOption testReportOpt
= parser
.getArgumentById(REPORT_GENERATING_OPT
);
if (testReportOpt
!= null
) {
String reportFile
= testReportOpt
.getArgument();
extractAndSetReportOutputFolder(parser
, deleteResultFile
);
ReportGenerator generator
= new ReportGenerator(reportFile
, null
);
generator
.generate();
} else if (parser
.getArgumentById(NONGUI_OPT
) == null
) {
startGui(testFile
);
startOptionalServers();
} else {
extractAndSetReportOutputFolder(parser
, deleteResultFile
);
CLOption remoteTest
= parser
.getArgumentById(REMOTE_OPT_PARAM
);
if (remoteTest
== null
) {
remoteTest
= parser
.getArgumentById(REMOTE_OPT
);
}
CLOption jtl
= parser
.getArgumentById(LOGFILE_OPT
);
String jtlFile
= null
;
if (jtl
!= null
) {
jtlFile
= processLAST(jtl
.getArgument(), ".jtl");
}
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");
}
startNonGui(testFile
, jtlFile
, remoteTest
, reportAtEndOpt
!= null
);
startOptionalServers();
}
}
} catch (IllegalUserActionException e
) {
System
.out
.println("Incorrect Usage:"+e
.getMessage());
System
.out
.println(CLUtil
.describeOptions(options
).toString());
} catch (Throwable e
) {
log
.error("An error occurred: ", e
);
System
.out
.println("An error occurred: " + e
.getMessage());
System
.exit(1);
}
}
2.1. 判断执行时命令行启动还是GUI启动
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";
}
2.2. 初始化启动配置
private void initializeProperties(CLArgsParser parser
) {
if (parser
.getArgumentById(PROPFILE_OPT
) != null
) {
JMeterUtils
.loadJMeterProperties(parser
.getArgumentById(PROPFILE_OPT
).getArgument());
} else {
JMeterUtils
.loadJMeterProperties(NewDriver
.getJMeterDir() + File
.separator
+ "bin" + File
.separator
+ "jmeter.properties");
}
JMeterUtils
.initLocale();
if (parser
.getArgumentById(JMETER_HOME_OPT
) == null
) {
JMeterUtils
.setJMeterHome(NewDriver
.getJMeterDir());
} else {
JMeterUtils
.setJMeterHome(parser
.getArgumentById(JMETER_HOME_OPT
).getArgument());
}
Properties jmeterProps
= JMeterUtils
.getJMeterProperties();
remoteProps
= new Properties();
String userProp
= JMeterUtils
.getPropDefault("user.properties","");
if (userProp
.length() > 0){
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
);
}
}
}
String sysProp
= JMeterUtils
.getPropDefault("system.properties","");
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
);
}
}
}
List
<CLOption> clOptions
= parser
.getArguments();
for (CLOption option
: clOptions
) {
String name
= option
.getArgument(0);
String value
= option
.getArgument(1);
switch (option
.getDescriptor().getId()) {
case CLOption
.TEXT_ARGUMENT
:
throw new IllegalArgumentException("Unknown arg: " + option
.getArgument());
case PROPFILE2_OPT
:
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
) {
log
.warn("Can't find additional property file: {}", name
, e
);
} catch (IOException e
) {
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
) {
if (log
.isWarnEnabled()) {
log
.warn("Cannot find system property file. {}", e
.getLocalizedMessage());
}
}
break;
case SYSTEM_PROPERTY
:
if (value
.length() > 0) {
log
.info("Setting System property: {}={}", name
, value
);
System
.getProperties().setProperty(name
, value
);
} else {
log
.warn("Removing System property: {}", name
);
System
.getProperties().remove(name
);
}
break;
case JMETER_PROPERTY
:
if (value
.length() > 0) {
log
.info("Setting JMeter property: {}={}", name
, value
);
jmeterProps
.setProperty(name
, value
);
} else {
log
.warn("Removing JMeter property: {}", name
);
jmeterProps
.remove(name
);
}
break;
case JMETER_GLOBAL_PROP
:
if (value
.length() > 0) {
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
) {
if (log
.isWarnEnabled()) {
log
.warn("Could not find properties file: {}", e
.getLocalizedMessage());
}
} catch (IOException e
) {
if (log
.isWarnEnabled()) {
log
.warn("Could not load properties file: {}", e
.getLocalizedMessage());
}
}
}
}
break;
case LOGLEVEL
:
if (value
.length() > 0) {
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 {
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;
case REMOTE_STOP
:
remoteStop
= true;
break;
case FORCE_DELETE_RESULT_FILE
:
deleteResultFile
= true;
break;
default:
break;
}
}
String sampleVariables
= (String
) jmeterProps
.get(SampleEvent
.SAMPLE_VARIABLES
);
if (sampleVariables
!= null
){
remoteProps
.put(SampleEvent
.SAMPLE_VARIABLES
, sampleVariables
);
}
jmeterProps
.put("jmeter.version", JMeterUtils
.getJMeterVersion());
}
2.3.设置JVM代理
private void setProxy(CLArgsParser parser
) throws IllegalUserActionException
{
if (parser
.getArgumentById(PROXY_USERNAME
) != null
) {
Properties jmeterProps
= JMeterUtils
.getJMeterProperties();
if (parser
.getArgumentById(PROXY_PASSWORD
) != null
) {
String u
= parser
.getArgumentById(PROXY_USERNAME
).getArgument();
String p
= parser
.getArgumentById(PROXY_PASSWORD
).getArgument();
Authenticator
.setDefault(new ProxyAuthenticator(u
, p
));
log
.info("Set Proxy login: {}/{}", u
, p
);
jmeterProps
.setProperty(HTTP_PROXY_USER
, u
);
jmeterProps
.setProperty(HTTP_PROXY_PASS
, p
);
} else {
String u
= parser
.getArgumentById(PROXY_USERNAME
).getArgument();
Authenticator
.setDefault(new ProxyAuthenticator(u
, ""));
log
.info("Set Proxy login: {}", u
);
jmeterProps
.setProperty(HTTP_PROXY_USER
, u
);
}
}
if (parser
.getArgumentById(PROXY_HOST
) != null
&& parser
.getArgumentById(PROXY_PORT
) != null
) {
String h
= parser
.getArgumentById(PROXY_HOST
).getArgument();
String p
= parser
.getArgumentById(PROXY_PORT
).getArgument();
System
.setProperty("http.proxyHost", h
);
System
.setProperty("https.proxyHost", h
);
System
.setProperty("http.proxyPort", p
);
System
.setProperty("https.proxyPort", p
);
String proxyScheme
= null
;
if (parser
.getArgumentById(PROXY_SCHEME
) != null
) {
proxyScheme
= parser
.getArgumentById(PROXY_SCHEME
).getArgument();
if(!StringUtils
.isBlank(proxyScheme
)){
System
.setProperty("http.proxyScheme", proxyScheme
);
}
}
if(log
.isInfoEnabled()) {
log
.info("Set proxy Host: {}, Port: {}, Scheme: {}", h
, p
, proxyScheme
!= null
? proxyScheme
: "Not set");
}
} else if (parser
.getArgumentById(PROXY_HOST
) != null
|| parser
.getArgumentById(PROXY_PORT
) != null
) {
throw new IllegalUserActionException(JMeterUtils
.getResString("proxy_cl_error"));
}
if (parser
.getArgumentById(NONPROXY_HOSTS
) != null
) {
String n
= parser
.getArgumentById(NONPROXY_HOSTS
).getArgument();
System
.setProperty("http.nonProxyHosts", n
);
System
.setProperty("https.nonProxyHosts", n
);
log
.info("Set http[s].nonProxyHosts: {}", n
);
}
}
2.4.更新代理加载器
private void updateClassLoader() throws MalformedURLException
{
updatePath("search_paths",";", true);
updatePath("user.classpath",File
.pathSeparator
, true);
updatePath("plugin_dependency_paths",";", false);
}
2.5.根据启动类型判断启动server还是主控服务(GUI or No GUI)
Start the server
try {
RemoteJMeterEngineImpl
.startServer(RmiUtils
.getRmiRegistryPort());
startOptionalServers();
} catch (Exception ex
) {
System
.err
.println("Server failed to start: "+ex
);
log
.error("Giving up, as server failed with:", ex
);
throw ex
;
}
Start the GUI
String testFile
=null
;
CLOption testFileOpt
= parser
.getArgumentById(TESTFILE_OPT
);
if (testFileOpt
!= null
){
testFile
= testFileOpt
.getArgument();
if (USE_LAST_JMX
.equals(testFile
)) {
testFile
= LoadRecentProject
.getRecentFile(0);
}
}
CLOption testReportOpt
= parser
.getArgumentById(REPORT_GENERATING_OPT
);
if (testReportOpt
!= null
) {
String reportFile
= testReportOpt
.getArgument();
extractAndSetReportOutputFolder(parser
, deleteResultFile
);
ReportGenerator generator
= new ReportGenerator(reportFile
, null
);
generator
.generate();
} else if (parser
.getArgumentById(NONGUI_OPT
) == null
) {
startGui(testFile
);
startOptionalServers();
No GUI
extractAndSetReportOutputFolder(parser
, deleteResultFile
);
CLOption remoteTest
= parser
.getArgumentById(REMOTE_OPT_PARAM
);
if (remoteTest
== null
) {
remoteTest
= parser
.getArgumentById(REMOTE_OPT
);
}
CLOption jtl
= parser
.getArgumentById(LOGFILE_OPT
);
String jtlFile
= null
;
if (jtl
!= null
) {
jtlFile
= processLAST(jtl
.getArgument(), ".jtl");
}
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");
}
startNonGui(testFile
, jtlFile
, remoteTest
, reportAtEndOpt
!= null
);
startOptionalServers();
3.startNonGui启动方法
主要调用runNonGui方法
private void startNonGui(String testFile
, String logFile
, CLOption remoteStart
, boolean generateReportDashboard
)
throws IllegalUserActionException
, ConfigurationException
{
System
.setProperty(JMETER_NON_GUI
, "true");
JMeter driver
= new JMeter();
driver
.remoteProps
= this.remoteProps
;
driver
.remoteStop
= this.remoteStop
;
driver
.deleteResultFile
= this.deleteResultFile
;
PluginManager
.install(this, false);
String remoteHostsString
= null
;
if (remoteStart
!= null
) {
remoteHostsString
= remoteStart
.getArgument();
if (remoteHostsString
== null
) {
remoteHostsString
= JMeterUtils
.getPropDefault(
"remote_hosts",
"127.0.0.1");
}
}
if (testFile
== null
) {
throw new IllegalUserActionException("Non-GUI runs require a test plan");
}
driver
.runNonGui(testFile
, logFile
, remoteStart
!= null
, remoteHostsString
, generateReportDashboard
);
}
4.runNonGui方法
void runNonGui(String testFile
, String logFile
, boolean remoteStart
, String remoteHostsString
, boolean generateReportDashboard
)
throws ConfigurationException
{
try {
File f
= new File(testFile
);
if (!f
.exists() || !f
.isFile()) {
throw new ConfigurationException("The file " + f
.getAbsolutePath() + " doesn't exist or can't be opened");
}
FileServer
.getFileServer().setBaseForScript(f
);
HashTree tree
= SaveService
.loadTree(f
);
@SuppressWarnings("deprecation")
JMeterTreeModel treeModel
= new JMeterTreeModel(new Object());
JMeterTreeNode root
= (JMeterTreeNode
) treeModel
.getRoot();
treeModel
.addSubTree(tree
, root
);
SearchByClass
<ReplaceableController> replaceableControllers
=
new SearchByClass<>(ReplaceableController
.class);
tree
.traverse(replaceableControllers
);
Collection
<ReplaceableController> replaceableControllersRes
= replaceableControllers
.getSearchResults();
for (ReplaceableController replaceableController
: replaceableControllersRes
) {
replaceableController
.resolveReplacementSubTree(root
);
}
HashTree clonedTree
= convertSubTree(tree
, true);
Summariser summariser
= null
;
String summariserName
= JMeterUtils
.getPropDefault("summariser.name", "");
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 {
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 RemoteThreadsListenerTestElement());
List
<JMeterEngine> engines
= new LinkedList<>();
println("Created the tree successfully using "+testFile
);
if (!remoteStart
) {
JMeterEngine engine
= new StandardJMeterEngine();
clonedTree
.add(clonedTree
.getArray()[0], new ListenToTest(
org
.apache
.jmeter
.JMeter
.ListenToTest
.RunMode
.LOCAL
, false, reportGenerator
));
engine
.configure(clonedTree
);
long now
=System
.currentTimeMillis();
println("Starting standalone test @ "+new Date(now
)+" ("+now
+")");
engines
.add(engine
);
engine
.runTest();
} else {
java
.util
.StringTokenizer st
= new java.util.StringTokenizer(remoteHostsString
.trim(), ",");
List
<String> hosts
= new LinkedList<>();
while (st
.hasMoreElements()) {
hosts
.add(((String
) st
.nextElement()).trim());
}
ListenToTest testListener
= new ListenToTest(
org
.apache
.jmeter
.JMeter
.ListenToTest
.RunMode
.REMOTE
, remoteStop
, reportGenerator
);
clonedTree
.add(clonedTree
.getArray()[0], testListener
);
DistributedRunner distributedRunner
=new DistributedRunner(this.remoteProps
);
distributedRunner
.setStdout(System
.out
);
distributedRunner
.setStdErr(System
.err
);
distributedRunner
.init(hosts
, clonedTree
);
engines
.addAll(distributedRunner
.getEngines());
testListener
.setStartedRemoteEngines(engines
);
distributedRunner
.start();
}
startUdpDdaemon(engines
);
} catch (ConfigurationException e
) {
throw e
;
} catch (Exception e
) {
System
.out
.println("Error in NonGUIDriver " + e
.toString());
log
.error("Error in NonGUIDriver", e
);
throw new ConfigurationException("Error in NonGUIDriver " + e
.getMessage(), e
);
}
}
4.1. 加载测试文件(jmx格式的测试计划)
FileServer
.getFileServer().setBaseForScript(f
);
HashTree tree
= SaveService
.loadTree(f
);
@SuppressWarnings("deprecation")
JMeterTreeModel treeModel
= new JMeterTreeModel(new Object());
JMeterTreeNode root
= (JMeterTreeNode
) treeModel
.getRoot();
treeModel
.addSubTree(tree
, root
);
SearchByClass
<ReplaceableController> replaceableControllers
=
new SearchByClass<>(ReplaceableController
.class);
tree
.traverse(replaceableControllers
);
Collection
<ReplaceableController> replaceableControllersRes
= replaceableControllers
.getSearchResults();
for (ReplaceableController replaceableController
: replaceableControllersRes
) {
replaceableController
.resolveReplacementSubTree(root
);
}
HashTree clonedTree
= convertSubTree(tree
, true);
4.2.加载结果采集类
4.3. 调用StandardJMeterEngine的runTest启动测试
@Override
public void runTest() throws JMeterEngineException
{
if (host
!= null
){
long now
=System
.currentTimeMillis();
System
.out
.println("Starting the test on host " + host
+ " @ "+new Date(now
)+" ("+now
+")");
}
try {
Thread runningThread
= new Thread(this, "StandardJMeterEngine");
runningThread
.start();
} catch (Exception err
) {
stopTest();
throw new JMeterEngineException(err
);
}
}
3.Jmeter执行类StandardJMeterEngine
StandardJMeterEngine实现了Runnable接口,run方法是执行用例的方法
public class StandardJMeterEngine implements JMeterEngine, Runnable
3.1 执行线程的run方法分析
@Override
public void run() {
log
.info("Running the test!");
running
= true;
SampleEvent
.initSampleVariables();
JMeterContextService
.startTest();
try {
PreCompiler compiler
= new PreCompiler();
test
.traverse(compiler
);
} catch (RuntimeException e
) {
log
.error("Error occurred compiling the tree:",e
);
JMeterUtils
.reportErrorToUser("Error occurred compiling the tree: - see log file", e
);
return;
}
SearchByClass
<TestStateListener> testListeners
= new SearchByClass<>(TestStateListener
.class);
test
.traverse(testListeners
);
testListeners
.getSearchResults().addAll(testList
);
testList
.clear();
test
.traverse(new TurnElementsOn());
notifyTestListenersOfStart(testListeners
);
List
<?> testLevelElements
= new LinkedList<>(test
.list(test
.getArray()[0]));
removeThreadGroups(testLevelElements
);
SearchByClass
<SetupThreadGroup> setupSearcher
= new SearchByClass<>(SetupThreadGroup
.class);
SearchByClass
<AbstractThreadGroup> searcher
= new SearchByClass<>(AbstractThreadGroup
.class);
SearchByClass
<PostThreadGroup> postSearcher
= new SearchByClass<>(PostThreadGroup
.class);
test
.traverse(setupSearcher
);
test
.traverse(searcher
);
test
.traverse(postSearcher
);
TestCompiler
.initialize();
Iterator
<SetupThreadGroup> setupIter
= setupSearcher
.getSearchResults().iterator();
Iterator
<AbstractThreadGroup> iter
= searcher
.getSearchResults().iterator();
Iterator
<PostThreadGroup> postIter
= postSearcher
.getSearchResults().iterator();
ListenerNotifier notifier
= new ListenerNotifier();
int groupCount
= 0;
JMeterContextService
.clearTotalThreads();
if (setupIter
.hasNext()) {
log
.info("Starting setUp thread groups");
while (running
&& setupIter
.hasNext()) {
AbstractThreadGroup group
= setupIter
.next();
groupCount
++;
String groupName
= group
.getName();
log
.info("Starting setUp ThreadGroup: {} : {} ", groupCount
, groupName
);
startThreadGroup(group
, groupCount
, setupSearcher
, testLevelElements
, notifier
);
if (serialized
&& setupIter
.hasNext()) {
log
.info("Waiting for setup thread group: {} to finish before starting next setup group",
groupName
);
group
.waitThreadsStopped();
}
}
log
.info("Waiting for all setup thread groups to exit");
waitThreadsStopped();
log
.info("All Setup Threads have ended");
groupCount
=0;
JMeterContextService
.clearTotalThreads();
}
groups
.clear();
JMeterUtils
.helpGC();
JMeterContextService
.getContext().setSamplingStarted(true);
boolean mainGroups
= running
;
while (running
&& iter
.hasNext()) {
AbstractThreadGroup group
= iter
.next();
if (group
instanceof SetupThreadGroup ||
group
instanceof PostThreadGroup) {
continue;
}
groupCount
++;
String groupName
= group
.getName();
log
.info("Starting ThreadGroup: {} : {}", groupCount
, groupName
);
startThreadGroup(group
, groupCount
, searcher
, testLevelElements
, notifier
);
if (serialized
&& iter
.hasNext()) {
log
.info("Waiting for thread group: {} to finish before starting next group", groupName
);
group
.waitThreadsStopped();
}
}
if (groupCount
== 0){
log
.info("No enabled thread groups found");
} else {
if (running
) {
log
.info("All thread groups have been started");
} else {
log
.info("Test stopped - no more thread groups will be started");
}
}
waitThreadsStopped();
groups
.clear();
if (postIter
.hasNext()){
groupCount
= 0;
JMeterContextService
.clearTotalThreads();
log
.info("Starting tearDown thread groups");
if (mainGroups
&& !running
) {
running
= tearDownOnShutdown
;
}
while (running
&& postIter
.hasNext()) {
AbstractThreadGroup group
= postIter
.next();
groupCount
++;
String groupName
= group
.getName();
log
.info("Starting tearDown ThreadGroup: {} : {}", groupCount
, groupName
);
startThreadGroup(group
, groupCount
, postSearcher
, testLevelElements
, notifier
);
if (serialized
&& postIter
.hasNext()) {
log
.info("Waiting for post thread group: {} to finish before starting next post group", groupName
);
group
.waitThreadsStopped();
}
}
waitThreadsStopped();
}
notifyTestListenersOfEnd(testListeners
);
JMeterContextService
.endTest();
if (JMeter
.isNonGUI() && SYSTEM_EXIT_FORCED
) {
log
.info("Forced JVM shutdown requested at end of test");
System
.exit(0);
}
}
3.1.1. 初始化
3.1.2. 启动setup线程组
3.1.3. 启动group线程组
3.1.4. 启动post线程租
3.2. 线程组执行方法startThreadGroup
private void startThreadGroup(AbstractThreadGroup group
, int groupCount
, SearchByClass
<?> searcher
, List
<?> testLevelElements
, ListenerNotifier notifier
)
{
try {
int numThreads
= group
.getNumThreads();
JMeterContextService
.addTotalThreads(numThreads
);
boolean onErrorStopTest
= group
.getOnErrorStopTest();
boolean onErrorStopTestNow
= group
.getOnErrorStopTestNow();
boolean onErrorStopThread
= group
.getOnErrorStopThread();
boolean onErrorStartNextLoop
= group
.getOnErrorStartNextLoop();
String groupName
= group
.getName();
log
.info("Starting {} threads for group {}.", numThreads
, groupName
);
if (onErrorStopTest
) {
log
.info("Test will stop on error");
} else if (onErrorStopTestNow
) {
log
.info("Test will stop abruptly on error");
} else if (onErrorStopThread
) {
log
.info("Thread will stop on error");
} else if (onErrorStartNextLoop
) {
log
.info("Thread will start next loop on error");
} else {
log
.info("Thread will continue on error");
}
ListedHashTree threadGroupTree
= (ListedHashTree
) searcher
.getSubTree(group
);
threadGroupTree
.add(group
, testLevelElements
);
groups
.add(group
);
group
.start(groupCount
, notifier
, threadGroupTree
, this);
} catch (JMeterStopTestException ex
) {
JMeterUtils
.reportErrorToUser("Error occurred starting thread group :" + group
.getName()+ ", error message:"+ex
.getMessage()
+", \r\nsee log file for more details", ex
);
return;
}
}
3.2.1 调用ThreadGroup的start方法
这里会判断delayedStartup,只有 delayedStartup 为true才会使用ThreadStarter去启动线程(就是界面上配置的ramp up的方式启动线程)
所以实际上通过ramp up方式启动线程组需要同时勾选上 delayedStartup
public void start(int groupNum
, ListenerNotifier notifier
, ListedHashTree threadGroupTree
, StandardJMeterEngine engine
) {
this.running
= true;
this.groupNumber
= groupNum
;
this.notifier
= notifier
;
this.threadGroupTree
= threadGroupTree
;
int numThreads
= getNumThreads();
int rampUpPeriodInSeconds
= getRampUp();
boolean isSameUserOnNextIteration
= isSameUserOnNextIteration();
delayedStartup
= isDelayedStartup();
log
.info("Starting thread group... number={} threads={} ramp-up={} delayedStart={}", groupNumber
,
numThreads
, rampUpPeriodInSeconds
, delayedStartup
);
if (delayedStartup
) {
threadStarter
= new Thread(new ThreadStarter(notifier
, threadGroupTree
, engine
), getName()+"-ThreadStarter");
threadStarter
.setDaemon(true);
threadStarter
.start();
} else {
final JMeterContext context
= JMeterContextService
.getContext();
long lastThreadStartInMillis
= 0;
int delayForNextThreadInMillis
= 0;
final int perThreadDelayInMillis
= Math
.round((float) rampUpPeriodInSeconds
* 1000 / numThreads
);
for (int threadNum
= 0; running
&& threadNum
< numThreads
; threadNum
++) {
long nowInMillis
= System
.currentTimeMillis();
if(threadNum
> 0) {
long timeElapsedToStartLastThread
= nowInMillis
- lastThreadStartInMillis
;
delayForNextThreadInMillis
+= perThreadDelayInMillis
- timeElapsedToStartLastThread
;
}
if (log
.isDebugEnabled()) {
log
.debug("Computed delayForNextThreadInMillis:{} for thread:{}", delayForNextThreadInMillis
, Thread
.currentThread().getId());
}
lastThreadStartInMillis
= nowInMillis
;
startNewThread(notifier
, threadGroupTree
, engine
, threadNum
, context
, nowInMillis
, Math
.max(0, delayForNextThreadInMillis
),
isSameUserOnNextIteration
);
}
}
log
.info("Started thread group number {}", groupNumber
);
}