性能工具之JMeter5.0核心源码浅析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 【5月更文挑战第14天】性能工具之JMeter5.0核心源码浅析

一、引言

周末无聊看了下 JMeter5.0 的源码,本文就当做个小结,水平有限,如有理解不到位或错误的的地方,望大家指出,谢谢。

源码下载地址:
https://github.com/apache/jmeter
废话不多说,下面进入正题~

二、源码结构

1、工程目录

image.png

2、源码目录

image.png

三、源码分析

1、运行机制

image.png

  • HashTree 是 JMeter 执行测试依赖的数据结构,在执行测试之前进行配置测试数据,HashTree将数据组织到一个递归树结构中,并提供了操作该结构的方法

  • StandardJMeterEngine 执行JMeter 测试 ,直接用于本地 GUI 和非 GUI 调用,或者在服务器模式下运行时由 RemoteJMeterEngineImpl 启动

  • JMeterEngine 接口被运行 JMeter的测试类实现,此接口共8个方法,JMeterEngine本质就是一个线程。

2、代码分析

此处以非GUI模式运行JMeter为例,了解下JMeter的运行机制。首先我们找到入口类 NewDriver。

image.png

main 方法代码如下:

 /**
     * The main program which actually runs JMeter.
     * mian方法
     * @param args
     *            the command line arguments
     */
    public static void main(String[] args) {
   
   
        if(!EXCEPTIONS_IN_INIT.isEmpty()) {
   
   
            System.err.println("Configuration error during init, see exceptions:"+exceptionsToString(EXCEPTIONS_IN_INIT));
        } else {
   
   
            Thread.currentThread().setContextClassLoader(loader);

            setLoggingProperties(args);

            try {
   
   
                // 加载JMeter类
                Class<?> initialClass = loader.loadClass("org.apache.jmeter.JMeter");// $NON-NLS-1$
                // 获取JMeter类实例
                Object instance = initialClass.getDeclaredConstructor().newInstance();
                // 获取start方法类型实例
                Method startup = initialClass.getMethod("start", new Class[] {
   
    new String[0].getClass() });// $NON-NLS-1$
                // 反射调用JMeter类的start方法
                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);
            }
        }
    }

很明显,这里是通过反射调用 JMeter 类的 start 方法。
接下来我们看下 start 方法

 /**
     * Takes the command line arguments and uses them to determine how to
     * startup JMeter.
     * 根据命令行执行不同的操作
     * 主要功能为:1)startgui 2)startnogui
     *
     * Called reflectively by {@link NewDriver#main(String[])}
     * @param args The arguments for 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 {
   
   
            // 初始化配置,同时初始化JMeter日志
            initializeProperties(parser); // Also initialises JMeter logging

            Thread.setDefaultUncaughtExceptionHandler(
                    (Thread t, Throwable e) -> {
   
   
                    if (!(e instanceof ThreadDeath)) {
   
   
                        log.error("Uncaught exception: ", e);
                        System.err.println("Uncaught Exception " + e + ". 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("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, false);
                    ReportGenerator generator = new ReportGenerator(reportFile, null);
                    generator.generate();
                } else if (parser.getArgumentById(NONGUI_OPT) == null) {
   
    // not non-GUI => GUI
                    // 在GUI模式下执行
                    startGui(testFile);
                    startOptionalServers();
                } else {
   
    // NON-GUI must be true 必须为无GUI模式
                    extractAndSetReportOutputFolder(parser, deleteResultFile);

                    CLOption rem = parser.getArgumentById(REMOTE_OPT_PARAM);
                    if (rem == null) {
   
   
                        rem = 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");
                    }
                    // 无GUI执行
                    startNonGui(testFile, jtlFile, rem, 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);
        }
    }

start 方法主要还是根据命令执行不同的启动方法
无 GUI 方法启动

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

GUI方法启动

 /**
     * Starts up JMeter in GUI mode
     * JMeter GUI启动
     */
    private void startGui(String testFile) {
   
   
        System.out.println("================================================================================");//NOSONAR
        System.out.println("Don't use GUI mode for load testing !, only for Test creation and Test debugging.");//NOSONAR
        System.out.println("For load testing, use NON GUI Mode:");//NOSONAR
        System.out.println("   jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder]");//NOSONAR
        System.out.println("& increase Java Heap to meet your test requirements:");//NOSONAR
        System.out.println("   Modify current env variable HEAP=\"-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m\" in the jmeter batch file");//NOSONAR
        System.out.println("Check : https://jmeter.apache.org/usermanual/best-practices.html");//NOSONAR
        System.out.println("================================================================================");//NOSONAR

        // 设置加载进度条  splash.setProgress(10/30/60/90/100)
        SplashScreen splash = new SplashScreen();
        splash.showScreen();
        String jMeterLaf = LookAndFeelCommand.getJMeterLaf();
        try {
   
   
            log.info("Setting LAF to: {}", jMeterLaf);
            UIManager.setLookAndFeel(jMeterLaf);
        } catch (Exception ex) {
   
   
            log.warn("Could not set LAF to: {}", jMeterLaf, ex);
        }
        splash.setProgress(10);
        JMeterUtils.applyHiDPIOnFonts();
        PluginManager.install(this, true);

        JMeterTreeModel treeModel = new JMeterTreeModel();
        splash.setProgress(30);
        JMeterTreeListener treeLis = new JMeterTreeListener(treeModel);
        final ActionRouter instance = ActionRouter.getInstance();
        instance.populateCommandMap(); //这个方法会去寻找<project>/lib/ext 下所有的jar
        splash.setProgress(60);
        treeLis.setActionHandler(instance);
        GuiPackage.initInstance(treeLis, treeModel);
        splash.setProgress(80);
        MainFrame main = new MainFrame(treeModel, treeLis);
        splash.setProgress(100);
        ComponentUtil.centerComponentInWindow(main, 80);
        main.setLocationRelativeTo(splash);
        main.setVisible(true);
        main.toFront();
        instance.actionPerformed(new ActionEvent(main, 1, ActionNames.ADD_ALL));
        if (testFile != null) {
   
   
            try {
   
   
                File f = new File(testFile);
                log.info("Loading file: {}", f);
                FileServer.getFileServer().setBaseForScript(f);

                HashTree tree = SaveService.loadTree(f);

                GuiPackage.getInstance().setTestPlanFile(f.getAbsolutePath());

                Load.insertLoadedTree(1, tree);
            } catch (ConversionException e) {
   
   
                log.error("Failure loading test file", e);
                JMeterUtils.reportErrorToUser(SaveService.CEtoString(e));
            } catch (Exception e) {
   
   
                log.error("Failure loading test file", e);
                JMeterUtils.reportErrorToUser(e.toString());
            }
        } else {
   
   
            JTree jTree = GuiPackage.getInstance().getMainFrame().getTree();
            TreePath path = jTree.getPathForRow(0);
            jTree.setSelectionPath(path);
            FocusRequester.requestFocus(jTree);
        }
        splash.setProgress(100);
        splash.close();
    }

在来看下 runNonGui 方法,主要功能是执行脚本。

// run test in batch mode  批处理运行测试
    private void runNonGui(String testFile, String logFile, boolean remoteStart, String remoteHostsString, boolean generateReportDashboard) {
   
   
        try {
   
   
            // 获取脚本文件
            File f = new File(testFile);
            if (!f.exists() || !f.isFile()) {
   
   
                println("Could not open " + testFile);
                return;
            }
            // 设置脚本文件
            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
            // 将测试文件(.jmx文件)解析成HashTree
            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<>();
            clonedTree.add(clonedTree.getArray()[0], new ListenToTest(remoteStart && remoteStop ? engines : null, reportGenerator));
            println("Created the tree successfully using "+testFile);
            if (!remoteStart) {
   
   
                // 实例化一个JMeterEngine来对付脚本,JMeterEngine本质就是一个线程
                JMeterEngine engine = new StandardJMeterEngine();
                engine.configure(clonedTree);
                long now=System.currentTimeMillis();
                println("Starting the test @ "+new Date(now)+" ("+now+")");
                // 调用runTest方法
                engine.runTest();
                engines.add(engine);
            } else {
   
   
                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();
            }
            startUdpDdaemon(engines);
        } catch (Exception e) {
   
   
            System.out.println("Error in NonGUIDriver " + e.toString());//NOSONAR
            log.error("Error in NonGUIDriver", e);
        }
    }

整体再来梳理下JMeter类逻辑,抛开 GUI 和 Remote test相关的代码,简单说,JMeter 做的事情主要有:

  • 解析命令行参数,加载配置文件;
  • 将 .Jmx 文件解析成 HashTree
  • 实例化一个StandardJMeterEngine,并把测试的工作交给JMeterEngine;

当然,JMeter类还有其他重要的职责,比如监听所有的 JMeterEngine ,当接收到 GUI 的 StopTestNow / Shutdown 等命令时候来调用JMeterEngine接口相应的方法。

接下来看下JMeterEngine,看下这个接口都提供哪些方法?


/**
 * This interface is implemented by classes that can run JMeter tests.
 */
public interface JMeterEngine {
   
   
    /**
     * Configure engine
     * 配置引擎
     * @param testPlan the test plan
     */
    void configure(HashTree testPlan);

    /**
     * Runs the test
     * 执行测试
     * @throws JMeterEngineException if an error occurs
     */
    void runTest() throws JMeterEngineException;

    /**
     * Stop test immediately interrupting current samplers
     * 停止测试,立即打断当前samplers
     */
    default void stopTest() {
   
   
        stopTest(true);
    }
    /**
     * 停止测试,根据参数是否立即打断当前samplers
     * @param now boolean that tell wether stop is immediate (interrupt) or not (wait for current sample end)
     */
    void stopTest(boolean now);

    /**
     * Stop test if running
     * 停止测试运行
     */
    void reset();

    /**
     * set Properties on engine
     * 设置引擎属性
     * @param p the properties to set
     */
    void setProperties(Properties p);

    /**
     * Exit engine
     * 退出引擎
     */
    void exit();

    /**
     * 引擎是否活跃
     * @return boolean Flag to show whether engine is active (true when test is running). Set to false at end of test
     */
    boolean isActive();
}

JMeterEngine 依赖于 HashTree,而 HashTree 是由 jmx 文件解析而来,每一个 JMeter 测试计划都会对应一个 jmx 文件。所以我们只要生成合理的 jmx 文件,就可以通过 JMeterEngine 压测引擎去执行测试任务。

具体 jmx 文件的生成方式,我们可以借鉴JMeter GUI模式下 jmx 文件生成方式。在这里我们的演示的处理方式是,先定义每个组件的生成方式,然后再按一定结构组装各个组件,示意代码如下。

四、JAVA运行JMeter示例

遵循以下规则:

  • 将JMeter文件安装在某个地方
  • 在项目lib或者JMeter安装的/ lib/ext文件夹中获取所需的 JMeter jar包。

JMeter的“压测引擎”就是 StandardJMeterEngine ,我们需要扩展此类或实现自己的JMeterEngine接口。
示例生成并读取.jmx文件并执行它,代码如下:
pom.xml引包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zuozewei</groupId>
    <artifactId>jmeter-from-code</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.zuozewei.demo.JMeterFromScratch</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.jmeter</groupId>
            <artifactId>ApacheJMeter_java</artifactId>
            <version>4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.jmeter</groupId>
            <artifactId>ApacheJMeter_http</artifactId>
            <version>4.0</version>
        </dependency>
    </dependencies>


</project>

测试类

public class JMeterFromScratch {
   
   

    public static void main(String[] argv) throws Exception {
   
   
        // 设置jmeterHome路径
        String jmeterHome1 = "/Users/apple/Downloads/performance/apache-jmeter-4.0";
        //File jmeterHome = new File(System.getProperty("jmeter.home"));
        File jmeterHome = new File(jmeterHome1);
        String slash = System.getProperty("file.separator");

        if (jmeterHome.exists()) {
   
   
            File jmeterProperties = new File(jmeterHome.getPath() + slash + "bin" + slash + "jmeter.properties");
            if (jmeterProperties.exists()) {
   
   
                //JMeter Engine 引擎
                StandardJMeterEngine jmeter = new StandardJMeterEngine();

                //JMeter initialization (properties, log levels, locale, etc)
                JMeterUtils.setJMeterHome(jmeterHome.getPath());
                JMeterUtils.loadJMeterProperties(jmeterProperties.getPath());
                JMeterUtils.initLogging();// you can comment this line out to see extra log messages of i.e. DEBUG level
                JMeterUtils.initLocale();


                // JMeter Test Plan, basically JOrphan HashTree
                HashTree testPlanTree = new HashTree();

                // 第一个 HTTP Sampler - 打开 baidu.com
                HTTPSamplerProxy examplecomSampler = new HTTPSamplerProxy();
                examplecomSampler.setDomain("baidu.com");
                examplecomSampler.setPort(80);
                examplecomSampler.setPath("/");
                examplecomSampler.setMethod("GET");
                examplecomSampler.setName("Open baidu.com");
                examplecomSampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName());
                examplecomSampler.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName());


                // 第二个 HTTP Sampler - 打开 qq.com
                HTTPSamplerProxy blazemetercomSampler = new HTTPSamplerProxy();
                blazemetercomSampler.setDomain("qq.com");
                blazemetercomSampler.setPort(80);
                blazemetercomSampler.setPath("/");
                blazemetercomSampler.setMethod("GET");
                blazemetercomSampler.setName("Open qq.com");
                blazemetercomSampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName());
                blazemetercomSampler.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName());


                // Loop Controller 循环控制
                LoopController loopController = new LoopController();
                loopController.setLoops(1);
                loopController.setFirst(true);
                loopController.setProperty(TestElement.TEST_CLASS, LoopController.class.getName());
                loopController.setProperty(TestElement.GUI_CLASS, LoopControlPanel.class.getName());
                loopController.initialize();

                // Thread Group 线程组
                ThreadGroup threadGroup = new ThreadGroup();
                threadGroup.setName("Example Thread Group");
                threadGroup.setNumThreads(1);
                threadGroup.setRampUp(1);
                threadGroup.setSamplerController(loopController);
                threadGroup.setProperty(TestElement.TEST_CLASS, ThreadGroup.class.getName());
                threadGroup.setProperty(TestElement.GUI_CLASS, ThreadGroupGui.class.getName());

                // Test Plan 测试计划
                TestPlan testPlan = new TestPlan("Create JMeter Script From Java Code");
                testPlan.setProperty(TestElement.TEST_CLASS, TestPlan.class.getName());
                testPlan.setProperty(TestElement.GUI_CLASS, TestPlanGui.class.getName());
                testPlan.setUserDefinedVariables((Arguments) new ArgumentsPanel().createTestElement());

                // Construct Test Plan from previously initialized elements
                // 从以上初始化的元素构造测试计划
                testPlanTree.add(testPlan);
                HashTree threadGroupHashTree = testPlanTree.add(testPlan, threadGroup);
                threadGroupHashTree.add(blazemetercomSampler);
                threadGroupHashTree.add(examplecomSampler);

                // save generated test plan to JMeter's .jmx file format
                // 将生成的测试计划保存为JMeter的.jmx文件格式
                SaveService.saveTree(testPlanTree, new FileOutputStream(jmeterHome + slash + "example.jmx"));

                //add Summarizer output to get test progress in stdout like:
                // 在stdout中添加summary输出,得到测试进度,如:
                // summary =      2 in   1.3s =    1.5/s Avg:   631 Min:   290 Max:   973 Err:     0 (0.00%)
                Summariser summer = null;
                String summariserName = JMeterUtils.getPropDefault("summariser.name", "summary");
                if (summariserName.length() > 0) {
   
   
                    summer = new Summariser(summariserName);
                }


                // Store execution results into a .jtl file
                // 将执行结果存储到.jtl文件中
                String logFile = jmeterHome + slash + "example.jtl";
                ResultCollector logger = new ResultCollector(summer);
                logger.setFilename(logFile);
                testPlanTree.add(testPlanTree.getArray()[0], logger);

                // Run Test Plan
                // 执行测试计划
                jmeter.configure(testPlanTree);
                jmeter.run();

                System.out.println("Test completed. See " + jmeterHome + slash + "example.jtl file for results");
                System.out.println("JMeter .jmx script is available at " + jmeterHome + slash + "example.jmx");
                System.exit(0);

            }
        }

        System.err.println("jmeter.home property is not set or pointing to incorrect location");
        System.exit(1);


    }
}

测试结果

summary =      2 in 00:00:03 =    0.8/s Avg:   951 Min:   933 Max:   969 Err:     0 (0.00%)
Test completed. See /Users/apple/Downloads/performance/apache-jmeter-4.0/example.jtl file for results
JMeter .jmx script is available at /Users/apple/Downloads/performance/apache-jmeter-4.0/example.jmx

Process finished with exit code 0

本文源码地址:

目录
相关文章
|
2月前
|
测试技术 数据库 UED
Python 性能测试进阶之路:JMeter 与 Locust 的强强联合,解锁性能极限
【9月更文挑战第9天】在数字化时代,确保软件系统在高并发场景下的稳定性至关重要。Python 为此提供了丰富的性能测试工具,如 JMeter 和 Locust。JMeter 可模拟复杂请求场景,而 Locust 则能更灵活地模拟真实用户行为。结合两者优势,可全面评估系统性能并优化瓶颈。例如,在电商网站促销期间,通过 JMeter 模拟大量登录请求并用 Locust 模拟用户浏览和购物行为,可有效识别并解决性能问题,从而提升系统稳定性和用户体验。这种组合为性能测试开辟了新道路,助力应对复杂挑战。
95 2
|
3月前
|
测试技术 持续交付 Apache
性能怪兽来袭!Python+JMeter+Locust,让你的应用性能飙升🦖
【8月更文挑战第5天】随着互联网应用规模增长,性能测试至关重要。本文介绍如何利用Python结合Apache JMeter和Locust构建高效可定制的性能测试框架。JMeter广泛用于负载测试,通过模拟大量虚拟用户并发访问来评估性能。Locust基于Python,通过编写简单脚本模拟HTTP请求,特别适合Web应用测试,比JMeter更灵活易扩展。Python作为胶水语言简化测试脚本编写并流畅自动化流程。文章提供JMeter命令行测试和Locust脚本示例,并展示如何用Python自动化执行和整合测试结果,最终帮助应用在高负载下稳定运行。
86 1
|
16天前
|
测试技术 持续交付 Apache
性能怪兽来袭!Python+JMeter+Locust,让你的应用性能飙升🦖
【10月更文挑战第10天】随着互联网应用规模的不断扩大,性能测试变得至关重要。本文将探讨如何利用Python结合Apache JMeter和Locust,构建高效且可定制的性能测试框架。通过介绍JMeter和Locust的使用方法及Python的集成技巧,帮助应用在高负载下保持稳定运行。
55 2
|
24天前
|
测试技术 持续交付 Apache
性能怪兽来袭!Python+JMeter+Locust,让你的应用性能飙升🦖
【10月更文挑战第2天】随着互联网应用规模的不断膨胀,性能测试变得至关重要。本文将介绍如何利用Python结合Apache JMeter和Locust构建高效且可定制的性能测试框架。Apache JMeter是一款广泛使用的开源负载测试工具,适合测试静态和动态资源;Locust则基于Python,通过编写简单的脚本模拟HTTP请求,更适合复杂的测试场景。
46 3
|
2月前
|
缓存 Java 测试技术
谷粒商城笔记+踩坑(11)——性能压测和调优,JMeter压力测试+jvisualvm监控性能+资源动静分离+修改堆内存
使用JMeter对项目各个接口进行压力测试,并对前端进行动静分离优化,优化三级分类查询接口的性能
谷粒商城笔记+踩坑(11)——性能压测和调优,JMeter压力测试+jvisualvm监控性能+资源动静分离+修改堆内存
|
3月前
|
存储 Linux 数据库
性能工具之JMeter + Grafana + InfluxDB 性能平台搭建
【8月更文挑战第7天】性能工具之JMeter + Grafana + InfluxDB 性能平台搭建
65 1
性能工具之JMeter + Grafana + InfluxDB 性能平台搭建
|
3月前
|
监控 Java 测试技术
实战派必看!Python性能测试中,JMeter与Locust如何助力性能调优
【8月更文挑战第6天】性能优化是软件开发的关键。本文介绍JMeter与Locust两款流行性能测试工具,演示如何用于Python应用的性能调优。JMeter可模拟大量用户并发访问,支持多种协议;Locust用Python编写,易于定制用户行为并模拟高并发。根据场景选择合适工具,确保应用在高负载下的稳定运行。
126 4
|
3月前
|
测试技术 数据库 UED
Python 性能测试进阶之路:JMeter 与 Locust 的强强联合,解锁性能极限
【8月更文挑战第6天】在数字化时代,确保软件在高并发下的稳定性至关重要。Python 提供了强大的性能测试工具,如 JMeter 和 Locust。JMeter 可配置复杂请求场景,而 Locust 则以 Python 脚本灵活模拟真实用户行为。两者结合,可全面评估系统性能。例如,对电商网站进行测试时,JMeter 模拟登录请求,Locust 定义浏览和购物行为,共同揭示系统瓶颈并指导优化,从而保证稳定高效的用户体验。
100 1
|
4月前
|
测试技术 Linux
linux 服务器运行jmeter 进行服务性能压测
linux 服务器运行jmeter 进行服务性能压测
311 0
|
5月前
|
测试技术 Windows
软件测试之 性能测试 性能测试基础指标 Loadrunner、Jmeter等工具(下)
软件测试之 性能测试 性能测试基础指标 Loadrunner、Jmeter等工具(下)
69 2