平台化:JMeter脚本在线编辑初步实现

目前在做的压测平台,执行JMeter脚本,都是用户在本地通过GUI方式编辑生成,并且在关联好JAR和CSV能够成功执行的情况下,再将JMX,JAR和CSV作为测试用例的几要素关联起来,上传到平台指定的目录来完成调试,压测

流程很简单

NewImage

这里有两个疑问

1、用户必须本地调试好脚本执行无误,再进行上传

对于这点,表面上看起来JMX上传之后,上传JAR和CSV除了正常数据库CRUD操作之外,只需要修改JMX脚本里的CLASSPATH和CSV PATH,但是这里有一个问题,JAR的CLASSPATH好确定,一个JXM脚本就一个位置,递归找TestPlan.user_define_classpath即可,可是CSV却不一定,比如一个脚本里用户新建了很多个ThreadGroup,有些不用的都disable了,最后只有一个ThreadGroup里的某几个CSV配置是enable的,上传CSV文件,根本不清楚应该修改哪个位置的CSV配置,因此我当时的设计就是加了约束条件:

  • 本地脚本执行无误,确定了enable了哪些csv文件,就是需要进行上传的
  • JMeter脚本里CSV Data Set Config控件必须要设置Name,且和csv文件名一致,这样做的好处就是,无论你关联了多少个csv文件,每次上传压测平台通过enable+name就能定位到脚本csv配置的节点位置

每次上传找到了jar和csv的path,就可以修改平台指定的绝对路径,使得上传完之后又是一种可执行的关系

2、还要本地调试好了,再上传,就不能在线直接编辑,生成脚本吗?

这种想法肯定是对的,可一开始就这么做不现实,比如线程组就有ThreadGroup,SteppingThreadGroup,ConcurrencyThreadGroup等等,每个用户的使用习惯也不一样,然后Sample又有HTTP,Dubbo,Java等,也就是说你需要将GUI用户使用习惯的插件或者模块都搬到在线编辑当中,不太可能一下就能完成,所以我一直也没来做这个,相反1中这种上传的方式,可以管理任意的压测脚本,虽然看起来很麻烦,但实际上是一种很靠谱的方法

我们做平台首先应该是能帮我们方便干活,然后慢慢做稳定,最后再考虑哪些地方做得更完美,最近就在考虑这些能做得更完美的地方,完成了个脚本在线编辑的初版,大概流程如下

首先编辑页面分为几个模块,基础信息,线程组类型,Sample类型,断言,CSV模块等等;后续可能会持续扩展

对于数据库的CRUD比较简单,关键还要进行JMeter脚本的修改,这里用了dom4j,大致步骤读入基础JMeter脚本,定位找到要修改的Element,最后更新对应的DO属性值到脚本文件,大致如下

public static void main(String[] args) {
    JMeterXMLService jmx = new JMeterXMLService();
    jmx.init("jmx/thread_group_http.jmx");

    ThreadGroupDO threadGroupDO = new ThreadGroupDO();
    threadGroupDO.setDelayedStart(0);
    threadGroupDO.setDuration("30");
    threadGroupDO.setNumThreads("2");
    threadGroupDO.setLoops("-1");
    threadGroupDO.setRampTime("1");
    threadGroupDO.setSameUserOnNextIteration(1);
    threadGroupDO.setScheduler(1);
    jmx.writeJmxFile("jmx/out.jmx");
}

对于读文件和最后写文件,都直接用dom4j里的方法即可,唯一找到具体要修改的Element稍微麻烦点,这里的实现,有两种情况

唯一性比较好的Element,直接深度遍历地找即可,比如找Http Header的节点,位置

Element collectionProp =
        document.getRootElement()
                .element("hashTree")
                .element("hashTree")
                .element("HeaderManager")
                .element("collectionProp");

但是有的Element不太好找,比如上面某一个hashTree并列有好几个,就必须要广度遍历,再深度遍历,比较麻烦,但如果找的Element的name在脚本里比较唯一,那也好弄,直接递归找到,比如ThreadGroup在JMeter脚本里就长这样

<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
  <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
  <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
    <boolProp name="LoopController.continue_forever">false</boolProp>
    <stringProp name="LoopController.loops">1</stringProp>
  </elementProp>
  <stringProp name="ThreadGroup.num_threads">1</stringProp>
  <stringProp name="ThreadGroup.ramp_time">1</stringProp>
  <boolProp name="ThreadGroup.scheduler">true</boolProp>
  <stringProp name="ThreadGroup.duration"></stringProp>
  <stringProp name="ThreadGroup.delay"></stringProp>
  <boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
</ThreadGroup>

那么就直接递归找ThreadGroup(目前测试还没发现bug)

private void findElement(Element root, String name) {
    if (flag == 1) {
        return;
    }
    for (Element element : root.elements()) {
        if (element.getName().equals(name)) {
            dest = element;
            flag = 1;
            return;
        } else {
            findElement(element, name);
        }
    }
}

调用findElement(document.getRootElement(), “ThreadGroup”);就可以找到ThreadGroup这个Element,进而将ThreadGroupDO的内容更新到JMeter脚本里

编辑好了之后,提交,就可以看到用例关联的脚本

简单预览下,可以看到JMX脚本里和编辑内容一致,这里每次生成脚本的时候,会同时生成一个调试脚本,会修改压测脚本的线程组参数,目的是执行调试,达到不压测只执行一次的效果,有利于确认脚本是否执行有误

也可以直接把脚本下载到本地对比下,可以看到是一致的

那么可以直接平台调试一下,这里执行的就是调试脚本,得到返回结果

直接返回结果的方法是,JMeter的NoGUI方式加两行参数,就会直接返回执行结果

commandLine.addArgument("-Jjmeter.save.saveservice.output_format=xml");
commandLine.addArgument("-Jjmeter.save.saveservice.response_data=true");

结果很完美

同时,如果你想换一种压测方式,比如SteppingThreadGroup也是可以的,同样的道理,也会修改JMeter脚本文件为Stepping线程组

这样,一个简单的在线编辑就完成了,而且兼容上传的

同样后面的CSV,Assertions都是类似的实现

发表回复