其他测试框架 Jmeter 二次封装

孙高飞 · 2016年07月11日 · 最后由 鬼魅红儿 回复于 2019年05月23日 · 3115 次阅读
本帖已被设为精华帖!

前言

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

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 21 条回复 时间 点赞
ABEE ycwdaaaa (孙高飞) 在 TesterHome 的发帖整理 中提及了此贴 01月12日 13:47

我就想问下 jmeter 是怎么去涉及场景的 ,然后是怎么去分析 关键是怎么去分析

能和您单独联系么,求教。留个微信或者 QQ 号码。

JMeterUtils.loadJMeterProperties(Constant.getJmeterHome()
+ "/bin/jmeter.properties");

SaveService.loadProperties();

执行测试前,这两个地方需要加载配置文件 jmeter.properties 和 saveservice.properties,导致了你必须设置 jmeter 的 home。

解决办法就是,把这两个文件 copy 到项目目录下分别加载就可以了
JMeterUtils.loadJMeterProperties("jmeter.properties");
JMeterUtils.setProperty("saveservice_properties", "saveservice.properties");

同时,需要指定 jmeter 的 home 为空
JMeterUtils.setJMeterHome("");

看 SaveService 类的源码就知道,找 saveservice.properties 文件时,都是 JMeterUtils.getJMeterHome()
+ JMeterUtils.getPropDefault(SAVESERVICE_PROPERTIES,
SAVESERVICE_PROPERTIES_FILE) 这样的

不指为空的话,会报错:
java.nio.file.NoSuchFileException: nullsaveservice.properties

然而 jmeter plugin 这个轮子已经设计的很棒了

—— 来自 TesterHome 官方 安卓客户端

好贴,我回去研究一下

#16 楼 @success 那就要不就分几个机器那种,做成集群化的.要不就找个好点的机器哈哈

机器受不了吧

#14 楼 @success 在线程组里设置 10K 个并发线程就行了

高并发怎么做。模拟 10K 个用户并发访问

然而 JMeterPluginsCMD Command Line Tool 早已看穿了一切
http://jmeter-plugins.org/wiki/JMeterPluginsCMD/

java -jar CMDRunner.jar --tool Reporter \
  --generate-csv test.csv \
  --generate-png test.png --input-jtl results.jtl \
  --plugin-type ResponseTimesOverTime --width 800 --height 600

http://jmeter-plugins.org/

呵呵。。

#7 楼 @seveniruby 受教受教,原来还有这样的系统去画图

#9 楼 @htdx0101 额。。。我能说我也忘了么。。。好久之前的代码了。。。

请问楼主,maven 依赖里为什么要把 ApacheJMeter_config 剔除出去呢?

请问楼主,有这样的示例代码可以共享不啦?多谢啊😂

不错. 对 JMeter 的 api 介绍挺有用. 我的建议是画图这种事情尽量还是先把数据输出出来. 画图可以交给其他的工具或者 ganglia kibanna 这种系统去画图.

jmeter 二次开发很多,出来写文章的不错,楼主又来写精华帖了。

恒温 将本帖设为了精华贴 07月11日 20:56

#3 楼 @xuxtc 额。。。好吧

#2 楼 @ycwdaaaa 这让我想起了一个是不是该给测试脚本写测试的讨论..

#1 楼 @xuxtc 这些都不是真实的例子,其实我根本没调用接口,在测试脚本里随便写个 for 循环 1 万次的。。。 我离职了么。已经没有 java 接口让我调用了。。。所以结果看着有点怪怪的

感谢作者分享,好思路。
不过有个问题,就是最后"性能测试 - 时间"的图里面还没怎么看明白: 180 线程时,max time 差不多快到 80 了,min time 目测是 0,那 avg time 才不到 10?

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册