Jmeter源码分析(二)

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);
}
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值