前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JMeter5.1启动类JMeter代码分析

JMeter5.1启动类JMeter代码分析

原创
作者头像
天堂小说
发布2021-12-03 16:48:00
9570
发布2021-12-03 16:48:00
举报
文章被收录于专栏:JMeter源码分析

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();
        }
    }

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.概述
  • 2.代码解读
    • 2.1解析输入的args命令行参数
      • 2.2初始化Jmeter参数配置
        • 2.2.1 加载jmeter.properties文件
        • 2.2.2 加载user.properties文件
        • 2.2.3 加载system.properties文件
        • 2.2.4 解析其他命令行参数
      • 2.3启动jmeter的方式
        • 2.4命令行方式运行
          • 2.5运行runNonGui方法
            • 判断测试jmx文件是否存在
              • 解析测试jmx文件,并存放到HashTree树结构中
                • 克隆HashTree对象,并去除disable的对象
                  • 创建测试报告
                    • 执行JMeter引擎
                      • 2.6创建UDP后台socket常驻线程
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档