JMeter5.1启动类JMeter代码分析

2021-12-03 16:48:00 浏览数 (1)

1.概述

JMeter类通过Java反射的方式进行初始化并执行调用start()方法。

这里重点讲非GUI模式启动JMeter。

2.代码解读

2.1解析输入的args命令行参数

代码语言:txt复制
    public void start(String[] args) {
        // 解析输入的args命令行参数
        CLArgsParser parser = new CLArgsParser(args, options);
        log.info(args[0]);
        String error = parser.getErrorString();
        if (error == null){// Check option combinations
            boolean gui = parser.getArgumentById(NONGUI_OPT)==null;
            boolean nonGuiOnly = parser.getArgumentById(REMOTE_OPT)!=null
                               || parser.getArgumentById(REMOTE_OPT_PARAM)!=null
                               || parser.getArgumentById(REMOTE_STOP)!=null;
            if (gui && nonGuiOnly) {
                error = "-r and -R and -X are only valid in non-GUI mode";
            }
        }
         // 打印解析命令函参数时的报错信息
        if (null != error) {
            System.err.println("Error: "   error);//NOSONAR
            System.out.println("Usage");//NOSONAR
            System.out.println(CLUtil.describeOptions(options).toString());//NOSONAR
            // repeat the error so no need to scroll back past the usage to see it
            System.out.println("Error: "   error);//NOSONAR
            return;
        }

2.2初始化Jmeter参数配置

调用initializeProperties方法来初始化Jmeter参数,主要有以下两点:

  • 加载jmeter.properties文件的参数
  • 加载user.properties文件的参数
  • 加载system.properties文件的参数
  • 解析外部其他参数
代码语言:txt复制
        initializeProperties(parser); // Also initialises JMeter logging
2.2.1 加载jmeter.properties文件

优先加载命令行指定的外部的user.properties文件,找不到才会加载bin目录自带的jmeter.properties文件

代码语言:txt复制
        //加载外部properties文件,并存入全局变量
        if (parser.getArgumentById(PROPFILE_OPT) != null) {
            JMeterUtils.loadJMeterProperties(parser.getArgumentById(PROPFILE_OPT).getArgument());
        } else { //加载自带的properties文件,并存入全局变量
            JMeterUtils.loadJMeterProperties(NewDriver.getJMeterDir()   File.separator
                      "bin"   File.separator // $NON-NLS-1$
                      "jmeter.properties");// $NON-NLS-1$
        }
2.2.2 加载user.properties文件

从jmeter.properties文件获取user.properties文件名,读取该文件,然后将变量存入全局变量。

代码语言:txt复制
        String userProp = JMeterUtils.getPropDefault("user.properties",""); //$NON-NLS-1$
        if (userProp.length() > 0){ //$NON-NLS-1$
            File file = JMeterUtils.findFile(userProp);
            if (file.canRead()){
                try (FileInputStream fis = new FileInputStream(file)){
                    log.info("Loading user properties from: {}", file);
                    Properties tmp = new Properties();
                    tmp.load(fis);
                    // 存入全局变量中
                    jmeterProps.putAll(tmp);
                } catch (IOException e) {
                    log.warn("Error loading user property file: {}", userProp, e);
                }
            }
        }
2.2.3 加载system.properties文件

从jmeter.properties文件获取system.properties文件名,读取该文件,然后将变量存入系统变量。

代码语言:txt复制
        String sysProp = JMeterUtils.getPropDefault("system.properties",""); //$NON-NLS-1$
        if (sysProp.length() > 0){
            File file = JMeterUtils.findFile(sysProp);
            if (file.canRead()) {
                try (FileInputStream fis = new FileInputStream(file)){
                    log.info("Loading system properties from: {}", file);
                    System.getProperties().load(fis);
                } catch (IOException e) {
                    log.warn("Error loading system property file: {}", sysProp, e);
                } 
            }
        }
2.2.4 解析其他命令行参数
代码语言:txt复制
        // 获取命令行参数列表,并进行解析
        List<CLOption> clOptions = parser.getArguments();
        for (CLOption option : clOptions) {
            // 变量键名
            String name = option.getArgument(0);
            // 变量值
            String value = option.getArgument(1);
            
            switch (option.getDescriptor().getId()) {

            // Should not have any text arguments
            case CLOption.TEXT_ARGUMENT:
                throw new IllegalArgumentException("Unknown arg: "   option.getArgument());

            // 加载其他jmeter属性文件
            case PROPFILE2_OPT: // Bug 33920 - allow multiple props
                log.info("Loading additional properties from: {}", name);
                try (FileInputStream fis = new FileInputStream(new File(name))){
                    Properties tmp = new Properties();
                    tmp.load(fis);
                    jmeterProps.putAll(tmp);
                } catch (FileNotFoundException e) { // NOSONAR
                    log.warn("Can't find additional property file: {}", name, e);
                } catch (IOException e) { // NOSONAR
                    log.warn("Error loading additional property file: {}", name, e);
                }
                break;

            // 加载外部其他系统属性文件
            case SYSTEM_PROPFILE:
                log.info("Setting System properties from file: {}", name);
                try (FileInputStream fis = new FileInputStream(new File(name))){
                    System.getProperties().load(fis);
                } catch (IOException e) { // NOSONAR
                    if (log.isWarnEnabled()) {
                        log.warn("Cannot find system property file. {}", e.getLocalizedMessage());
                    }
                }
                break;

            // 获取外部其他系统属性参数
            case SYSTEM_PROPERTY:
                if (value.length() > 0) { // Set it
                    log.info("Setting System property: {}={}", name, value);
                    System.getProperties().setProperty(name, value);
                } else { // Reset it
                    log.warn("Removing System property: {}", name);
                    System.getProperties().remove(name);
                }
                break;

            // 获取外部其他JMeter属性参数
            case JMETER_PROPERTY:
                if (value.length() > 0) { // Set it
                    log.info("Setting JMeter property: {}={}", name, value);
                    jmeterProps.setProperty(name, value);
                } else { // Reset it
                    log.warn("Removing JMeter property: {}", name);
                    jmeterProps.remove(name);
                }
                break;
            
            // 定义全局属性或属性文件          
            case JMETER_GLOBAL_PROP:
                if (value.length() > 0) { // Set it
                    log.info("Setting Global property: {}={}", name, value);
                    remoteProps.setProperty(name, value);
                } else {
                    File propFile = new File(name);
                    if (propFile.canRead()) {
                        log.info("Setting Global properties from the file {}", name);
                        try (FileInputStream fis = new FileInputStream(propFile)){
                            remoteProps.load(fis);
                        } catch (FileNotFoundException e) { // NOSONAR
                            if (log.isWarnEnabled()) {
                                log.warn("Could not find properties file: {}", e.getLocalizedMessage());
                            }
                        } catch (IOException e) { // NOSONAR
                            if (log.isWarnEnabled()) {
                                log.warn("Could not load properties file: {}", e.getLocalizedMessage());
                            }
                        } 
                    }
                }
                break;

            // 定义日志级别
            case LOGLEVEL:
                if (value.length() > 0) { // Set category
                    log.info("LogLevel: {}={}", name, value);
                    final Level logLevel = Level.getLevel(value);
                    if (logLevel != null) {
                        String loggerName = name;
                        if (name.startsWith("jmeter") || name.startsWith("jorphan")) {
                            loggerName = PACKAGE_PREFIX   name;
                        }
                        Configurator.setAllLevels(loggerName, logLevel);
                    } else {
                        log.warn("Invalid log level, '{}' for '{}'.", value, name);
                    }
                } else { // Set root level
                    log.warn("LogLevel: {}", name);
                    final Level logLevel = Level.getLevel(name);
                    if (logLevel != null) {
                        Configurator.setRootLevel(logLevel);
                    } else {
                        log.warn("Invalid log level, '{}', for the root logger.", name);
                    }
                }
                break;
            
            // 测试结束时退出远程服务器(CLI模式)          
            case REMOTE_STOP:
                remoteStop = true;
                break;
            
            // 开始测试前,强制删除现有结果文件和Web报告文件夹(如果存在)的标志       
            case FORCE_DELETE_RESULT_FILE:
                deleteResultFile = true;
                break;
            default:
                // ignored
                break;
            }
        }

2.3启动jmeter的方式

根据不同的命令参数,判断JMeter的启动方式,作为master(client端)还是作为slave(server端)

代码语言:txt复制
            // 打印版本并退出,用于CLI模式
            if (parser.getArgumentById(VERSION_OPT) != null) {
                displayAsciiArt();

            // 打印帮助信息并退出
            } else if (parser.getArgumentById(HELP_OPT) != null) {
                displayAsciiArt();
                System.out.println(JMeterUtils.getResourceFileAsText("org/apache/jmeter/help.txt"));//NOSONAR $NON-NLS-1$
            
            // 打印命令行选项并退出
            } else if (parser.getArgumentById(OPTIONS_OPT) != null) {
                displayAsciiArt();
                System.out.println(CLUtil.describeOptions(options).toString());//NOSONAR
            
            // 运行服务器,作为slave模式
            } else if (parser.getArgumentById(SERVER_OPT) != null) {
                // Start the server
                try {
                    // 启动server服务
                    RemoteJMeterEngineImpl.startServer(RmiUtils.getRmiRegistryPort());
                    startOptionalServers();
                } catch (Exception ex) {
                    System.err.println("Server failed to start: " ex);//NOSONAR
                    log.error("Giving up, as server failed with:", ex);
                    throw ex;
                }
            
            // 运行client,作为master模式
            } else {
                String testFile=null;
                // 获取测试jmx文件
                CLOption testFileOpt = parser.getArgumentById(TESTFILE_OPT);
                if (testFileOpt != null){
                    testFile = testFileOpt.getArgument();
                    if (USE_LAST_JMX.equals(testFile)) {
                        testFile = LoadRecentProject.getRecentFile(0);// most recent
                    }
                }

                // 获取测试报告文件
                CLOption testReportOpt = parser.getArgumentById(REPORT_GENERATING_OPT);
                if (testReportOpt != null) { // generate report from existing file
                    String reportFile = testReportOpt.getArgument();
                    // 创建测试报告文件
                    extractAndSetReportOutputFolder(parser, deleteResultFile);
                    // 生成测试报告
                    ReportGenerator generator = new ReportGenerator(reportFile, null);
                    generator.generate();
                } else if (parser.getArgumentById(NONGUI_OPT) == null) { // not non-GUI => GUI
                   // 图形界面方式运行JMeter
                    startGui(testFile);
                    startOptionalServers();
                } else { // NON-GUI must be true

                    // 创建测试报告文件
                    extractAndSetReportOutputFolder(parser, deleteResultFile);
                    
                    // 远程启动服务器时,确定使用的host是命令行参数还是系统配置的
                    CLOption rem = parser.getArgumentById(REMOTE_OPT_PARAM);
                    if (rem == null) {
                        rem = parser.getArgumentById(REMOTE_OPT);
                    }

                    // 生成log日志文件
                    CLOption jtl = parser.getArgumentById(LOGFILE_OPT);
                    String jtlFile = null;
                    if (jtl != null) {
                        jtlFile = processLAST(jtl.getArgument(), ".jtl"); // $NON-NLS-1$
                    }

                    CLOption reportAtEndOpt = parser.getArgumentById(REPORT_AT_END_OPT);
                    if(reportAtEndOpt != null && jtlFile == null) {
                        throw new IllegalUserActionException(
                                "Option -"  ((char)REPORT_AT_END_OPT) " requires -" ((char)LOGFILE_OPT )  " option");
                    }

                     // 命令行方式运行JMeter
                    startNonGui(testFile, jtlFile, rem, reportAtEndOpt != null);
                    startOptionalServers();
                }
            }

2.4命令行方式运行

这里主要讲命令行方式启动JMeter,通过调用startNonGui方法。

代码语言:txt复制
private void startNonGui(String testFile, String logFile, CLOption remoteStart, boolean generateReportDashboard)
            throws IllegalUserActionException, ConfigurationException {
        // add a system property so samplers can check to see if JMeter
        // is running in NonGui mode
        System.setProperty(JMETER_NON_GUI, "true");// $NON-NLS-1$
        JMeter driver = new JMeter();// TODO - why does it create a new instance?
        driver.remoteProps = this.remoteProps;  // 远程配置参数对象
        driver.remoteStop = this.remoteStop;    // 测试结束时退出远程服务器的标志
        driver.deleteResultFile = this.deleteResultFile;
        
        PluginManager.install(this, false);

        // 解析远程服务器IP参数列表
        String remoteHostsString = null;
        if (remoteStart != null) {
            remoteHostsString = remoteStart.getArgument();
            // 从自带的配置文件获取
            if (remoteHostsString == null) {
                remoteHostsString = JMeterUtils.getPropDefault(
                        "remote_hosts", //$NON-NLS-1$
                        "127.0.0.1");//NOSONAR $NON-NLS-1$ 
            }
        }
        if (testFile == null) {
            throw new IllegalUserActionException("Non-GUI runs require a test plan");
        }
        driver.runNonGui(testFile, logFile, remoteStart != null, remoteHostsString, generateReportDashboard);
    }

2.5运行runNonGui方法

判断测试jmx文件是否存在

代码语言:txt复制
       File f = new File(testFile);
       if (!f.exists() || !f.isFile()) {
            println("Could not open "   testFile);
            return;
       }

解析测试jmx文件,并存放到HashTree树结构中

代码语言:txt复制
      HashTree tree = SaveService.loadTree(f);

克隆HashTree对象,并去除disable的对象

代码语言:txt复制
      HashTree clonedTree = convertSubTree(tree, true);

创建测试报告

代码语言:txt复制
    Summariser summariser = null;
    String summariserName = JMeterUtils.getPropDefault("summariser.name", "");//$NON-NLS-1$
    if (summariserName.length() > 0) {
        log.info("Creating summariser <{}>", summariserName);
        println("Creating summariser <"   summariserName   ">");
        summariser = new Summariser(summariserName);
    }
    ResultCollector resultCollector = null; //测试结果收集器
    if (logFile != null) {
        resultCollector = new ResultCollector(summariser);
        resultCollector.setFilename(logFile);
        clonedTree.add(clonedTree.getArray()[0], resultCollector);
    }
    else {
        // only add Summariser if it can not be shared with the ResultCollector
        if (summariser != null) {
            clonedTree.add(clonedTree.getArray()[0], summariser);
        }
    }

    // 删除现有测试报告文件夹
    if (deleteResultFile) {
        SearchByClass<ResultCollector> resultListeners = new SearchByClass<>(ResultCollector.class);
        clonedTree.traverse(resultListeners);
        Iterator<ResultCollector> irc = resultListeners.getSearchResults().iterator();
        while (irc.hasNext()) {
            ResultCollector rc = irc.next();
            File resultFile = new File(rc.getFilename());
            if (resultFile.exists() && !resultFile.delete()) {
                throw new IllegalStateException("Could not delete results file "   resultFile.getAbsolutePath()
                      "(canRead:" resultFile.canRead() ", canWrite:" resultFile.canWrite() ")");
            }
        }
    }
    ReportGenerator reportGenerator = null;
    if (logFile != null && generateReportDashboard) {
        reportGenerator = new ReportGenerator(logFile, resultCollector);
    }

    clonedTree.add(clonedTree.getArray()[0], new ListenToTest(remoteStart && remoteStop ? engines : null, reportGenerator));

执行JMeter引擎

代码语言:txt复制
    // 单机执行JMeter引擎
    if (!remoteStart) {
    	//启动jmeter引擎
        JMeterEngine engine = new StandardJMeterEngine();
        engine.configure(clonedTree);
        long now=System.currentTimeMillis();
        println("Starting the test @ " new Date(now) " (" now ")");
        engine.runTest();
        engines.add(engine);
    } else {  // 分布式执行JMeter引擎
        java.util.StringTokenizer st = new java.util.StringTokenizer(remoteHostsString, ",");//$NON-NLS-1$
        List<String> hosts = new LinkedList<>();
        while (st.hasMoreElements()) {
            hosts.add((String) st.nextElement());
        }
        
        DistributedRunner distributedRunner=new DistributedRunner(this.remoteProps);
        distributedRunner.setStdout(System.out); // NOSONAR
        distributedRunner.setStdErr(System.err); // NOSONAR
        distributedRunner.init(hosts, clonedTree);
        engines.addAll(distributedRunner.getEngines());
        distributedRunner.start();
    }

    // 将jmeter引擎加入Udp后台常驻进程列表中
    startUdpDdaemon(engines);

2.6创建UDP后台socket常驻线程

代码语言:txt复制
    private static void startUdpDdaemon(final List<JMeterEngine> engines) {
        // 获取UDP连接默认端口号,默认从user.properties文件中获取,如果为空,则使用默认的端口号
        int port = JMeterUtils.getPropDefault("jmeterengine.nongui.port", UDP_PORT_DEFAULT); // $NON-NLS-1$

        // 获取UDP连接最大端口号
        int maxPort = JMeterUtils.getPropDefault("jmeterengine.nongui.maxport", 4455); // $NON-NLS-1$
        if (port > 1000){
            // 建立UDP的socket连接
            final DatagramSocket socket = getSocket(port, maxPort);
            if (socket != null) {
                // 创建线程
                Thread waiter = new Thread("UDP Listener"){
                    @Override
                    public void run() {
                        waitForSignals(engines, socket);
                    }
                };
                // 将进程设置为常驻线程
                waiter.setDaemon(true);
                // 启动线程
                waiter.start();
            } else {
                System.out.println("Failed to create UDP port");//NOSONAR
            }
        }
    }

    // 接收shutDown、stopTestNow等脚本建立的UDP连接
    private static void waitForSignals(final List<JMeterEngine> engines, DatagramSocket socket) {
        byte[] buf = new byte[80];
        System.out.println("Waiting for possible Shutdown/StopTestNow/HeapDump/ThreadDump message on port " socket.getLocalPort());//NOSONAR
        DatagramPacket request = new DatagramPacket(buf, buf.length);
        try {
            while(true) {
                // 接收到UDP的socket请求
                socket.receive(request);
                InetAddress address = request.getAddress();
                // Only accept commands from the local host
                if (address.isLoopbackAddress()){
                    String command = new String(request.getData(), request.getOffset(), request.getLength(),"ASCII");
                    System.out.println("Command: " command " received from " address);//NOSONAR
                    log.info("Command: {} received from {}", command, address);
                    switch(command) {
                        case "StopTestNow" :
                            for(JMeterEngine engine : engines) {
                                engine.stopTest(true);
                            }
                            break;
                        case "Shutdown" :
                            for(JMeterEngine engine : engines) {
                                engine.stopTest(false);
                            }
                            break;
                        case "HeapDump" :
                            HeapDumper.dumpHeap();
                            break;
                        case "ThreadDump" :
                            ThreadDumper.threadDump();
                            break;
                        default:
                            System.out.println("Command: " command " not recognised ");//NOSONAR                            
                    }
                }
            }
        } catch (Exception e) {
            System.out.println(e);//NOSONAR
        } finally {
            socket.close();
        }
    }

0 人点赞