写点什么

性能测试框架中实时 QPS 取样器实现

用户头像
FunTester
关注
发布于: 31 分钟前

性能测试框架中实时 QPS 取样器实现


在以往的性能测试中,我一般都是先将测试数据保存,然后等测试完成之后再进行数据统计和出图展示,既减少了用例运行时资源消耗,也能对测试数据进行二次分析。


单这种模式下无法对测试过程进行监控,有时候运行用例的时候,会有长达数分钟的真空期。有点难熬,所以前段时间增加了一个性能测试中异步展示测试进度的功能。


在某次思考人生的时候突然从JMeter取样器sampler得到了灵感,我要是也能实时获取当前系统的QPS处理能力的数据的话,既可以提前预估到本次测试结果QPS的数值,也能观察到QPS在整个过程中变化的曲线,如果不符合标准压测模型的话,还可以辅助排查瓶颈,可谓一举多得。


说干就干,本来想重新写一个异步类来完成这个功能,但是写完发现功能和之前写过的进度条功能类重合度太高了,最终决定把功能整合在一个类中,在检测进度条的时候也输出当前系统QPS

实现类

这次对进度条类Progress进行了功能丰富,改动较大,所以这次把代码都贴过来了。


package com.funtester.frame.execute;
import com.funtester.base.constaint.FixedQpsThread;import com.funtester.base.constaint.ThreadBase;import com.funtester.base.constaint.ThreadLimitTimeCount;import com.funtester.base.constaint.ThreadLimitTimesCount;import com.funtester.base.exception.ParamException;import com.funtester.config.HttpClientConstant;import com.funtester.frame.SourceCode;import com.funtester.utils.StringUtil;import com.funtester.utils.Time;import org.slf4j.Logger;import org.slf4j.LoggerFactory;
import java.util.ArrayList;import java.util.List;import java.util.concurrent.atomic.AtomicInteger;import java.util.stream.Collectors;
/** * 用于异步展示性能测试进度的多线程类 * * @param <F> 多线程任务{@link ThreadBase}对象的实现子类 */public class Progress<F extends ThreadBase> extends SourceCode implements Runnable {
private static Logger logger = LoggerFactory.getLogger(Progress.class);
/** * 会长 */ private static final String SUFFIX = "QPS变化曲线";
/** * 记录每一次获取QPS的值,可能用于结果展示 */ public List<Integer> qs = new ArrayList<>();
/** * 多线程任务类对象 */ private List<F> threads;
/** * 线程数,用于计算实时QPS */ private int threadNum;
/** * 进度条的长度 */ private static final int LENGTH = 67;
/** * 标志符号 */ private static final String ONE = getPart(3);
/** * 总开关,是否运行,默认true */ private boolean st = true;
/** * 是否次数模型 */ private boolean isTimesMode;
/** * 用于区分固定QPS请求模型,这里不计算固定QPS模型中的实时QPS */ private boolean canCount;
/** * 多线程任务基类对象,本类中不处理,只用来获取值,若使用的话请调用clone()方法 */ private F base;
/** * 在固定QPS模式中使用 */ private AtomicInteger excuteNum;
/** * 限制条件 */ private int limit;
/** * 非精确时间,误差可以忽略 */ private long startTime = Time.getTimeStamp();
/** * 描述 */ private String taskDesc;
/** * 固定线程模型 * * @param threads * @param desc */ public Progress(final List<F> threads, String desc) { this.threads = threads; this.threadNum = threads.size(); this.taskDesc = desc; this.base = threads.get(0); init(); }
/** * 适配固定QPS模型 * * @param threads * @param desc * @param excuteNum */ public Progress(final List<F> threads, String desc, final AtomicInteger excuteNum) { this.threads = threads; this.threadNum = threads.size(); this.taskDesc = desc; this.base = threads.get(0); init(); }
/** * 初始化对象,对istimesMode和limit赋值 */ private void init() { if (base instanceof ThreadLimitTimeCount) { this.isTimesMode = false; this.canCount = true; this.limit = ((ThreadLimitTimeCount) base).time; } else if (base instanceof ThreadLimitTimesCount) { this.isTimesMode = true; this.canCount = true; this.limit = ((ThreadLimitTimesCount) base).times; } else if (base instanceof FixedQpsThread) { FixedQpsThread fix = (FixedQpsThread) base; this.canCount = false; this.isTimesMode = fix.isTimesMode; this.limit = fix.limit; } else { ParamException.fail("创建进度条对象失败!"); } }
@Override public void run() { double pro = 0; while (st) { sleep(HttpClientConstant.LOOP_INTERVAL); pro = isTimesMode ? base.executeNum == 0 ? FixedQpsConcurrent.executeTimes.get() * 1.0 / limit : base.executeNum * 1.0 / limit : (Time.getTimeStamp() - startTime) * 1.0 / limit; if (pro > 0.95) break; if (st) logger.info("{}进度:{} {} ,当前QPS: {}", taskDesc, getManyString(ONE, (int) (pro * LENGTH)), getPercent(pro * 100), getQPS()); } }
/** * 获取某一个时刻的QPS * * @return */ private int getQPS() { int qps = 0; if (canCount) { List<Integer> times = new ArrayList<>(); for (int i = 0; i < threadNum; i++) { List<Integer> costs = threads.get(i).costs; int size = costs.size(); if (size < 3) continue; times.add(costs.get(size - 1)); times.add(costs.get(size - 2)); } qps = times.isEmpty() ? 0 : (int) (1000 * threadNum / (times.stream().collect(Collectors.summarizingInt(x -> x)).getAverage())); } else { qps = excuteNum.get() / (int) (Time.getTimeStamp() - startTime); } qs.add(qps); return qps; }
/** * 关闭线程,防止死循环 */ public void stop() { st = false; logger.info("{}进度:{} {}", taskDesc, getManyString(ONE, LENGTH), "100%"); printQPS(); }
/** * 打印QPS变化曲线 */ private void printQPS() { int size = qs.size(); if (size < 5) return; if (size <= BUCKET_SIZE) { output(StatisticsUtil.draw(qs, StringUtil.center(taskDesc + SUFFIX, size * 3)) + LINE + LINE); } else { double v = size * 1.0 / BUCKET_SIZE; List<Integer> qpss = range(BUCKET_SIZE).mapToObj(x -> qs.get((int) (x * v))).collect(Collectors.toList()); output(StatisticsUtil.draw(qpss, StringUtil.center(taskDesc + SUFFIX, BUCKET_SIZE * 3) + LINE + LINE)); } }
}
复制代码

测试脚本

随便写了一个内部类,随机休眠的方式重写了doing()方法。


package com.funtester.groovy
import com.funtester.base.constaint.ThreadBaseimport com.funtester.base.constaint.ThreadLimitTimesCountimport com.funtester.frame.SourceCodeimport com.funtester.frame.execute.Concurrentimport com.funtester.utils.StringUtil
class WebT extends SourceCode {

static void main(String[] args) { def ts = []
10.times { ts << new FunTester(StringUtil.getString(10), 400) }
new Concurrent(ts, "FunTester测试进度条取样器").start()
}
private static class FunTester extends ThreadLimitTimesCount<String> {

FunTester(String s, int times) { super(s, times, null) }
@Override protected void doing() throws Exception { sleep(0.01 + getRandomDouble()) }
@Override ThreadBase clone() { new FunTester(StringUtil.getString(10), times) } }
}
复制代码

控制台输出

省略了无关内容。


INFO-> 当前用户:fv,IP:10.60.193.37,工作目录:/Users/fv/Documents/workspace/funtester/,系统编码格式:UTF-8,系统Mac OS X版本:10.16INFO-> FunTester测试进度条取样器进度:▍  2.25% ,当前QPS: 14INFO-> FunTester测试进度条取样器进度:▍▍▍  4.75% ,当前QPS: 22INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍  8% ,当前QPS: 25INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍  10.25% ,当前QPS: 16INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍  12.5% ,当前QPS: 18INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍  14.5% ,当前QPS: 18INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍  16.75% ,当前QPS: 21INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍  19% ,当前QPS: 19INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍  21.5% ,当前QPS: 21INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  23.75% ,当前QPS: 21INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  26.75% ,当前QPS: 25INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  29.75% ,当前QPS: 28INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  31.75% ,当前QPS: 18INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  34% ,当前QPS: 22INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  36.25% ,当前QPS: 22INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  38.25% ,当前QPS: 18INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  40.25% ,当前QPS: 18INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  42.5% ,当前QPS: 21INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  45.25% ,当前QPS: 19INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  47% ,当前QPS: 24INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  49.5% ,当前QPS: 25INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  51.75% ,当前QPS: 17INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  54.25% ,当前QPS: 16INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  56.75% ,当前QPS: 19INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  59.75% ,当前QPS: 17INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  62% ,当前QPS: 25INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  64.25% ,当前QPS: 16INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  66.75% ,当前QPS: 23INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  69% ,当前QPS: 20INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  71.5% ,当前QPS: 19INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  74.25% ,当前QPS: 18INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  77.5% ,当前QPS: 27INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  80.75% ,当前QPS: 24INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  83.25% ,当前QPS: 16INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  86% ,当前QPS: 23INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  88% ,当前QPS: 18INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  90.25% ,当前QPS: 21INFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  93.5% ,当前QPS: 21INFO-> 线程:FunTester测试进度条取样器2,执行次数:400,错误次数: 0,总耗时:198.109 sINFO-> 线程:FunTester测试进度条取样器0,执行次数:400,错误次数: 0,总耗时:200.369 sINFO-> 线程:FunTester测试进度条取样器1,执行次数:400,错误次数: 0,总耗时:204.173 sINFO-> 线程:FunTester测试进度条取样器3,执行次数:400,错误次数: 0,总耗时:205.531 sINFO-> 线程:FunTester测试进度条取样器5,执行次数:400,错误次数: 0,总耗时:206.551 sINFO-> 线程:FunTester测试进度条取样器8,执行次数:400,错误次数: 0,总耗时:208.543 sINFO-> 线程:FunTester测试进度条取样器4,执行次数:400,错误次数: 0,总耗时:208.618 sINFO-> 线程:FunTester测试进度条取样器9,执行次数:400,错误次数: 0,总耗时:208.856 sINFO-> 线程:FunTester测试进度条取样器6,执行次数:400,错误次数: 0,总耗时:209.112 sINFO-> 线程:FunTester测试进度条取样器7,执行次数:400,错误次数: 0,总耗时:211.758 sINFO-> FunTester测试进度条取样器进度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍  100%INFO->                                     FunTester测试进度条取样器QPS变化曲线                                    
图片往下看
INFO-> 总计10个线程,共用时:211.762 s,执行总数:4000,错误数:0,失败数:0INFO-> 数据保存成功!文件名:/Users/fv/Documents/workspace/funtester/long/data/FunTester测试进度条取样器181550_10INFO-> ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ JSON ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~> {> ① . "rt":515,> ① . "total":4000,> ① . "qps":19.417,> ① . "failRate":0.0,> ① . "threads":10,> ① . "startTime":"2021-03-18 15:50:18",> ① . "endTime":"2021-03-18 15:53:50",> ① . "errorRate":0.0,> ① . "executeTotal":4000,> ① . "mark":"FunTester测试进度条取样器181550",> ① . "table":"省略压缩字符串"> }~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ JSON ~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~INFO-> FunTester测试进度条取样器 10 thread
Response Time: x-serial num, y-median min median:30 ms,max:995 ms 图片往下看 Process finished with exit code 0
复制代码



![FunTester 测试进度条取样器 10 thread](http://pic.automancloud.com/FunTester测试进度条取样器 10 thread.png)



FunTester腾讯云年度作者Boss直聘签约作者,非著名测试开发 er,欢迎关注。


发布于: 31 分钟前阅读数: 4
用户头像

FunTester

关注

公众号:FunTester,Have Fun, Tester! 2020.10.20 加入

Have Fun,Tester!

评论

发布
暂无评论
性能测试框架中实时QPS取样器实现