Jmeter二次封装

本帖已被设为精华帖!,

前言

之前在老东家做性能测试的时候,一开始是使用LR的(服务端测试)。在LR里写vuser scripts 去进行RPC协议的接口性能测试。但是后来觉得LR实在太重了,一般的机器消受不了。而且无法自动化的驱动测试。所以后来引入了Jmeter。但是Jmeter也有它的缺点, 尤其是最后结果统计中那蛋疼的报表简直low到爆。所以临走前搞了一次二次封装。增强jmeter的特性,可惜只做了比较少的一部分我就离职了。 这里我写出来就当抛砖引玉了吧。

原理

其实原理十分简单,我们一般就是用Jmeter的GUI来完成工作么。现在我们不用GUI了,我们直接引用jmeter的核心lib,直接调用jmeter的API来执行我们的操作。你需要引入maven的依赖。如下

<dependency>
<groupId>com.lazerycode.jmeter</groupId>
<artifactId>jmeter-maven-plugin</artifactId>
<version>1.10.1</version>
<exclusions>
<exclusion>
<artifactId>ApacheJMeter_config</artifactId>
<groupId>org.apache.jmeter</groupId>
</exclusion>
</exclusions>
</dependency>

然后运行的时候,你还是需要装一个Jmeter的,这个我没绕过去。需要在代码中设置jmter的home。以及一些其他的配置信息

JMeterUtils.setJMeterHome(Constant.getJmeterHome());
JMeterUtils.loadJMeterProperties(Constant.getJmeterHome()
+ "/bin/jmeter.properties");
JMeterUtils.setProperty("jmeter.save.saveservice.output_format", "xml");
File log = new File(Constant.logFilePath() + "perfTest.log");
private static String jmeterHome = "E:\\apache-jmeter-2.13";//jmeter的home目录

代码部分

现在我们来看看核心代码部分吧

Logger.info("测试方法: "+caseInfo.getName());
Logger.info("起始并发数: "+caseInfo.getBeginThread());
Logger.info("递增并发数: "+caseInfo.getAddThread());
Logger.info("结束并发数: "+caseInfo.getEndThread());
Logger.info("每个线程的循环次数: "+caseInfo.getLoop());
//Logger.html_link("1111", "222");

JMeterUtils.setJMeterHome(Constant.getJmeterHome());
JMeterUtils.loadJMeterProperties(Constant.getJmeterHome()
+ "/bin/jmeter.properties");
JMeterUtils.setProperty("jmeter.save.saveservice.output_format", "xml");
File log = new File(Constant.logFilePath() + "perfTest.log");

JMeterUtils.setProperty(LoggingManager.LOG_FILE, log.getAbsolutePath());
JMeterUtils.initLogging();// you can comment this line out to see extra
// log messages of i.e. DEBUG level
JMeterUtils.initLocale();

// Initialize JMeter SaveService
SaveService.loadProperties();

JavaSampler javaSample = new JavaSampler();
javaSample.setClassname(caseInfo.getName());

// Loop Controller
LoopController loopController = new LoopController();
loopController.addTestElement(javaSample);
loopController.setLoops(caseInfo.getLoop());
loopController.setFirst(true);
loopController.initialize();

// Thread Group
List<ThreadGroup> threadGrouplist = this.setupThreadGroup(caseInfo, loopController);

// JMeter Test Plan, basic all u JOrphan HashTree
TestPlan testPlan = new TestPlan("My Test Plan");
testPlan.setSerialized(true);

// JMeter Test Plan, basic all u JOrphan HashTree
HashTree testPlanTree = new HashTree();

// Construct Test Plan from previously initialized elements
testPlanTree.add("TestPlan", testPlan);
testPlanTree.add("LoopController", loopController);
testPlanTree.add("JavaSampler", javaSample);

int count = 1000;
for(ThreadGroup threadGroup:threadGrouplist){
testPlanTree.add("ThreadGroup"+count,threadGroup);
count--;
}


// Store execution results
MyResultCollector requestCollector = new MyResultCollector();
requestCollector.setFilename(Constant.logFilePath()+"result.log");
testPlanTree.add(testPlanTree.getArray()[0], requestCollector);
// Run Test Plan
StandardJMeterEngine jmeterEngine = new StandardJMeterEngine();
jmeterEngine.configure(testPlanTree);
jmeterEngine.run();

Map<Integer,List<SampleResult>> sampleResultsMap = requestCollector.getSampleResults();
for (Object key : sampleResultsMap.keySet()) {
List<SampleResult> list = sampleResultsMap.get(key);
/*for(SampleResult s : list){
System.out.println(s.getTime()+"fffff");
}*/

System.out.println(list.size()+"kkkkkkkkkkk");

}
PerfChartHelper.createChart_Jmeter(1, sampleResultsMap,caseInfo.getName());

可以看到,我们在代码中设置循环,设置线程组,测试计划等等,熟悉jmeter的同学一定熟悉这些概念。其中我们测试的java的接口么。所以用的是javasimpler

JavaSampler javaSample = new JavaSampler();
javaSample.setClassname(caseInfo.getName());

其中需要给sample传一个测试类(如果是http接口,其实就不需要了)。下面我们看看这个测试类是怎么定义的。

public class TestP extends AbstractJavaSamplerClient {
private static AtomicInteger temp = new AtomicInteger();

@Override
public Arguments getDefaultParameters() {
System.out.println("===========init parameters ========");
Arguments arg = new Arguments();
return arg;
}
public SampleResult runTest(JavaSamplerContext paramJavaSamplerContext) {
SampleResult sr = new SampleResult();
sr.sampleStart();
//System.out.println(123);

//此处是调用逻辑

sr.setSuccessful(true);
sr.sampleEnd();
return sr;
}

}

可以看到我们只要继承jmeter定义的类AbstractJavaSamplerClient 就可以了。 jmeter也是有集合点这个概念的。即便没有,现在你都能调用它的API了,你自己写一个实现嵌入进去也行。然后我们看结果收集,这里我是自己进行了计算。

public class MyResultCollector extends ResultCollector {

private static final long serialVersionUID = -8648350950445938218L;

//private List<SampleResult> sampleResults;
private Map<Integer,List<SampleResult>> threadResultsMap;

public MyResultCollector() {
threadResultsMap = new HashMap<Integer,List<SampleResult>>();
}

@Override
public void sampleOccurred(SampleEvent e) {
super.sampleOccurred(e);
SampleResult r = e.getResult();
int threadGroupName = Integer.parseInt(e.getThreadGroup());
if(threadResultsMap.containsKey(threadGroupName)){
List<SampleResult> sampleResults = threadResultsMap.get(threadGroupName);
sampleResults.add(r);
}else{
List<SampleResult> sampleResults = new ArrayList<SampleResult>();
sampleResults.add(r);
threadResultsMap.put(threadGroupName, sampleResults);
}
}

public Map<Integer,List<SampleResult>> getSampleResults() {
return threadResultsMap;
}
}

首先是扩展Jmeter自己的结果收集器。

public static void createChart_Jmeter(int type, Map<Integer, List<SampleResult>> sampleResultsMap,String className) throws IOException {
ArrayList<double[]> tpsSeries = new ArrayList<double[]>();
ArrayList<double[]> averageSeries = new ArrayList<double[]>();
ArrayList<double[]> minSeries = new ArrayList<double[]>();
ArrayList<double[]> maxSeries = new ArrayList<double[]>();
ArrayList<double[]> time60 = new ArrayList<double[]>();
ArrayList<double[]> time90 = new ArrayList<double[]>();
ArrayList<double[]> time95 = new ArrayList<double[]>();

//用treeSet排序
TreeSet<Integer> tree = new TreeSet<Integer>();
for(int thread : sampleResultsMap.keySet()) {
tree.add(thread);
}

// 遍历每一个threadgroup的结果集,算出tps,平均相应时间并画图
for(int thread : tree) {
AggregatedParser aggregated = new AggregatedParser(sampleResultsMap.get(thread));

double tps = aggregated.getTps();
double averageTime = aggregated.getAverageTime();
long maxTime = aggregated.getMaxTime();
long minTime = aggregated.getMinTime();
long per60Time = aggregated.getPercentTime(0.6);
long per90Time = aggregated.getPercentTime(0.9);
long per95Time = aggregated.getPercentTime(0.95);
double errorRate = aggregated.getErrorRate();

tpsSeries.add(new double[]{thread, tps});
averageSeries.add(new double[]{thread, averageTime});
maxSeries.add(new double[]{thread, maxTime});
minSeries.add(new double[]{thread, minTime});
time60.add(new double[]{thread, per60Time});
time90.add(new double[]{thread, per90Time});
time95.add(new double[]{thread, per95Time});
Logger.info("在"+thread+"并发下错误率:"+errorRate);
}

Map<String, ArrayList<double[]>> timeMap = new HashMap<String, ArrayList<double[]>>();
timeMap.put("average time", averageSeries);
timeMap.put("min time", minSeries);
timeMap.put("max time", maxSeries);
timeMap.put("60% time", time60);
timeMap.put("90% time", time90);
timeMap.put("95% time", time95);


JfreeChart tpsChart = new JfreeChart("thread_tps", "thread", "tps");
tpsChart.addXYSeries("thread_tps_report", tpsSeries);
File folder = new File(Constant.imagePath()+className);
folder.mkdirs();
File imgTps = new File(Constant.imagePath()+className, "_tps.png");
tpsChart.createJfreeChartImage(imgTps, 800, 600);

//在html report中增加图片链接
//Logger.html_img(Constant.imagePath()+className+"/_tps.png");
//echarts_Qps(tpsSeries);


JfreeChart avgTimeChart = new JfreeChart("avgTime", "thread", "avg_time");
avgTimeChart.addXYSeries("thread_avgTime", averageSeries);
File imgAvgTime = new File(Constant.imagePath()+className, "_avg.png");
avgTimeChart.createJfreeChartImage(imgAvgTime, 800, 600);

//在html report中增加图片链接
//Logger.html_img(Constant.imagePath()+className+"/_avg.png");

//使用echart在html页面中花出tps和平均响应时间的图
Integer id = tempCount.getAndAdd(1);
Logger.html("<div id=\"chart_qps"+id+"\" style=\"width:800px;height:600px;\"></div>");
Logger.html("<div id=\"chart_time"+id+"\" style=\"width:800px;height:600px;\"></div>");
Logger.html("<script>");
echarts_Qps(tpsSeries,id);
echarts_Time(timeMap,id);
Logger.html("</script>");

然后增加结果收集算法。并画出图来。我分别用jfreechart和echart画图。

然后我们一下case控制

<suite name="rock" >
<paras>
<para host_ip="10.9.20.171"></para>
</paras>
<cases>
<case name="performance.TestP" run="false">
<perf begin_thread="100" end_thread="200" add_thread="20" loop="10"/>
<para desc="根据订单ID查询订单"></para>
</case>

<case name="performance.TestP" run="false">
<perf begin_thread="100" end_thread="200" add_thread="20" loop="10"/>
<para desc="根据订单ID查询订单"></para>
</case>

</cases>
</suite>

case里有我们想要控制的信息,例如起始的并发数,每次递增的并发数。结束的并发数等等。
其中name就是我们的测试类的全路径(我这里都是我自己写的demo类)。

效果图

现在我们来看看效果图吧,就是我们最后的结果
Jmeter二次封装

以及

Jmeter二次封装

结尾

我之前想实现更多的功能,例如可以写个连接池让Jmeter也可以像LR一样对数据库做参数化,加入时间控制等等。当初也就是按着LR的功能去设想的。只可惜才开始做就离职了。新公司是To B的业务,性能压力根本不在高并发上,就用不上这一套了。不知道老东家的小伙伴们有没有把这个想法发扬光大。当初希望的是把性能测试也加到CI中去,可惜我是没这个机会了。

* 注:本文来自网络投稿,不代表本站立场,如若侵犯版权,请及时知会删除