在以往的性能測試中,我一般都是先將測試數據保存,然後等測試完成之後再進行數據統計和出圖展示,既減少了用例運行時資源消耗,也能對測試數據進行二次分析。
但這種模式下無法對測試過程進行監控,有時候運行用例的時候,會有長達數分鐘的真空期。有點難熬,所以前段時間增加了一個性能測試中異步展示測試進度的功能。
在某次思考人生的時候突然從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.ThreadBase
import com.funtester.base.constaint.ThreadLimitTimesCount
import com.funtester.frame.SourceCode
import com.funtester.frame.execute.Concurrent
import 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.16
INFO-> FunTester測試進度條取樣器進度:▍ 2.25% ,當前QPS: 14
INFO-> FunTester測試進度條取樣器進度:▍▍▍ 4.75% ,當前QPS: 22
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍ 8% ,當前QPS: 25
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍ 10.25% ,當前QPS: 16
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍ 12.5% ,當前QPS: 18
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍ 14.5% ,當前QPS: 18
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍ 16.75% ,當前QPS: 21
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍ 19% ,當前QPS: 19
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 21.5% ,當前QPS: 21
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 23.75% ,當前QPS: 21
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 26.75% ,當前QPS: 25
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 29.75% ,當前QPS: 28
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 31.75% ,當前QPS: 18
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 34% ,當前QPS: 22
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 36.25% ,當前QPS: 22
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 38.25% ,當前QPS: 18
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 40.25% ,當前QPS: 18
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 42.5% ,當前QPS: 21
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 45.25% ,當前QPS: 19
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 47% ,當前QPS: 24
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 49.5% ,當前QPS: 25
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 51.75% ,當前QPS: 17
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 54.25% ,當前QPS: 16
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 56.75% ,當前QPS: 19
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 59.75% ,當前QPS: 17
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 62% ,當前QPS: 25
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 64.25% ,當前QPS: 16
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 66.75% ,當前QPS: 23
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 69% ,當前QPS: 20
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 71.5% ,當前QPS: 19
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 74.25% ,當前QPS: 18
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 77.5% ,當前QPS: 27
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 80.75% ,當前QPS: 24
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 83.25% ,當前QPS: 16
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 86% ,當前QPS: 23
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 88% ,當前QPS: 18
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 90.25% ,當前QPS: 21
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 93.5% ,當前QPS: 21
INFO-> 線程:FunTester測試進度條取樣器2,執行次數:400,錯誤次數: 0,總耗時:198.109 s
INFO-> 線程:FunTester測試進度條取樣器0,執行次數:400,錯誤次數: 0,總耗時:200.369 s
INFO-> 線程:FunTester測試進度條取樣器1,執行次數:400,錯誤次數: 0,總耗時:204.173 s
INFO-> 線程:FunTester測試進度條取樣器3,執行次數:400,錯誤次數: 0,總耗時:205.531 s
INFO-> 線程:FunTester測試進度條取樣器5,執行次數:400,錯誤次數: 0,總耗時:206.551 s
INFO-> 線程:FunTester測試進度條取樣器8,執行次數:400,錯誤次數: 0,總耗時:208.543 s
INFO-> 線程:FunTester測試進度條取樣器4,執行次數:400,錯誤次數: 0,總耗時:208.618 s
INFO-> 線程:FunTester測試進度條取樣器9,執行次數:400,錯誤次數: 0,總耗時:208.856 s
INFO-> 線程:FunTester測試進度條取樣器6,執行次數:400,錯誤次數: 0,總耗時:209.112 s
INFO-> 線程:FunTester測試進度條取樣器7,執行次數:400,錯誤次數: 0,總耗時:211.758 s
INFO-> FunTester測試進度條取樣器進度:▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍▍ 100%
INFO->
FunTester測試進度條取樣器QPS變化曲線
圖片往下看
INFO-> 總計10個線程,共用時:211.762 s,執行總數:4000,錯誤數:0,失敗數:0
INFO-> 數據保存成功!文件名:/Users/fv/Documents/workspace/funtester/long/data/FunTester測試進度條取樣器181550_10
INFO->
~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~~☢~ 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,騰訊雲年度作者、Boss直聘簽約作者,非著名測試開發er,歡迎關注。
-
2021年自動化測試流行趨勢【譯】 -
Appium 2.0速覽 -
FunTester測試框架架構圖初探 -
FunTester測試項目架構圖初探 -
從JVM堆內存分析驗證深淺拷貝 -
單鏈路性能測試實踐 -
自動化策略六步走 -
用Groovy處理JMeter中的請求參數 -
Gradle+Groovy基礎篇 -
Gradle+Groovy提高篇 -
弱網測試:最低流暢網速是多少?
本文分享自微信公衆號 - FunTester(NuclearTester)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。