在进行性能测试的过程中,通常可能会遇到长时间测试的情况,但是在这过程中很难控制压测进度(偷偷告诉你终止性能测试并输出报告可以实现)。
为了解决无法实时掌控测试进度的问题,我写了一个多线程类,主要的功能就是异步完成对性能测试进度的收集和输出。
思路如下:性能测试模型分两类(固定线程和固定 QPS),测试的模式两种(定时和定量),为了兼容这两种模型和两种模式,我用了一个类,使用不同的标记属性来区分。然后根据具体的限制类型(时间或者次数)来获取不同的进度值,通过简单的运算得到结果,利用之前性能测试中图形化输出测试数据文章中用到的█符合来输出结果。
package com.fun.frame.execute;
import com.fun.base.constaint.FixedQpsThread;
import com.fun.base.constaint.ThreadBase;
import com.fun.base.constaint.ThreadLimitTimeCount;
import com.fun.base.constaint.ThreadLimitTimesCount;
import com.fun.base.exception.ParamException;
import com.fun.config.HttpClientConstant;
import com.fun.frame.SourceCode;
import com.fun.utils.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 用于异步展示性能测试进度的多线程类
*/
public class Progress extends SourceCode implements Runnable {
private static Logger logger = LoggerFactory.getLogger(Progress.class);
/**
* 总开关,是否运行,默认true
*/
private boolean st = true;
/**
* 是否次数模型
*/
public boolean isTimesMode;
/**
* 多线程任务基类对象,本类中不处理,只用来获取值,若使用的话请调用clone()方法
*/
private ThreadBase base;
/**
* 限制条件
*/
private int limit;
/**
* 非精确时间,误差可以忽略
*/
private long startTime = Time.getTimeStamp();
/**
* 描述
*/
private String taskDesc;
public Progress(ThreadBase base, String desc) {
this(base);
this.base = base;
this.taskDesc = desc;
}
private Progress(ThreadBase base) {
if (base instanceof ThreadLimitTimeCount) {
this.isTimesMode = false;
this.limit = ((ThreadLimitTimeCount) base).time;
} else if (base instanceof ThreadLimitTimesCount) {
this.isTimesMode = true;
this.limit = ((ThreadLimitTimesCount) base).times;
} else if (base instanceof FixedQpsThread) {
FixedQpsThread fix = (FixedQpsThread) base;
this.isTimesMode = fix.isTimesMode;
this.limit = fix.limit;
} else {
ParamException.fail("创建进度条对象失败!");
}
}
@Override
public void run() {
int pro = 0;
while (st) {
sleep(HttpClientConstant.LOOP_INTERVAL);
if (isTimesMode) {
pro = (int) (base.executeNum * 1.0 / limit * BUCKET_SIZE * 2);
} else {
pro = (int) ((Time.getTimeStamp() - startTime) * 1.0 / limit * BUCKET_SIZE * 2);
}
if (pro >= BUCKET_SIZE * 2) break;
logger.info("{}测试进度:{} {}", taskDesc, getManyString(getPercent(8), pro), getPercent(getPercent(BUCKET_SIZE * 2, pro)));
}
}
/**
* 关闭线程,防止死循环
*/
public void stop() {
st = false;
logger.info("{}测试进度:{} {}", taskDesc, getManyString(getPercent(8), BUCKET_SIZE * 2), "100%");
}
}
两种测试模型执行类的代码都差不多,这里在start()
方法中添加一个线程即可,在结束的时候执行一下stop()
方法。
/**
* 执行多线程任务
* 默认取list中thread对象,丢入线程池,完成多线程执行,如果没有threadname,name默认采用desc+线程数作为threadname,去除末尾的日期
*/
public PerformanceResultBean start() {
Progress progress = new Progress(threads.get(0), desc.replaceAll("\\d{14}$", EMPTY));
new Thread(progress).start();
startTime = Time.getTimeStamp();
for (int i = 0; i < threadNum; i++) {
ThreadBase thread = threads.get(i);
if (StringUtils.isBlank(thread.threadName)) thread.threadName = desc.replaceAll("\\d{14}$", EMPTY) + i;
thread.setCountDownLatch(countDownLatch);
executorService.execute(thread);
}
shutdownService(executorService, countDownLatch);
endTime = Time.getTimeStamp();
progress.stop();
threads.forEach(x -> {
if (x.status()) failTotal++;
errorTotal += x.errorNum;
executeTotal += x.executeNum;
});
logger.info("总计{}个线程,共用时:{} s,执行总数:{},错误数:{},失败数:{}", threadNum, Time.getTimeDiffer(startTime, endTime), executeTotal, errorTotal, failTotal);
return over();
}
另外一个固定 QPS 压测模式探索中的使用我就不写了。
这里输出的都是字符串,这里复制一批展示效果。
16:42:33 INFO - 查询已发布教学活动列表测试进度: 0%
16:42:38 INFO - 查询已发布教学活动列表测试进度:██ 4.34%
16:42:48 INFO - 查询已发布教学活动列表测试进度:████ 8.69%
16:42:53 INFO - 查询已发布教学活动列表测试进度:█████ 10.86%
16:42:58 INFO - 查询已发布教学活动列表测试进度:██████ 13.04%
16:43:03 INFO - 查询已发布教学活动列表测试进度:███████ 15.21%
16:43:08 INFO - 查询已发布教学活动列表测试进度:████████ 17.39%
16:43:13 INFO - 查询已发布教学活动列表测试进度:█████████ 19.56%
16:43:18 INFO - 查询已发布教学活动列表测试进度:██████████ 21.73%
16:43:28 INFO - 查询已发布教学活动列表测试进度:███████████ 23.91%
16:43:33 INFO - 查询已发布教学活动列表测试进度:████████████ 26.08%
16:43:43 INFO - 查询已发布教学活动列表测试进度:██████████████ 30.43%
16:43:48 INFO - 查询已发布教学活动列表测试进度:████████████████ 34.78%
16:43:53 INFO - 查询已发布教学活动列表测试进度:█████████████████ 36.95%