1. 程式人生 > >JMeter二次封裝之原始碼解析初步

JMeter二次封裝之原始碼解析初步

       近日,正在做一個壓測平臺,主要功能是想整合壓力機資源,通過一個統一的web平臺,建立和管理壓測任務,並展示包括實時壓測結果、壓力機效能資料、應用伺服器效能資料等資料的效能測試報告。

  本系統的核心是對JMeter進行二次封裝,從而實現web版的JMeter平臺。針對其中的實現原理,會逐一通過文章記錄。本文主要是對JMeter的原始碼進行初步解析。

  我們以非GUI模式執行JMeter為例,瞭解下JMeter的執行機制。首先我們找到入口類NewDriver,程式碼如下。

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 { Class<?> initialClass = loader.loadClass("org.apache.jmeter.JMeter"
);// $NON-NLS-1$ Object instance = initialClass.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); } } }

  很明顯,這裡是通過反射呼叫JMeter類的start方法。接下來我們看下start方法,如下。

    public void start(String[] args) {
        CLArgsParser parser = new CLArgsParser(args, options);
        ···
        initializeProperties(parser); 
        ···
        startNonGui(testFile, jtlFile, rem, reportAtEndOpt != null);
        ···
    }
private void startNonGui(String testFile, String logFile, CLOption remoteStart, boolean generateReportDashboard)
            throws IllegalUserActionException, ConfigurationException {
        ···
        driver.runNonGui(testFile, logFile, remoteStart != null, remoteHostsString, generateReportDashboard);
}
private void runNonGui(String testFile, String logFile, boolean remoteStart, String remoteHostsString, boolean generateReportDashboard) {
    ···
    File f = new File(testFile);
    ···
    HashTree tree = SaveService.loadTree(f);
    ···
    JMeterEngine engine = new StandardJMeterEngine();
    engine.configure(tree);

    engine.runTest();
    ···
}

  再來看下JMeter類,拋開GUI和Remote test相關的程式碼,簡單說,JMeter做的事情主要有

  1. 解析命令列引數,載入配置檔案;

  2. 將測試檔案(.jmx檔案)解析成HashTree;

  3. 建立一個StandardJMeterEngine,並把測試的工作交給JMeterEngine;

  當然,JMeter類還有其他重要的職責,比如監聽所有的JMeterEngine,當接收到GUI的StopTestNow/Shutdown等命令時候來呼叫JMeterEngine相應的方法。

  接下來研究下JMeterEngine,看下這個介面都提供哪些方法:

public interface JMeterEngine {
    /**
     * 配置引擎
     */
    void configure(HashTree testPlan);
    /**
     * 執行測試
     */
    void runTest() throws JMeterEngineException;
    /**
     * 停止測試,立即打斷當前取樣器
     */
    default void stopTest() {
        stopTest(true);
    }
    /**
     * 停止測試,根據引數是否立即打斷當前取樣器
     */
    void stopTest(boolean now);
    /**
     * 停止測試執行
     */
    void reset();
    /**
     * 設定引擎屬性
     */
    void setProperties(Properties p);
    /**
     * 退出引擎
     */
    void exit();
    /**
     * 引擎是否活躍
     */
    boolean isActive();
}

  JMeterEngine依賴於HashTree,而HashTree是由jmx檔案解析而來,熟悉JMeter的同學都知道,每一個JMeter測試計劃都會對應一個jmx檔案。所以我們只要生成合理的jmx檔案,就可以通過JMeterEngine去執行測試任務。

  具體jmx檔案的生成方式,我們可以借鑑JMeter GUI模式下jmx檔案生成方式。在這裡我們的處理方式是,先定義每個元件的生成方式,然後再按一定結構組裝各個元件,示意程式碼如下。

public class HashTreeMaker {
    public static TestPlan testPlan(TestPlanDTO testPlanDTO) {
        TestPlan tp = new TestPlan();
        tp.setName(testPlanDTO.getCaseName());
        tp.setProperty(TestElement.TEST_CLASS, "TestPlan");
        tp.setProperty(TestElement.GUI_CLASS, "TestPlanGui");
        tp.setProperty(TestElement.ENABLED, true);
        tp.setComment("");
        tp.setFunctionalMode(false);
        tp.setSerialized(false);

        Arguments arguments = new Arguments();
        arguments.setName("使用者定義的變數");
        arguments.setProperty(TestElement.TEST_CLASS, "Arguments");
        arguments.setProperty(TestElement.GUI_CLASS, "ArgumentsPanel");
        arguments.setProperty(TestElement.ENABLED, true);

        tp.setUserDefinedVariables(arguments);

        return tp;
    }
    ···
}
private HashTree createHashTree(LoadTestCaseDTO loadTestCaseDTO) {
        TestPlan testPlan = HashTreeMaker.testPlan(loadTestCaseDTO.getTestPlanDTO());
        ListedHashTree tree = new ListedHashTree();
        tree.add(testPlan);
        for (ThreadGroupDTO threadGroupDTO : loadTestCaseDTO.getThreadGroupDTOList()) {
            HashTree groupTree = new HashTree();
            ThreadGroup threadGroup = HashTreeMaker.threadGroup(threadGroupDTO);
            groupTree.add(threadGroup);

            for (HttpSamplerDTO httpSamplerDTO : threadGroupDTO.getHttpSamplerDTOList()) {
                HashTree samplerTree = new HashTree();
                HTTPSamplerProxy sampler = HashTreeMaker.httpSamplerProxy(httpSamplerDTO);
                samplerTree.add(sampler);

                HashTree resultTree = new HashTree();
                ResultCollector resultCollector = HashTreeMaker.resultCollector();
                resultTree.add(resultCollector);

                samplerTree.add(samplerTree.getArray()[0], resultTree);
                groupTree.add(groupTree.getArray()[0], samplerTree);
            }

            HashTree resultTree = new HashTree();
            ResultCollector resultCollector = HashTreeMaker.resultCollector();
            resultTree.add(resultCollector);

            groupTree.add(groupTree.getArray()[0], resultTree);

            HashTree backendListenerTree = new HashTree();
            BackendListener backendListener = HashTreeMaker.backendListener(influxdbUrl, loadTestCaseDTO.getCaseId());
            backendListenerTree.add(backendListener);
            groupTree.add(groupTree.getArray()[0], backendListenerTree);

            tree.add(tree.getArray()[0], groupTree);
        }
        return tree;
    }

  知道以上原理,我們就可以對JMeter進行二次封裝,不過如果要執行成功,除了引入JMeter的jar外,還需要載入jmeter.properties和saveservice.properties兩個配置檔案,如下。

JMeterUtils.loadJMeterProperties(new ClassPathResource("jmeter/jmeter.properties").getURL().getPath());
            JMeterUtils.setProperty("saveservice_properties", new ClassPathResource("jmeter/saveservice.properties").getURL().getPath());
            JMeterUtils.setJMeterHome("");
            JMeterUtils.initLocale();

  水平有限,JMeter原始碼的初步解析到此結束。