目前在做的压测平台,执行JMeter脚本,都是用户在本地通过GUI方式编辑生成,并且在关联好JAR和CSV能够成功执行的情况下,再将JMX,JAR和CSV作为测试用例的几要素关联起来,上传到平台指定的目录来完成调试,压测
流程很简单
这里有两个疑问
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都是类似的实现