Jmeter源码解析之启动流程分析
流程图:
时序图:
1.启动类NewDriver
功能目录结构
// src/launcher catalog
org.apache.jmeter.NewDriver
1.主自动类初始化代码
static {
final List<URL> jars = new LinkedList<>();
final String initiaClasspath = System.getProperty(JAVA_CLASS_PATH);
// Find JMeter home dir from the initial classpath
String tmpDir;
StringTokenizer tok = new StringTokenizer(initiaClasspath, File.pathSeparator);
if (tok.countTokens() == 1
|| (tok.countTokens() == 2 // Java on Mac OS can add a second entry to the initial classpath
&& OS_NAME_LC.startsWith("mac os x")// $NON-NLS-1$
)
) {
File jar = new File(tok.nextToken());
try {
tmpDir = jar.getCanonicalFile().getParentFile().getParent();
} catch (IOException e) {
tmpDir = null;
}
} else {// e.g. started from IDE with full classpath
tmpDir = System.getProperty("jmeter.home","");// Allow override $NON-NLS-1$ $NON-NLS-2$
if (tmpDir.length() == 0) {
File userDir = new File(System.getProperty("user.dir"));// $NON-NLS-1$
tmpDir = userDir.getAbsoluteFile().getParent();
}
}
tmpDir = tmpDir + File.separator + "apache-jmeter-5.3";
JMETER_INSTALLATION_DIRECTORY=tmpDir;
/*
* Does the system support UNC paths? If so, may need to fix them up
* later
*/
boolean usesUNC = OS_NAME_LC.startsWith("windows");// $NON-NLS-1$
// Add standard jar locations to initial classpath
StringBuilder classpath = new StringBuilder();
File[] libDirs = new File[] { new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib"),// $NON-NLS-1$ $NON-NLS-2$
new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib" + File.separator + "ext"),// $NON-NLS-1$ $NON-NLS-2$
new File(JMETER_INSTALLATION_DIRECTORY + File.separator + "lib" + File.separator + "junit")};// $NON-NLS-1$ $NON-NLS-2$
for (File libDir : libDirs) {
File[] libJars = libDir.listFiles((dir, name) -> name.endsWith(".jar"));
if (libJars == null) {
new Throwable("Could not access " + libDir).printStackTrace(); // NOSONAR No logging here
continue;
}
Arrays.sort(libJars); // Bug 50708 Ensure predictable order of jars
for (File libJar : libJars) {
try {
String s = libJar.getPath();
// Fix path to allow the use of UNC URLs
if (usesUNC) {
if (s.startsWith("\\\\") && !s.startsWith("\\\\\\")) {// $NON-NLS-1$ $NON-NLS-2$
s = "\\\\" + s;// $NON-NLS-1$
} else if (s.startsWith("//") && !s.startsWith("///")) {// $NON-NLS-1$ $NON-NLS-2$
s = "//" + s;// $NON-NLS-1$
}
} // usesUNC
jars.add(new File(s).toURI().toURL());// See Java bug 4496398
classpath.append(CLASSPATH_SEPARATOR);
classpath.append(s);
} catch (MalformedURLException e) { // NOSONAR
EXCEPTIONS_IN_INIT.add(new Exception("Error adding jar:"+libJar.getAbsolutePath(), e));
}
}
}
// ClassFinder needs the classpath
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. 加载所有的依赖类
// ClassFinder needs the classpath
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);
}
// Make the addURL method visible
@Override
public void addURL(URL url) {
super.addURL(url);
}
/**
*
* @param urls - list of URLs to add to the thread's classloader
*/
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)); // NOSONAR Intentional System.err use
} else {
Thread.currentThread().setContextClassLoader(loader);
setLoggingProperties(args);
try {
// Only set property if it has not been set explicitely
if(System.getProperty(HEADLESS_MODE_PROPERTY) == null && shouldBeHeadless(args)) {
System.setProperty(HEADLESS_MODE_PROPERTY, "true");
}
Class<?> initialClass = loader.loadClass("org.apache.jmeter.JMeter");// $NON-NLS-1$
Object instance = initialClass.getDeclaredConstructor().newInstance();
Method startup = initialClass.getMethod("start", new Class[] { new String[0].getClass() });// $NON-NLS-1$
startup.invoke(instance, new Object[] { args });
} catch(Throwable e){ // NOSONAR We want to log home directory in case of exception
e.printStackTrace(); // NOSONAR No logger at this step
System.err.println("JMeter home directory was detected as: "+JMETER_INSTALLATION_DIRECTORY); // NOSONAR Intentional System.err use
}
}
}
2.1. 设置线程的上下文加载器
Thread.currentThread().setContextClassLoader(loader);// loader initialized in the static code
2.2. 设置日志属性setLoggingProperties(args)
根据启动命令行参数j和i执行日志文件路径
private static void setLoggingProperties(String[] args) {
String jmLogFile = getCommandLineArgument(args, 'j', "jmeterlogfile");// $NON-NLS-1$ $NON-NLS-2$
if (jmLogFile != null && !jmLogFile.isEmpty()) {
jmLogFile = replaceDateFormatInFileName(jmLogFile);
System.setProperty(JMETER_LOGFILE_SYSTEM_PROPERTY, jmLogFile);// $NON-NLS-1$
} else if (System.getProperty(JMETER_LOGFILE_SYSTEM_PROPERTY) == null) {// $NON-NLS-1$
System.setProperty(JMETER_LOGFILE_SYSTEM_PROPERTY, "jmeter.log");// $NON-NLS-1$ $NON-NLS-2$
}
String jmLogConf = getCommandLineArgument(args, 'i', "jmeterlogconf");// $NON-NLS-1$ $NON-NLS-2$
File logConfFile = null;
if (jmLogConf != null && !jmLogConf.isEmpty()) {
logConfFile = new File(jmLogConf);
} else if (System.getProperty("log4j.configurationFile") == null) {// $NON-NLS-1$
logConfFile = new File("log4j2.xml");// $NON-NLS-1$
if (!logConfFile.isFile()) {
logConfFile = new File(JMETER_INSTALLATION_DIRECTORY, "bin" + File.separator + "log4j2.xml");// $NON-NLS-1$ $NON-NLS-2$
}
}
if (logConfFile != null) {
System.setProperty("log4j.configurationFile", logConfFile.toURI().toString());// $NON-NLS-1$
}
}
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){// 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;
}
try {
initializeProperties(parser); // Also initialises JMeter logging
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.");//NOSONAR
}
});
if (log.isInfoEnabled()) {
log.info(JMeterUtils.getJMeterCopyright());
log.info("Version {}", JMeterUtils.getJMeterVersion());
log.info("java.version={}", System.getProperty("java.version"));//$NON-NLS-1$ //$NON-NLS-2$
log.info("java.vm.name={}", System.getProperty("java.vm.name"));//$NON-NLS-1$ //$NON-NLS-2$
log.info("os.name={}", System.getProperty("os.name"));//$NON-NLS-1$ //$NON-NLS-2$
log.info("os.arch={}", System.getProperty("os.arch"));//$NON-NLS-1$ //$NON-NLS-2$
log.info("os.version={}", System.getProperty("os.version"));//$NON-NLS-1$ //$NON-NLS-2$
log.info("file.encoding={}", System.getProperty("file.encoding"));//$NON-NLS-1$ //$NON-NLS-2$
log.info("java.awt.headless={}", System.getProperty("java.awt.headless"));//$NON-NLS-1$ //$NON-NLS-2$
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"));//$NON-NLS-1$ //$NON-NLS-2$
log.info("PWD ={}", new File(".").getCanonicalPath());//$NON-NLS-1$
log.info("IP: {} Name: {} FullName: {}", JMeterUtils.getLocalHostIP(), JMeterUtils.getLocalHostName(),
JMeterUtils.getLocalHostFullName());
}
setProxy(parser);
updateClassLoader();
if (log.isDebugEnabled())
{
String jcp=System.getProperty("java.class.path");// $NON-NLS-1$
String[] bits = jcp.split(File.pathSeparator);
log.debug("ClassPath");
for(String bit : bits){
log.debug(bit);
}
}
// Set some (hopefully!) useful properties
long now=System.currentTimeMillis();
JMeterUtils.setProperty("START.MS",Long.toString(now));// $NON-NLS-1$
Date today=new Date(now); // so it agrees with above
JMeterUtils.setProperty("START.YMD",new SimpleDateFormat("yyyyMMdd").format(today));// $NON-NLS-1$ $NON-NLS-2$
JMeterUtils.setProperty("START.HMS",new SimpleDateFormat("HHmmss").format(today));// $NON-NLS-1$ $NON-NLS-2$
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
} else if (parser.getArgumentById(SERVER_OPT) != null) {
// Start the server
try {
RemoteJMeterEngineImpl.startServer(RmiUtils.getRmiRegistryPort()); // $NON-NLS-1$
startOptionalServers();
} catch (Exception ex) {
System.err.println("Server failed to start: "+ex);//NOSONAR
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);// 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
startGui(testFile);
startOptionalServers();
} else { // NON-GUI must be true
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"); // $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");
}
startNonGui(testFile, jtlFile, remoteTest, reportAtEndOpt != null);
startOptionalServers();
}
}
} catch (IllegalUserActionException e) {// NOSONAR
System.out.println("Incorrect Usage:"+e.getMessage());//NOSONAR
System.out.println(CLUtil.describeOptions(options).toString());//NOSONAR
} catch (Throwable e) { // NOSONAR
log.error("An error occurred: ", e);
System.out.println("An error occurred: " + e.getMessage());//NOSONAR
// FIXME Should we exit here ? If we are called by Maven or Jenkins
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 // $NON-NLS-1$
+ "jmeter.properties");// $NON-NLS-1$
}
JMeterUtils.initLocale();
// Bug 33845 - allow direct override of Home dir
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();
// Add local JMeter properties, if the file is found
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);
}
}
}
// Add local system properties, if the file is found
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);
}
}
}
// Process command line property definitions
// These can potentially occur multiple times
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());
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;
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;
case REMOTE_STOP:
remoteStop = true;
break;
case FORCE_DELETE_RESULT_FILE:
deleteResultFile = true;
break;
default:
// ignored
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);//for Httpclient
jmeterProps.setProperty(HTTP_PROXY_PASS, p);//for Httpclient
} 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 );// $NON-NLS-1$
System.setProperty("https.proxyHost", h);// $NON-NLS-1$
System.setProperty("http.proxyPort", p);// $NON-NLS-1$
System.setProperty("https.proxyPort", p);// $NON-NLS-1$
String proxyScheme = null;
if (parser.getArgumentById(PROXY_SCHEME) != null) {
proxyScheme = parser.getArgumentById(PROXY_SCHEME).getArgument();
if(!StringUtils.isBlank(proxyScheme)){
System.setProperty("http.proxyScheme", proxyScheme );// $NON-NLS-1$
}
}
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"));// $NON-NLS-1$
}
if (parser.getArgumentById(NONPROXY_HOSTS) != null) {
String n = parser.getArgumentById(NONPROXY_HOSTS).getArgument();
System.setProperty("http.nonProxyHosts", n );// $NON-NLS-1$
System.setProperty("https.nonProxyHosts", n );// $NON-NLS-1$
log.info("Set http[s].nonProxyHosts: {}", n);
}
}
2.4.更新代理加载器
// Update classloader if necessary
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$
}
2.5.根据启动类型判断启动server还是主控服务(GUI or No GUI)
-
Start the server
// Start the server try { RemoteJMeterEngineImpl.startServer(RmiUtils.getRmiRegistryPort()); // $NON-NLS-1$ startOptionalServers(); } catch (Exception ex) { System.err.println("Server failed to start: "+ex);//NOSONAR 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);// 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 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"); // $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"); } startNonGui(testFile, jtlFile, remoteTest, reportAtEndOpt != null); startOptionalServers();
3.startNonGui启动方法
主要调用runNonGui方法
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);
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);
}
4.runNonGui方法
// run test in batch mode
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") // Deliberate use of deprecated ctor
JMeterTreeModel treeModel = new JMeterTreeModel(new Object());// NOSONAR Create non-GUI version to avoid headless problems
JMeterTreeNode root = (JMeterTreeNode) treeModel.getRoot();
treeModel.addSubTree(tree, root);
// Hack to resolve ModuleControllers in non GUI mode
SearchByClass<ReplaceableController> replaceableControllers =
new SearchByClass<>(ReplaceableController.class);
tree.traverse(replaceableControllers);
Collection<ReplaceableController> replaceableControllersRes = replaceableControllers.getSearchResults();
for (ReplaceableController replaceableController : replaceableControllersRes) {
replaceableController.resolveReplacementSubTree(root);
}
// Ensure tree is interpreted (ReplaceableControllers are replaced)
// For GUI runs this is done in Start.java
HashTree clonedTree = convertSubTree(tree, true);
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);
}
// Used for remote notification of threads start/stop,see BUG 54152
// Summariser uses this feature to compute correctly number of threads
// when NON GUI mode is used
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(), ",");//$NON-NLS-1$
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); // NOSONAR
distributedRunner.setStdErr(System.err); // NOSONAR
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());//NOSONAR
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") // Deliberate use of deprecated ctor
JMeterTreeModel treeModel = new JMeterTreeModel(new Object());// NOSONAR Create non-GUI version to avoid headless problems
JMeterTreeNode root = (JMeterTreeNode) treeModel.getRoot();
treeModel.addSubTree(tree, root);
// Hack to resolve ModuleControllers in non GUI mode
SearchByClass<ReplaceableController> replaceableControllers =
new SearchByClass<>(ReplaceableController.class);
tree.traverse(replaceableControllers);
Collection<ReplaceableController> replaceableControllersRes = replaceableControllers.getSearchResults();
for (ReplaceableController replaceableController : replaceableControllersRes) {
replaceableController.resolveReplacementSubTree(root);
}
// Ensure tree is interpreted (ReplaceableControllers are replaced)
// For GUI runs this is done in Start.java
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+")"); // NOSONAR Intentional
}
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;
/*
* Ensure that the sample variables are correctly initialised for each run.
*/
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; // no point continuing
}
/**
* Notification of test listeners needs to happen after function
* replacement, but before setting RunningVersion to true.
*/
SearchByClass<TestStateListener> testListeners = new SearchByClass<>(TestStateListener.class); // TL - S&E
test.traverse(testListeners);
// Merge in any additional test listeners
// currently only used by the function parser
testListeners.getSearchResults().addAll(testList);
testList.clear(); // no longer needed
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();
// for each thread group, generate threads
// hand each thread the sampler controller
// and the listeners, and the timer
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()) {//for each setup thread group
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");
//wait for all Setup Threads To Exit
waitThreadsStopped();
log.info("All Setup Threads have ended");
groupCount=0;
JMeterContextService.clearTotalThreads();
}
groups.clear(); // The groups have all completed now
/*
* Here's where the test really starts. Run a Full GC now: it's no harm
* at all (just delays test start by a tiny amount) and hitting one too
* early in the test can impair results for short tests.
*/
JMeterUtils.helpGC();
JMeterContextService.getContext().setSamplingStarted(true);
boolean mainGroups = running; // still running at this point, i.e. setUp was not cancelled
while (running && iter.hasNext()) {// for each thread group
AbstractThreadGroup group = iter.next();
//ignore Setup and Post here. We could have filtered the searcher. but then
//future Thread Group objects wouldn't execute.
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();
}
} // end of thread groups
if (groupCount == 0){ // No TGs found
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");
}
}
//wait for all Test Threads To Exit
waitThreadsStopped();
groups.clear(); // The groups have all completed now
if (postIter.hasNext()){
groupCount = 0;
JMeterContextService.clearTotalThreads();
log.info("Starting tearDown thread groups");
if (mainGroups && !running) { // i.e. shutdown/stopped during main thread groups
running = tearDownOnShutdown; // re-enable for tearDown if necessary
}
while (running && postIter.hasNext()) {//for each setup thread group
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(); // wait for Post threads to stop
}
notifyTestListenersOfEnd(testListeners);
JMeterContextService.endTest();
if (JMeter.isNonGUI() && SYSTEM_EXIT_FORCED) {
log.info("Forced JVM shutdown requested at end of test");
System.exit(0); // NOSONAR Intentional
}
}
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) { // NOSONAR Reported by log
JMeterUtils.reportErrorToUser("Error occurred starting thread group :" + group.getName()+ ", error message:"+ex.getMessage()
+", \r\nsee log file for more details", ex);
return; // no point continuing
}
}
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(); // Fetch once; needs to stay constant
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();
// N.B. we don't wait for the thread to complete, as that would prevent parallel TGs
} 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);
}