性能测试工具 性能测试工具 nGrinder 项目剖析及二次开发

胡刚 · 2016年02月26日 · 最后由 okeymen 回复于 2023年09月27日 · 8396 次阅读
本帖已被设为精华帖!

0.背景

组内需要一款轻量级的性能测试工具,之前考虑过 LR(太笨重,单实例,当然它的地位是不容置疑的),阿里云的 PTS(https://pts.aliyun.com/lite/index.htm, 仅支持阿里云内网和公网机器),Gatling(http://gatling.io/#/) 没有 TPS 数据等等,不太适合我们。

nGrinderr 是 NAVER(韩国最大互联网公司 NHN 旗下搜索引擎网站)开源的性能测试工具,直接部署成 web 服务,支持多用户使用,可扩展性好,可自定义 plugin(http://www.cubrid.org/wiki_ngrinder/entry/how-to-develop-plugin),wiki 文档较丰富 (http://www.cubrid.org/wiki_ngrinder/entry/ngrinder-devzone),数据及图形化展示满足需求;但是展示的统计数据较简单,二次开发整合数据:TPS 标准差,TPS 波动率,最小/大 RT,RT 25/50/75/80/85/90/95/99 百分位数字段,并将这些数据展示在详细测试报告页中。

1.项目剖析

1-1. nGrinder 架构

nGrinder 是一款在一系列机器上执行 Groovy 或 JPython 测试脚本的应用,内部引擎是基于 Grinder。
架构图:
这里写图片描述

层级图:
这里写图片描述

默认的 NGRINDER_HOME 为/root/.ngrinder, 大多是配置文件和数据文件。

这里写图片描述

目录/root/.ngrinder/perftest/0_999 下,以每个 test_id 为名的文件夹对应的存储了执行性能测试时的采样数据:
这里写图片描述

*.data 文件就是执行性能测试时对应的各种性能采样数据,性能测试详细报告页就是根据这些 data 文件,进行图形化展示(ajax)。

nGrinder 包含 2 大组件:
1)Controller
为性能测试提供 web interface
协同测试进程
收集和显示测试数据
新建和修改脚本

2)Agent
agent mode: 运行进程和线程,压测目标服务
monitor mode: 监控目标系统性能 (cpu/memory), 可以自定义收集的数据 (比如 jvm 数据)

http://www.cubrid.org/wiki_ngrinder/entry/general-architecture

1-2. 技术栈

1) Controller 层
FreeMarker: 基于 Java 的模板引擎
Spring Security
Spring Mvc:Spring MVC provides rich functionality for building robust web applications.
GSon
SVNKit Dav

2) Service 层
Grinder
Spring
EhCache: Ehcache has excellent Spring integration.

3) Data 层
Spring Data
Hibernate:Hibernate is a powerful technology for persisting data,and it is Spring Data back-end within nGrinder.
H2: (nGrinder 默认使用该 DB)
Cubrid:(nGrinder 同一家公司的 DB)
Liquidase: Liquibase is an open source that automates database schema updates.
SVNKit

http://www.cubrid.org/wiki_ngrinder/entry/technology-stack

2.源码实现

需求:在详细测试报告页中展示 TPS 标准差,TPS 波动率,最小/大 RT,RT 25/50/75/80/85/90/95/99 百分位数这些数据。

修改 Controller 层,增加数据处理业务逻辑 (计算 TPS 标准差,TPS 波动率,最小/大 RT,RT 25/50/75/80/85/90/95/99 百分位数)

在获取采样数据
ngrinder-core/src/main/java/net/grinder/SingleConsole.java 中新增处理业务逻辑,核心修改代码片段:

    // tps list
    List<Double> tps = new CopyOnWriteArrayList<Double>();
    // rt list
    List<Double> meanTestTime = new CopyOnWriteArrayList<Double>();

    /**
     * 
     * 每次请求调用一次 Build up statistics for current sampling.
     *
     * @param accumulatedStatistics
     *            intervalStatistics
     * @param intervalStatistics
     *            accumulatedStatistics
     */
    protected void updateStatistics(StatisticsSet intervalStatistics,
            StatisticsSet accumulatedStatistics) {
        Map<String, Object> result = newHashMap();
        result.put("testTime", getCurrentRunningTime() / 1000);
        List<Map<String, Object>> cumulativeStatistics = new ArrayList<Map<String, Object>>();
        List<Map<String, Object>> lastSampleStatistics = new ArrayList<Map<String, Object>>();

        for (Test test : accumulatedStatisticMapPerTest.keySet()) {
            Map<String, Object> accumulatedStatisticMap = newHashMap();
            Map<String, Object> intervalStatisticsMap = newHashMap();
            StatisticsSet accumulatedSet = this.accumulatedStatisticMapPerTest
                    .get(test);
            StatisticsSet intervalSet = this.intervalStatisticMapPerTest
                    .get(test);

            accumulatedStatisticMap.put("testNumber", test.getNumber());
            accumulatedStatisticMap.put("testDescription",
                    test.getDescription());
            intervalStatisticsMap.put("testNumber", test.getNumber());
            intervalStatisticsMap.put("testDescription", test.getDescription());
            // When only 1 test is running, it's better to use the parametrized
            // snapshot.
            for (Entry<String, StatisticExpression> each : getExpressionEntrySet()) {
                if (INTERESTING_STATISTICS.contains(each.getKey())) {
                    accumulatedStatisticMap.put(
                            each.getKey(),
                            getRealDoubleValue(each.getValue().getDoubleValue(
                                    accumulatedSet)));
                    intervalStatisticsMap.put(
                            each.getKey(),
                            getRealDoubleValue(each.getValue().getDoubleValue(
                                    intervalSet)));
                }
            }
            cumulativeStatistics.add(accumulatedStatisticMap);
            lastSampleStatistics.add(intervalStatisticsMap);
        }

        Map<String, Object> totalStatistics = newHashMap();

        for (Entry<String, StatisticExpression> each : getExpressionEntrySet()) {
            if (INTERESTING_STATISTICS.contains(each.getKey())) {
                totalStatistics.put(each.getKey(), getRealDoubleValue(each
                        .getValue().getDoubleValue(accumulatedStatistics)));
            }
        }

        LOGGER.debug("hugang start get plug data");

        // 获取tps, rt集合
        for (Entry<String, StatisticExpression> each : getExpressionEntrySet()) {
            if ("TPS".equals(each.getKey())) {
                tps.add((Double) getRealDoubleValue(each.getValue()
                        .getDoubleValue(intervalStatistics)));
            } else if ("Mean_Test_Time_(ms)".equals(each.getKey())) {
                meanTestTime.add((Double) getRealDoubleValue(each.getValue()
                        .getDoubleValue(intervalStatistics)));
            }
        }


        result.put("totalStatistics", totalStatistics);
        result.put("cumulativeStatistics", cumulativeStatistics);
        result.put("lastSampleStatistics", lastSampleStatistics);
        result.put("tpsChartData", getTpsValues());
        result.put("peakTpsForGraph", this.peakTpsForGraph);
        synchronized (this) {
            result.put(GrinderConstants.P_PROCESS, this.runningProcess);
            result.put(GrinderConstants.P_THREAD, this.runningThread);
            result.put("success", !isAllTestFinished());
        }
        // Finally overwrite.. current one.
        this.statisticData = result;
    }

    /**
     * 从updateStatistics()累加数据, list :rt 和 tps, 为成员变量
     * 
     * 再处理集合,放到statisticData中
     * 
     * @author hugang
     */
    public void getPlusResult(){

        LOGGER.debug("hugang getPlusResult() tpslist {}  rtlist is {}",
                tps.toString(), meanTestTime.toString());

        int i = 0;
        int j = 0;
        // list转成数组, 标准库使用数组作为参数
        double[] tpsArray = new double[tps.size()];
        for (double tpsNum : tps) {
            tpsArray[i++] = tpsNum;
        }

        // list转成数组
        double[] meanTestTimeArray = new double[meanTestTime.size()];
        for (double meanTime : meanTestTime) {
            meanTestTimeArray[j++] = meanTime;
        }

        // tps 标准差
        double tpsStd = new StandardDeviation().evaluate(tpsArray);
        // tps 平均值
        double tpsMean = new Mean().evaluate(tpsArray, 0, tpsArray.length);
        // tps 波动率= tps 标准差 / tps 平均值
        double tpsVix = 0;
        if(0 != tpsMean){
            tpsVix = tpsStd / tpsMean;
        }

        // meanTestTime 百分位数
        Percentile percentile = new Percentile();
        // 先排序
        Arrays.sort(meanTestTimeArray);
        // meanTestTime最小值
        double minMeanTime = meanTestTimeArray[0];
        double twentyFiveMeanTime = percentile.evaluate(meanTestTimeArray, 25);
        double fiftyMeanTime = percentile.evaluate(meanTestTimeArray, 50);
        double serventyFiveMeanTime = percentile
                .evaluate(meanTestTimeArray, 75);
        double eightyMeanTime = percentile.evaluate(meanTestTimeArray, 80);
        double eightyFiveMeanTime = percentile.evaluate(meanTestTimeArray, 85);
        double ninetyMeanTime = percentile.evaluate(meanTestTimeArray, 90);
        double ninetyFiveMeanTime = percentile.evaluate(meanTestTimeArray, 95);
        double ninetyNineMeanTime = percentile.evaluate(meanTestTimeArray, 99);

        int length = meanTestTimeArray.length;
        // meanTestTime最高值
        double maxMeanTime = meanTestTimeArray[length - 1];
        // meanTestTime平均值
        // double TimeMean = new Mean().evaluate(meanTestTimeArray, 0,
        // meanTestTimeArray.length);

        LOGGER.debug(
                "hugang plug Statistics MinMeanTime {}  MaxMeanTime is {}",
                minMeanTime, maxMeanTime);
        // 附加信息 hugang
        // tps 标准差, tps 波动率, 最小/最大RT, RT百分位数
        Map<String, Object> plusStatistics = newHashMap();
        plusStatistics.put("tpsStd", tpsStd);
//      plusStatistics.put("tpsMean", tpsMean);
        plusStatistics.put("tpsVix", tpsVix);
        plusStatistics.put("minMeanTime", minMeanTime);
        plusStatistics.put("twentyFiveMeanTime", twentyFiveMeanTime);
        plusStatistics.put("fiftyMeanTime", fiftyMeanTime);
        plusStatistics.put("serventyFiveMeanTime", serventyFiveMeanTime);
        plusStatistics.put("eightyMeanTime", eightyMeanTime);
        plusStatistics.put("eightyFiveMeanTime", eightyFiveMeanTime);
        plusStatistics.put("ninetyMeanTime", ninetyMeanTime);
        plusStatistics.put("ninetyFiveMeanTime", ninetyFiveMeanTime);
        plusStatistics.put("ninetyNineMeanTime", ninetyNineMeanTime);
        plusStatistics.put("maxMeanTime", maxMeanTime);


        LOGGER.debug("SingleConsole plug Statistics map plusStatistics {}", plusStatistics);


        this.statisticData.put("plusStatistics", plusStatistics);
    }



    /**
     * 
     * 停止采样数据
     * Stop sampling.
     */
    public void unregisterSampling() {
        this.currentNotFinishedProcessCount = 0;
        if (sampleModel != null) {
            this.sampleModel.reset();
            this.sampleModel.stop();
        }
        LOGGER.info("Sampling is stopped");
        informTestSamplingEnd();

        // 结束采样后,处理数据
        // hugang
        getPlusResult();
    }



Map statisticData 为不同数据集集合。

Service 层从 SingleConsole 类中获取数据集 statisticData:
ngrinder-controller/src/main/java/org/ngrinder/perftest/server/PerfTestService.java 中Map<String, Object> result = consoleManager.getConsoleUsingPort(perfTest.getPort()).getStatisticsData();


/**
     * Update the given {@link PerfTest} properties after test finished.
     *
     * @param perfTest perfTest
     * 
     * getConsoleUsingPort()获取数据
     *
     * 
     * hugang
     */
    public void updatePerfTestAfterTestFinish(PerfTest perfTest) {
        checkNotNull(perfTest);
        Map<String, Object> result = consoleManager.getConsoleUsingPort(perfTest.getPort()).getStatisticsData();
        @SuppressWarnings("unchecked")
        Map<String, Object> totalStatistics = MapUtils.getMap(result, "totalStatistics", MapUtils.EMPTY_MAP);
        // 获取附加数据
        Map<String, Object> plusStatistics = MapUtils.getMap(result, "plusStatistics", MapUtils.EMPTY_MAP);

        LOGGER.info("Total Statistics for test {}  is {}", perfTest.getId(), totalStatistics);
        LOGGER.info("plug Statistics for test {}  is {}", perfTest.getId(), plusStatistics);

        perfTest.setTps(parseDoubleWithSafety(totalStatistics, "TPS", 0D));
        perfTest.setMeanTestTime(parseDoubleWithSafety(totalStatistics, "Mean_Test_Time_(ms)", 0D));
        perfTest.setPeakTps(parseDoubleWithSafety(totalStatistics, "Peak_TPS", 0D));
        perfTest.setTests(MapUtils.getDouble(totalStatistics, "Tests", 0D).longValue());
        perfTest.setErrors(MapUtils.getDouble(totalStatistics, "Errors", 0D).longValue());


        // 附加信息写到model, 持久化
        perfTest.setTpsStd(parseDoubleWithSafety(plusStatistics, "tpsStd", 0D));
        perfTest.setTpsVix(parseDoubleWithSafety(plusStatistics, "tpsVix", 0D));
        perfTest.setMinRT(parseDoubleWithSafety(plusStatistics, "minMeanTime", 0D));
        perfTest.setTwentyFiveMeanTime(parseDoubleWithSafety(plusStatistics, "twentyFiveMeanTime", 0D));
        perfTest.setFiftyMeanTime(parseDoubleWithSafety(plusStatistics, "fiftyMeanTime", 0D));
        perfTest.setServentyFiveMeanTime(parseDoubleWithSafety(plusStatistics, "serventyFiveMeanTime", 0D));
        perfTest.setEightyMeanTime(parseDoubleWithSafety(plusStatistics, "eightyMeanTime", 0D));
        perfTest.setEightyFiveMeanTime(parseDoubleWithSafety(plusStatistics, "eightyFiveMeanTime", 0D));
        perfTest.setNinetyMeanTime(parseDoubleWithSafety(plusStatistics, "ninetyMeanTime", 0D));
        perfTest.setNinetyFiveMeanTime(parseDoubleWithSafety(plusStatistics, "ninetyFiveMeanTime", 0D));
        perfTest.setNinetyNineMeanTime(parseDoubleWithSafety(plusStatistics, "ninetyNineMeanTime", 0D));
        perfTest.setMaxRT(parseDoubleWithSafety(plusStatistics, "maxMeanTime", 0D));


    }

修改 Model 层,在 javabean 中增加 TPS 标准差,TPS 波动率,最小/大 RT,RT 25/50/75/80/85/90/95/99 百分位数, JPA 持久化(H2 DB 新增 TPS 标准差,TPS 波动率,最小/大 RT,RT 25/50/75/80/85/90/95/99 百分位数字段)

model 文件为:ngrinder-core/src/main/java/org/ngrinder/model/PerfTest.java


/**
 * 新增字段,TPS标准差,TPS波动率,最小/大RT,RT 25/50/75/80/85/90/95/99百分位数
 * hugang
 */
@Expose
@Column(name = "tpsStd")
private Double tpsStd;

@Expose
@Column(name = "tpsVix")
private Double tpsVix;


@Expose
@Column(name = "minRT")
private Double minRT;

@Expose
@Column(name = "twentyFiveMeanTime")
private Double twentyFiveMeanTime;

@Expose
@Column(name = "fiftyMeanTime")
private Double fiftyMeanTime;

@Expose
@Column(name = "serventyFiveMeanTime")
private Double serventyFiveMeanTime;

@Expose
@Column(name = "eightyMeanTime")
private Double eightyMeanTime;

@Expose
@Column(name = "eightyFiveMeanTime")
private Double eightyFiveMeanTime;

@Expose
@Column(name = "ninetyMeanTime")
private Double ninetyMeanTime;

@Expose
@Column(name = "ninetyFiveMeanTime")
private Double ninetyFiveMeanTime;

@Expose
@Column(name = "ninetyNineMeanTime")
private Double ninetyNineMeanTime;

@Expose
@Column(name = "maxRT")
private Double maxRT;


对应的set(), get()


还需修改 db change 文件 (因为系统 DB 默认使用 H2, 只需修改 H2 对应的 xml),ngrinder-controller/src/main/resources/ngrinder_datachange_logfile/db.changelog_schema_H2.xml


create table PERF_TEST (
            id bigint generated by default as identity unique,
            created_date timestamp,
            last_modified_date timestamp,
            agent_count integer,
            description varchar(2048),
            distribution_path varchar(255),
            duration bigint,
            errors integer,
            finish_time timestamp,
            ignore_sample_count integer,
            init_processes integer,
            init_sleep_time integer,
            last_progress_message varchar(2048),
            mean_test_time double,
            peak_tps double,
            errorRate double,
            tpsStd double,
            tpsVix double,
            minRT double,
            twentyFiveMeanTime double,
            fiftyMeanTime double,
            serventyFiveMeanTime double,
            eightyMeanTime double,
            eightyFiveMeanTime double,
            ninetyMeanTime double,
            ninetyFiveMeanTime double,
            ninetyNineMeanTime double,
            maxRT double,

系统重启加载时,Liquidase 会自动更新 DB。

修改 View 层,在详细报告对应的 freemarker 模板新增 TPS 标准差,TPS 波动率,最小/大 RT,RT 25/50/75/80/85/90/95/99 百分位数字段,前端新增展示这些数据

ngrinder-controller/src/main/webapp/WEB-INF/ftl/perftest/detail_report.ftl



<#-- hugang -->
<#-- 新增 错误率,TPS标准差,TPS波动率,最小RT, 最大RT, RT 25/50/75/80/85/90/95/99百分位数 -->
<tr>
    <th><@spring.message "perfTest.report.errorRate"/></th>
    <td>${(test.errors /(test.tests + test.errors))!""}</td>
</tr>
<tr>
    <th><@spring.message "perfTest.report.tpsStd"/></th>
    <td>${test.tpsStd!""}</td>
</tr>
<tr>
    <th><@spring.message "perfTest.report.tpsVix"/></th>
    <td>${test.tpsVix!""}</td>
</tr>
    <tr>
    <th><@spring.message "perfTest.report.minRT"/></th>
    <td>${test.minRT!""}&nbsp;&nbsp; <code>ms</code></td>
</tr>
    <tr>
    <th><@spring.message "perfTest.report.TwentyFiveMeanTime"/></th>
    <td>${test.twentyFiveMeanTime!""}&nbsp;&nbsp; <code>ms</code></td>
</tr>
    <tr>
    <th><@spring.message "perfTest.report.FiftyMeanTime"/></th>
    <td>${test.fiftyMeanTime!""}&nbsp;&nbsp; <code>ms</code></td>
</tr>
    <tr>
    <th><@spring.message "perfTest.report.ServentyFiveMeanTime"/></th>
    <td>${test.serventyFiveMeanTime!""}&nbsp;&nbsp; <code>ms</code></td>
</tr>
    <tr>
    <th><@spring.message "perfTest.report.EightyMeanTime"/></th>
    <td>${test.eightyMeanTime!""}&nbsp;&nbsp; <code>ms</code></td>
</tr>
</tr>
    <tr>
    <th><@spring.message "perfTest.report.EightyFiveMeanTime"/></th>
    <td>${test.eightyFiveMeanTime!""}&nbsp;&nbsp; <code>ms</code></td>
</tr>
</tr>
    <tr>
    <th><@spring.message "perfTest.report.NinetyMeanTime"/></th>
    <td>${test.ninetyMeanTime!""}&nbsp;&nbsp; <code>ms</code></td>
</tr>
</tr>
    <tr>
    <th><@spring.message "perfTest.report.NinetyFiveMeanTime"/></th>
    <td>${test.ninetyFiveMeanTime!""}&nbsp;&nbsp; <code>ms</code></td>
</tr>
</tr>
    <tr>
    <th><@spring.message "perfTest.report.NinetyNineMeanTime"/></th>
    <td>${test.ninetyNineMeanTime!""}&nbsp;&nbsp; <code>ms</code></td>
</tr>
    </tr>
    <tr>
    <th><@spring.message "perfTest.report.maxRT"/></th>
    <td>${test.maxRT!""}&nbsp;&nbsp; <code>ms</code></td>
</tr>

还有个坑,就是从 github 拉下的代码,源码中 pom.xml 依赖的 jar 包不完整,直接打不了包,项目有的依赖的 jar 公有 maven 仓库已经没有了,需要自己从网上找 jar 包,安装到本地仓库,我归整了下:

http://download.csdn.net/detail/neven7/9443895

直接在 ngrinder 根路径下执行打包命令:

mvn -Dmaven.test.skip=true clean package

部署生成的 war 即可。

3.结果展示

在详细报告页新增如下数据结果:
这里写图片描述

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 48 条回复 时间 点赞

性能工具又多了一项实践

很好的实践! 以后值得借鉴!

很好的实践!

刚开始接触这个平台不久,楼主是否可以多多分析源码。

很赞!阿里的 pts 性能测试,不知道是不是也是用 nGrinder 框架做的?

谢谢分享!

这个能否用来测试 android 手机的性能指标呢??

#4 楼 @shijin880921 还是以实际应用为主,用到哪块具体分析

胡刚 #23 · 2016年02月28日 Author

#5 楼 @cpfeng0124 应该不是,不过设计思路都差不多。

胡刚 #24 · 2016年02月28日 Author

#7 楼 @rockyrock 不适合,定位不一样

#8 楼 @neven7 现在我想监控到 cup 的 load 值,但是不懂怎么搞。

我们公司用的性能测试工具也是基于 Grinder 做的二次开发。模拟并发和读取用户数据,用到了 shell 和 python 脚本;然后收集施压机和被压机的性能指标,通过 Excel 的 VBA 绘制出 cpu、内存、硬盘等报告。针对 nGrinder 有两个问题想请教下:
1.前面说 nGrinder 的详细报告是通过采集 *.data 得来的,那么性能报告页也是默认生成的吗?
2.需要对页面上的多个模块进行性能监控,报告的图表中能反映出来吗?

胡刚 #28 · 2016年03月09日 Author

#13 楼 @rikufly
第一个问题:nGrinder 是用来 SpringMVC 架构,View 层是用了 freemarker 模板展示前端,没太理解你说的默认生成是什么意思?它不是静态页面,是动态生成的;性能报告页性能报告页中有 2 部分数据,第一部分是从 DB 中获取的,另一部分是从 *data 文件展示时序图;详见:https://testerhome.com/topics/4341
第二个问题:前端压测需要录制生成脚本,请参见:http://www.cubrid.org/wiki_ngrinder/entry/recorder;性能数据都能反映。

谢谢分享。

问个问题,ngrinder 中如果用 maven 工程,好像每次执行脚本都会去下载对应的 jar 包到 lib 文件夹内,能否控制,第一次下载就好了,如果没更改就不需要再下载?

弱弱地问一句,为什么不用 jmeter 呢?

胡刚 #32 · 2016年06月05日 Author

#17 楼 @taurus 这种问题,就好比选择哪种编程语言;不选择 jmeter 的原因是它是单实例,我们想把测试脚本和结果大家都能直接共享,包括开发也能直接查看性能测试结果,ngrinder 是个 web 服务,能做到这一点,也能很好的满足业务需求,入门比较低(开发也可以直接做些简单的压测),也便于管理,大家的东西都放在一个地方。适合自己的才是最好的。

我想请教一下如何监控服务器的 cpu,内存等情况呢。我把 ngrinder_monitor 解压到了服务器 root 目录下,然后 sh run_monitor_bg.sh 之后,在新建的测试用例中也添加了服务器 ip,但是测试结束后,还是没有服务器的 cpu 等信息的图形化展示,是我哪一步做的不对么

胡刚 #20 · 2016年06月07日 Author

#19 楼 @wanxi3 请参考:https://testerhome.com/topics/4279; 你要在配置文件 agent.conf 中修改 controller 端所在的 ip 和 port(默认是 13243)。

补充说明:

        // tps 标准差
double tpsStd = new StandardDeviation().evaluate(tpsArray);
// tps 平均值
double tpsMean = new Mean().evaluate(tpsArray, 0, tpsArray.length);
// tps 波动率= tps 标准差 / tps 平均值
double tpsVix = 0;
if(0 != tpsMean){
    tpsVix = tpsStd / tpsMean;
}

// meanTestTime 百分位数
Percentile percentile = new Percentile();

以上的三个类无法找到,需要在 ngrinder-core 下的 pom.xlm 中添加:

org.apache.commons
commons-math3
3.4

#16 楼 @shijin880921 不会重复下载的,只有第一次的时候会下载。

#20 楼 @neven7
monitor 中的 agent.conf 里面的配置有误,@wanxi3 可以参考:https://testerhome.com/topics/5121
monitor.binding_ip=192.168.84.174
monitor.binding_port=13243

胡刚 #38 · 2016年06月16日 Author

#21 楼 @aizaimenghuangu 恩恩,工具包。

胡刚 nGrinder 源码分析:自动中断测试任务 中提及了此贴 12月15日 10:40

@neven7 在 ngrinder-core/src/main/java/org/ngrinder/model/PerfTest.java 添加 tpsStd 等信息时碰到 org.hibernate.HibernateException: Missing column tpsStd 错误,ngrinder 无法启动,请问您在运行过程中遇到了吗?

胡刚 #41 · 2016年12月21日 Author

#26 楼 @seanshao ngrinder-controller/src/main/resources/ngrinder_datachange_logfile/db.changelog_schema_H2.xml 这个文件中添加了新增字段吗,比如 tpsStd double, 如果确认加了;要把以前的 H2 中的数据删除(/root/.ngrinder/db),因为以前数据跟现在表字段不兼容。

#27 楼 @neven7 删除可以了。

代码按照上面修改的,详细报告是这样的,还有哪里需要注意的吗?

@neven7 使用最新版本 3.4 进行上述操作,发现新增加的指标在日志中已经体现了(/root/.ngrinder/logs),但是在数据库目录下没有响应的 Data 生成(/root/.ngrinder/perftest/0_999/1/report),请问你在二次开发过程中是否遇见过这样的情况?

胡刚 #46 · 2017年01月07日 Author

#30 楼 @seanshao 在 ngrinder-controller 中 src->main->resources 里提示语的配置文件,messages_cn.properties(中文),messages_en.properties(英文),messages_kr.properties(韩文)在配置文件添加相应的文本,比如:在 messages_cn.properties 中:

perfTest.report.successRate=成功率
perfTest.report.tpsStd=TPS \u6807\u51C6\u5DEE
perfTest.report.tpsVix=TPS \u6CE2\u52A8\u7387
perfTest.report.minRT=\u6700\u5C0F RT
perfTest.report.TwentyFiveMeanTime=25 \u767E\u5206\u4F4D\u6570 RT
perfTest.report.FiftyMeanTime=50 \u767E\u5206\u4F4D\u6570 RT
perfTest.report.ServentyFiveMeanTime=75 \u767E\u5206\u4F4D\u6570 RT
perfTest.report.EightyMeanTime=80 \u767E\u5206\u4F4D\u6570 RT
perfTest.report.EightyFiveMeanTime=85 \u767E\u5206\u4F4D\u6570 RT
perfTest.report.NinetyMeanTime=90 \u767E\u5206\u4F4D\u6570 RT
perfTest.report.NinetyFiveMeanTime=95 \u767E\u5206\u4F4D\u6570 RT
perfTest.report.NinetyNineMeanTime=99 \u767E\u5206\u4F4D\u6570 RT
perfTest.report.maxRT=\u6700\u5927 RT
胡刚 #47 · 2017年01月07日 Author

#31 楼 @seanshao 这些新增数据其实是根据已有数据算出来的,不需要记录到 data 文件,将计算结果持久到 DB 中就行,前端展示这些新数据,节省了你计算的成本。

@neven7 非常感谢!

Caused by:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'NGrinderDefaultPluginManager': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.ngrinder.infra.plugin.extension.NGrinderDefaultPluginManager.setExtensionFinder(ro.fortsoft.pf4j.ExtensionFinder); nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'NGrinderDefaultExtensionFinder': Requested bean is currently in creation: Is there an unresolvable circular reference?

谁遇到过这个问题?
我编译出来的包可以在 Windows 上运行,但是在 linux 下运行就报错,相同版本的 jdk,相同版本的 maven

请问这些修改有提 pull request 吗?每个人一处处改还是挺麻烦的,版本更新了又得改一次

getPlusResult 里从 meanTestTimeArray 取数据的时候需要捕获异常或者做空判断,否则特殊情况下,如果脚本错误(正常般情况下,都会保证验证脚本通过的情况再去执行测试),执行测试的时候,一直会提示 Script error 但却无法停止测试

数据库的 schema 变更,建议增加一个 db.changelog_schema_xx.xml 来存放,这样就可以利用 liquibase 在原有数据库基础上做升级而不会丢失原有数据

贺旭升 回复

你好,现在的版本按文章方法更新后会报 db 异常 NGrinderRuntimeException: Exception occurs while Liquibase update DB,只能删除.ngrinder 的 db 文件才能更新成功,也要这种方式解决么?

beyondht2003 回复

具体是不是这个错误记不清了,你可以试下,应该可以的

gatling 报告上的 Req/s 这一栏不就是 tps 吗
或者 gating 上的这个数值和 ngrinder 出来的结果计算方式不同?

如果是多个 test 在一个脚本中并行压测,报告页面能统计出来每个 test 的 tp99,tp90 这些数据么?

beyondht2003 回复

请问你的那个问题解决了吗?数据库总增加一个字段,报这个错误的问题 NGrinderRuntimeException: Exception occurs while Liquibase update DB

guoxd 回复

请问这个问题解决了吗?我也遇到相同的问题?在 mac 部署可以正常运行,但在 linux 系统部署,tomcat 不能访问;具体报错如下:

guoxd 回复

org.apache.catalina.core.StandardContext.listenerStart Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'NGrinderSpringExtensionFactory' defined in file [/home/qa/tomcat/webapps/ROOT/WEB-INF/classes/org/ngrinder/infra/plugin/extension/NGrinderSpringExtensionFactory.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [ro.fortsoft.pf4j.PluginManager]: Error creating bean with name 'NGrinderDefaultPluginManager': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.ngrinder.infra.plugin.extension.NGrinderDefaultPluginManager.setSpringExtensionFactory(ro.fortsoft.pf4j.spring.SpringExtensionFactory); nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'NGrinderSpringExtensionFactory': Requested bean is currently in creation: Is there an unresolvable circular reference?; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'NGrinderDefaultPluginManager': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.ngrinder.infra.plugin.extension.NGrinderDefaultPluginManager.setSpringExtensionFactory(ro.fortsoft.pf4j.spring.SpringExtensionFactory); nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'NGrinderSpringExtensionFactory': Requested bean is currently in creation: Is there an unresolvable circular reference?

@ 胡刚 老哥你好,斗胆问一个小问题。目前我们把前端改为,拖拽脚本到文件夹中的功能,昨晚把,实验了一下,功能是可以运行的,全都正常。今天打开项目又试了一下,拖拽不成功,后台这边的话就是先查到脚本的信息,然后保存起来,然后把对象信息删除,最后再添加其他文件夹,例如 test.groovy 文件,添加到 hellowrold/test.groovy。报错的原因是 org .tmatesoft.svn.core.SYNException: sVn:E165002:Stonage of non-regular property 'sin:entry:revision' is disallowed , through the repository interface,and could indicate a bug in your client。我一头雾水啊,这单独创建或者上传一个脚本就没问题,为啥我删除 A 脚本,然后拿着 A 脚本的信息再存到其他文件就会出错呢????

1.获取根目录下的 A 脚本,读取 A 脚本的信息,然后把脚本信息添加到 hello 文件夹,会报错,好像是因为脚本文件有 uuid 导致的

cmcccc 回复

你好,你这个问题有解决么

@ 胡刚 ,老师你好,问下,如果一个脚本里多个 test,这种在详细报告中怎么体现每个 test 的数据结果?

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