浅析metersphere如何调用jmeter作为测试引擎的

目录

说明

为了简单,我们使用metersphere快速调试功能的接口( /run/debug)作为分析对象,因为底层调用逻辑应该是类似的,如下操作
在这里插入图片描述

在Servcie中的核心代码就是ApiExecuteService的debug中如下两个方法。请求转为Jmeter可以执行的dto对象。然后执行。

     //转为jemter可执行的对象。主要是里面的hashtree
     JmeterRunRequestDTO runRequest = this.initRunRequest(request, bodyFiles);
      // 开始执行
     jMeterService.run(runRequest);

request转为JmeterRunRequestDTO 的核心

其核心就是将request转为org.apache.jorphan.collections.HashTree。这个是jmeter实际执行的对象的api。我们可以在ApiExecuteService的initRunRequest方法中看到。

 HashTree hashTree = request.getTestElement().generateHashTree(config);
   
   .......
 JmeterRunRequestDTO runRequest = new JmeterRunRequestDTO(testId, request.getId(), runMode, hashTree);
   

核心就在HashTree。这个HashTree 一定是在jmeter中可以运行的hashtree。

request.getTestElement() 从源码看。返回的是一个MsTestElement。但实际是其子类MsTestPlan。

只是子类没有重写父类的generateHashTree方法所以调用的依然是父类的generateHashTree方法。但是调用的是子类实现toHashTree是子类实现,也就是MsTestPlan的toHashTree方法

  public HashTree generateHashTree(MsParameter config) {
        HashTree jmeterTestPlanHashTree = new ListedHashTree();
        //MsTestPlan的toHashTree方法,并且调用MsTestPlan的hashTree这个字段,看下面json结构
        this.toHashTree(jmeterTestPlanHashTree, this.hashTree, config);
        return jmeterTestPlanHashTree;
    }

那么为啥request.getTestElement() 是子类实现呢?回到请求。似乎在传入转为request对象时候就已经转为子类了

{
  "id": "93438da6",
  "testElement": {
    "id": "01c812da-60ea-6cfb-b8cd-0291615cd435",
    "type": "TestPlan",
    "name": "TestPlan",
    "enabled": true,
    "hashTree": [{
      "id": "c62c7828-7ea5-ffad-a07c-b44c525f566f",
      "type": "ThreadGroup",
      "name": "ThreadGroup",
      "enabled": true,
      "onSampleError": true,
      "clazzName": "io.metersphere.api.dto.definition.request.MsThreadGroup",
      "hashTree": [{
        "id": "3db381ae-be7f-fe14-b7ba-57599a9dc626",
        "type": "HTTPSamplerProxy",
        "name": "75b7dbd5",
        "enabled": true,
        "$type": "Sampler",
        "protocol": "HTTP",
        "method": "GET",
        "autoRedirects": false,
        "followRedirects": true,
        "useKeepalive": true,
        "doMultipartPost": false,
        "connectTimeout": 60000,
        "responseTimeout": 60000,
        "body": {
          "type": "KeyValue",
          "kvs": [],
          "binary": []
        },
        "arguments": [{
          "value": null,
          "type": "text",
          "files": null,
          "enable": true,
          "uuid": "28c76",
          "contentType": "text/plain",
          "description": null,
          "file": false,
          "isEdit": false,
          "max": null,
          "min": null,
          "required": false,
          "urlEncode": false,
          "valid": false
        }],
        "rest": [],
        "files": [],
        "headers": [{
          "name": "",
          "value": "",
          "enable": true
        }],
        "hashTree": [{
          "resourceId": "ffbb1004-8c36-462b-8702-65844b976da6",
          "type": "Assertions",
          "text": [],
          "regex": [],
          "jsonPath": [],
          "jsr223": [],
          "xpath2": [],
          "duration": {
            "type": "Duration"
          },
          "enable": true,
          "document": {
            "type": "JSON",
            "data": {
              "xmlFollowAPI": false,
              "jsonFollowAPI": false,
              "json": [],
              "xml": []
            },
            "enable": true
          },
          "id": "731c1739-6381-1a45-cd3c-f33c9f5a3871",
          "clazzName": "io.metersphere.api.dto.definition.request.assertions.MsAssertions"
        }],
        "preSize": 0,
        "postSize": 0,
        "ruleSize": 0,
        "url": "www.baidu.com",
        "projectId": "1e0a6c4c-b274-11ed-ba08-00155da5ea2b",
        "clazzName": "io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy"
      }]
    }],
    "clazzName": "io.metersphere.api.dto.definition.request.MsTestPlan"
  },
  "projectId": "1e0a6c4c-b274-11ed-ba08-00155da5ea2b",
  "environmentMap": null,
  "bodyUploadIds": [],
  "requestId": "3db381ae-be7f-fe14-b7ba-57599a9dc626",
  "editCaseRequest": false,
  "debug": true,
  "name": "www.baidu.com",
  "reportId": "93438da6"
}

这里应该是通过MsTestElement上的@JsonTypeInfo进行了反序列化

@JsonTypeInfo(
    use = Id.CLASS,
    include = As.EXISTING_PROPERTY,
    property = "clazzName"
)
@JsonIgnoreProperties(
    ignoreUnknown = true
)
public abstract class MsTestElement {

}

回到this.toHashTree这一行,我们看下mstestPaln是如何处理的。又调每个子类的toHashTree。包括MsThreadGroup等。

    @Override
    public void toHashTree(HashTree tree, List<MsTestElement> hashTree, MsParameter msParameter) {
        ParameterConfig config = (ParameterConfig) msParameter;
        final HashTree testPlanTree = tree.add(getPlan());
        if (CollectionUtils.isNotEmpty(hashTree)) {
            hashTree.forEach(el -> {
                el.toHashTree(testPlanTree, el.getHashTree(), config);
            });
        }
    }

如MsThreadGroup的toHashTree

    @Override
    public void toHashTree(HashTree tree, List<MsTestElement> hashTree, MsParameter msParameter) {
        ParameterConfig config = (ParameterConfig) msParameter;
        final HashTree groupTree = tree.add(getThreadGroup());
        if ((config != null && config.isEnableCookieShare()) || enableCookieShare) {
            CookieManager cookieManager = new CookieManager();
            cookieManager.setProperty(TestElement.TEST_CLASS, CookieManager.class.getName());
            cookieManager.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("CookiePanel"));
            cookieManager.setEnabled(true);
            cookieManager.setName("CookieManager");
            cookieManager.setClearEachIteration(false);
            cookieManager.setControlledByThread(false);
            groupTree.add(cookieManager);
        }

        if (CollectionUtils.isNotEmpty(hashTree)) {
            for (MsTestElement el : hashTree) {
                el.toHashTree(groupTree, el.getHashTree(), config);
            }
            if (!config.isOperating()) {
                MsDebugSampler el = new MsDebugSampler();
                el.setName(RunningParamKeys.RUNNING_DEBUG_SAMPLER_NAME);
                el.toHashTree(groupTree, el.getHashTree(), config);
            }
        }
    }

所以从这里我们大概理解了,request如何转hashtree的基本逻辑。就是通过子类的toHashTree去处理。怎么处理呢。每个子类不太相同。但是有个基本的两个操作就是tree.add,上面第4行的操作。此处的tree。实际是父节点的hashree对象。然后添加了新的Jmeter中的测试对象。作为key。存入到一个map中。


    public HashTree add(Object key) {
        if (!data.containsKey(key)) {
            HashTree newTree = createNewTree();
            data.put(key, newTree);
            return newTree;
        }
        return getTree(key);
    }



我们来分析下hashtree到底是一个什么样子的结构
在这里插入图片描述
我们可以看下hashtree实际是实现了map接口的。

总结

如果我们想要将自己的对象转换成Hashtree对象。实际就是参考MsTestElement所有实现子类如何实现toHashTree这个方法的,这里应该都对应了Jmeter的组件对象的转换。

如何运行的

当前接口走的是else if分支,通过通读,应是调用的jmeter的原生api执行测试的。runNode有待下次解读

    public void run(JmeterRunRequestDTO request) {
        if (request.getPool().isPool() && StringUtils.isNotBlank(request.getRunMode())) {
            this.runNode(request);
        } else if (request.getHashTree() != null) {
            //大概就是替换变量
            ElementUtil.coverArguments(request.getHashTree());
            //解析hashTree,是否含有文件库文件?有待研究
            HashTreeUtil.initRepositoryFiles(request);
            execThreadPoolExecutor.addTask(request);
        }
    }

    public void addTask(JmeterRunRequestDTO requestDTO) {
        ExecTask task = new ExecTask(requestDTO);
        threadPool.execute(task);
        outApiThreadPoolExecutorLogger("报告:[" + requestDTO.getReportId() + "] 资源:[" + requestDTO.getTestId() + "] 加入执行队列");
    }
public class ExecTask implements Runnable {
    private JmeterRunRequestDTO request;

    public ExecTask(JmeterRunRequestDTO request) {
        this.request = request;
    }

    public JmeterRunRequestDTO getRequest() {
        return this.request;
    }

    @Override
    public void run() {
        CommonBeanFactory.getBean(JMeterService.class).addQueue(request);
        Object res = PoolExecBlockingQueueUtil.take(request.getReportId());
        if (res == null && !JMeterThreadUtils.isRunning(request.getReportId(), request.getTestId())) {
            LoggerUtil.info("任务执行超时", request.getReportId());
        }
    }
}
  public void addQueue(JmeterRunRequestDTO request) {
        this.runLocal(request);
    }
    private void runLocal(JmeterRunRequestDTO request) {
        init();
        // 接口用例集成报告/测试计划报告日志记录
        if (StringUtils.isNotEmpty(request.getTestPlanReportId())
                && StringUtils.equals(request.getReportType(), RunModeConstants.SET_REPORT.toString())) {
            FixedCapacityUtil.put(request.getTestPlanReportId(), new StringBuffer(""));
        } else {
            // 报告日志记录
            FixedCapacityUtil.put(request.getReportId(), new StringBuffer(""));
        }
        LoggerUtil.debug("监听MessageCache.tasks当前容量:" + FixedCapacityUtil.size());
        if (request.isDebug() && !StringUtils.equalsAny(request.getRunMode(), ApiRunMode.DEFINITION.name())) {
            LoggerUtil.debug("为请求 [ " + request.getReportId() + " ] 添加同步接收结果 Listener");
            JMeterBase.addBackendListener(request, request.getHashTree(), MsApiBackendListener.class.getCanonicalName());
        }

        if (MapUtils.isNotEmpty(request.getExtendedParameters())
                && request.getExtendedParameters().containsKey(ExtendedParameter.SYNC_STATUS)
                && (Boolean) request.getExtendedParameters().get(ExtendedParameter.SYNC_STATUS)) {
            LoggerUtil.debug("为请求 [ " + request.getReportId() + " ] 添加Debug Listener");
            addDebugListener(request.getReportId(), request.getHashTree(), request.getRunMode());
        }

        if (request.isDebug()) {
            LoggerUtil.debug("为请求 [ " + request.getReportId() + " ] 添加Debug Listener");
            addDebugListener(request.getReportId(), request.getHashTree(), request.getRunMode());
        } else {
            LoggerUtil.debug("为请求 [ " + request.getReportId() + " ] 添加同步接收结果 Listener");
            JMeterBase.addBackendListener(request, request.getHashTree(), MsApiBackendListener.class.getCanonicalName());
        }

        LoggerUtil.info("资源:[" + request.getTestId() + "] 加入JMETER中开始执行", request.getReportId());
        LocalRunner runner = new LocalRunner(request.getHashTree());
        runner.run(request.getReportId());
    }

核心应该在 runner.run(request.getReportId())。大概就是启动了一个本地Runner启动。

    public void run(String report) {
        //应该就是调用了jmeter自身的api启动。
        StandardJMeterEngine engine = new StandardJMeterEngine();
        engine.configure(jmxTree);
        try {
            LoggerUtil.info("LocalRunner 开始执行报告",report);
            engine.runTest();
        } catch (JMeterEngineException e) {
            engine.stopTest(true);
        }
    }

其核心有ms-jemter-core提供。可以看出改包应该从jmeter源码的core抽取了engine等相关代码

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
可以通过Java代码调用JMeter实现自动化压力测试。以下是一个简单的示例代码,可以启动JMeter并运行一个测试计划: ``` import org.apache.jmeter.engine.StandardJMeterEngine; import org.apache.jmeter.reporters.ResultCollector; import org.apache.jmeter.reporters.Summariser; import org.apache.jmeter.samplers.SampleSaveConfiguration; import org.apache.jmeter.testelement.TestPlan; import org.apache.jmeter.threads.SetupThreadGroup; import org.apache.jmeter.util.JMeterUtils; public class JMeterTest { public static void main(String[] args) throws Exception { //设置JMeter Home路径 JMeterUtils.setJMeterHome("C:/apache-jmeter-5.4.1"); //初始化JMeter JMeterUtils.loadJMeterProperties("C:/apache-jmeter-5.4.1/bin/jmeter.properties"); JMeterUtils.initLocale(); //创建JMeter引擎 StandardJMeterEngine jmeter = new StandardJMeterEngine(); //创建测试计划 TestPlan testPlan = new TestPlan("My Test Plan"); //创建线程组 SetupThreadGroup threadGroup = new SetupThreadGroup(); threadGroup.setNumThreads(10); threadGroup.setRampUp(10); threadGroup.setSamplerController(testPlan); //设置结果保存配置 SampleSaveConfiguration saveConfig = new SampleSaveConfiguration(); saveConfig.setAsXml(true); saveConfig.setXmlPi(true); //添加结果收集器 Summariser summariser = new Summariser(); ResultCollector resultCollector = new ResultCollector(summariser); resultCollector.setSaveConfig(saveConfig); testPlan.add(testPlan.getArray()[0], resultCollector); //将测试计划添加到JMeter引擎jmeter.configure(testPlan); jmeter.run(); } } ``` 这段代码会启动JMeter,并运行一个包含10个线程的测试计划,每个线程在10秒内逐步启动,并发送测试请求。测试结果将保存为XML格式。可以根据需要修改代码以满足自己的测试需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值