• 性能框架多线程基类和执行类--视频讲解


    相信一万行代码的理论!

    讲完了自动化测试的相关内容,接下来开喷性能测试了。首先分享了我的思路:通过一个继承Thread的基类(虚拟类)来规范一些通用的行为和功能,这一部分比较浅,然后通过两个虚拟类来实现两种不同压测模式(定量压测和定时压测),然后在这两个模式类(虚拟类)的基础上,去实现各种不同需求的多线程测试类。还有一个非常重要的就是执行类,通过多线程类来构造多线程任务,用执行类来执行,完事儿之后计算和保存相关测试数据(包括数据库存储和可视化)。

    涉及到一些未很详细的讲解内容,相关文章如下:

    • 在讲到多线程基类的时候有个地方翻车了,errorNum是基类的属性,但是failNum是统计多线程任务的执行状态status,并不是基类属性,而是执行类Concurrent类的属性。

    欢迎各位多提提意见,关注FunTester交流测试相关。

    性能测试框架多线程基类和执行类


    gitee地址:https://gitee.com/fanapi/tester

    可视化效果图

    代码

    多线程基类:

    package com.fun.base.constaint;
    
    import com.fun.base.interfaces.MarkThread;
    import com.fun.frame.SourceCode;
    
    import java.util.List;
    import java.util.concurrent.CountDownLatch;
    import java.util.stream.Collectors;
    
    /**
     * 多线程任务基类,可单独使用
     *
     * @param <T> 必需实现Serializable
     */
    public abstract class ThreadBase<T> extends SourceCode implements Runnable {
    
        public String threadmark;
    
        /**
         * 错误数
         */
        public int errorNum;
    
        /**
         * 执行数,一般与响应时间记录数量相同
         */
        public int excuteNum;
    
        /**
         * 计数锁
         * <p>
         * 会在concurrent类里面根据线程数自动设定
         * </p>
         */
        protected CountDownLatch countDownLatch;
    
        /**
         * 标记对象
         */
        public MarkThread mark;
    
        /**
         * 用于设置访问资源,用于闭包中无法访问包外实例对象的情况
         */
        public T t;
    
        protected ThreadBase() {
        }
    
        /**
         * groovy无法直接访问t,所以写了这个方法,如果报错可以忽略,直接运行,兴许可以成功的
         *
         * @return
         */
        public String getTString() {
            return t.toString();
        }
    
        /**
         * 运行待测方法的之前的准备
         */
        protected abstract void before();
    
        /**
         * 待测方法
         *
         * @throws Exception 抛出异常后记录错误次数,一般在性能测试的时候重置重试控制器不再重试
         */
        protected abstract void doing() throws Exception;
    
        /**
         * 运行待测方法后的处理
         */
        protected void after() {
            if (countDownLatch != null)
                countDownLatch.countDown();
        }
    
        /**
         * 设置计数器
         *
         * @param countDownLatch
         */
        public void setCountDownLatch(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }
    
        /**
         * 拷贝对象方法,用于统计单一对象多线程调用时候的请求数和成功数,对于<T>的复杂情况,需要将T类型也重写clone方法
         *
         * <p>
         * 此处若具体实现类而非虚拟类建议自己写clone方法
         * </p>
         *
         * @return
         */
        @Override
        public ThreadBase clone() {
            return deepClone(this);
        }
    
        /**
         * 线程任务是否需要提前关闭,默认返回false
         * <p>
         * 一般用于单线程错误率过高的情况
         * </p>
         *
         * @return
         */
        public boolean status() {
            return false;
        }
    
        /**
         * Groovy乘法调用方法
         *
         * @param num
         * @return
         */
        public List<ThreadBase> multiply(int num) {
            return range(num).mapToObj(x -> this.clone()).collect(Collectors.toList());
        }
    
    
    }
    
    

    执行类:

    package com.fun.frame.excute;
    
    import com.fun.base.bean.PerformanceResultBean;
    import com.fun.base.constaint.ThreadBase;
    import com.fun.config.Constant;
    import com.fun.frame.Save;
    import com.fun.frame.SourceCode;
    import com.fun.utils.Time;
    import com.fun.utils.WriteRead;
    import org.apache.commons.lang3.ArrayUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.util.*;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.stream.Collectors;
    
    import static java.util.stream.Collectors.toList;
    
    /**
     * 并发类,用于启动压力脚本
     */
    public class Concurrent extends SourceCode {
    
        private static Logger logger = LoggerFactory.getLogger(Concurrent.class);
    
        /**
         * 开始时间
         */
        private long startTime;
    
        /**
         * 结束时间
         */
        private long endTime;
    
        /**
         * 任务描述
         */
        public String desc = "FunTester";
    
        /**
         * 任务集
         */
        public List<ThreadBase> threads = new ArrayList<>();
    
        /**
         * 线程数
         */
        public int threadNum;
    
        /**
         * 执行失败总数
         */
        private int errorTotal;
    
        /**
         * 任务执行失败总数
         */
        private int failTotal;
    
        /**
         * 执行总数
         */
        private int excuteTotal;
    
        /**
         * 用于记录所有请求时间
         */
        public static Vector<Long> allTimes = new Vector<>();
    
        /**
         * 记录所有markrequest的信息
         */
        public static Vector<String> requestMark = new Vector<>();
    
        /**
         * 线程池
         */
        ExecutorService executorService;
    
        /**
         * 计数器
         */
        CountDownLatch countDownLatch;
    
        /**
         * @param thread    线程任务
         * @param threadNum 线程数
         */
        public Concurrent(ThreadBase thread, int threadNum) {
            this(threadNum);
            range(threadNum).forEach(x -> threads.add(thread.clone()));
        }
    
        /**
         * @param threads 线程组
         */
        public Concurrent(List<ThreadBase> threads) {
            this(threads.size());
            this.threads = threads;
        }
    
        /**
         * @param thread    线程任务
         * @param threadNum 线程数
         * @param desc      任务描述
         */
        public Concurrent(ThreadBase thread, int threadNum, String desc) {
            this(thread, threadNum);
            this.desc = desc + Time.getNow();
        }
    
        /**
         * @param threads 线程组
         * @param desc    任务描述
         */
        public Concurrent(List<ThreadBase> threads, String desc) {
            this(threads);
            this.desc = desc + Time.getNow();
        }
    
        private Concurrent(int threadNum) {
            this.threadNum = threadNum;
            executorService = Executors.newFixedThreadPool(threadNum);
            countDownLatch = new CountDownLatch(threadNum);
        }
    
        private Concurrent() {
    
        }
    
        /**
         * 执行多线程任务
         */
        public PerformanceResultBean start() {
            startTime = Time.getTimeStamp();
            for (int i = 0; i < threadNum; i++) {
                ThreadBase thread = getThread(i);
                thread.setCountDownLatch(countDownLatch);
                executorService.execute(thread);
            }
            shutdownService(executorService, countDownLatch);
            endTime = Time.getTimeStamp();
            threads.forEach(x -> {
                if (x.status()) failTotal++;
                errorTotal += x.errorNum;
                excuteTotal += x.excuteNum;
            });
            logger.info("总计{}个线程,共用时:{} s,执行总数:{},错误数:{},失败数:{}", threadNum, Time.getTimeDiffer(startTime, endTime), excuteTotal, errorTotal, failTotal);
            return over();
        }
    
        /**
         * 关闭任务相关资源
         *
         * @param executorService 线程池
         * @param countDownLatch  计数器
         */
        private static void shutdownService(ExecutorService executorService, CountDownLatch countDownLatch) {
            try {
                countDownLatch.await();
                executorService.shutdown();
            } catch (InterruptedException e) {
                logger.warn("线程池关闭失败!", e);
            }
        }
    
        private PerformanceResultBean over() {
            Save.saveLongList(allTimes, threadNum + desc);
            Save.saveStringListSync(Concurrent.requestMark, MARK_Path.replace(LONG_Path, EMPTY) + desc);
            allTimes = new Vector<>();
            requestMark = new Vector<>();
            return countQPS(threadNum, desc, Time.getTimeByTimestamp(startTime), Time.getTimeByTimestamp(endTime));
        }
    
        ThreadBase getThread(int i) {
            return threads.get(i);
        }
    
        /**
         * 计算结果
         * <p>此结果仅供参考</p>
         *
         * @param name 线程数
         */
        public PerformanceResultBean countQPS(int name, String desc, String start, String end) {
            List<String> strings = WriteRead.readTxtFileByLine(Constant.LONG_Path + name + desc);
            int size = strings.size();
            List<Integer> data = strings.stream().map(x -> changeStringToInt(x)).collect(toList());
            int sum = data.stream().mapToInt(x -> x).sum();
            Collections.sort(data);
            String statistics = statistics(data, desc);
            double qps = 1000.0 * size * name / sum;
            return new PerformanceResultBean(desc, start, end, name, size, sum / size, qps, getPercent(excuteTotal, errorTotal), getPercent(threadNum, failTotal), excuteTotal, statistics);
        }
    
        /**
         * 将性能测试数据图表展示
         *
         * <p>
         * 将数据排序,然后按照循序分桶,选择桶中中位数作代码,通过二维数组转化成柱状图
         * </p>
         *
         * @param data 性能测试数据,也可以其他统计数据
         * @return
         */
        public static String statistics(List<Integer> data, String title) {
            int size = data.size();
            if (size < 1000) return EMPTY;
            int[] ints = range(1, BUCKET_SIZE + 1).map(x -> data.get(size * x / BUCKET_SIZE - size / BUCKET_SIZE / 2)).toArray();
            int largest = ints[BUCKET_SIZE - 1];
            String[][] map = Arrays.asList(ArrayUtils.toObject(ints)).stream().map(x -> getPercent(x, largest, BUCKET_SIZE)).collect(toList()).toArray(new String[BUCKET_SIZE][BUCKET_SIZE]);
            String[][] result = new String[BUCKET_SIZE][BUCKET_SIZE];
            /*将二维数组反转成竖排*/
            for (int i = 0; i < BUCKET_SIZE; i++) {
                for (int j = 0; j < BUCKET_SIZE; j++) {
                    result[i][j] = getManyString(map[j][BUCKET_SIZE - 1 - i], 2) + SPACE_1;
                }
            }
            StringBuffer table = new StringBuffer(LINE + getManyString(TAB, 4) + ((title == null || title.length() == 0) ? DEFAULT_STRING : title) + LINE + LINE + TAB + ">>响应时间分布图,横轴排序分成桶的序号,纵轴每个桶的中位数<<" + LINE + TAB + TAB + "--<中位数数据最小值为:" + ints[0] + " ms,最大值:" + ints[BUCKET_SIZE - 1] + " ms>--" + LINE);
            for (int i = 0; i < BUCKET_SIZE; i++) {
                table.append(Arrays.asList(result[i]).stream().collect(Collectors.joining()) + LINE);
            }
            return table.toString();
        }
    
    
        /**
         * 用于做后期的计算
         *
         * @param name
         * @param desc
         * @return
         */
        public PerformanceResultBean countQPS(int name, String desc) {
            return countQPS(name, desc, Time.getDate(), Time.getDate());
        }
    
        /**
         * 后期计算用
         *
         * @param name
         * @return
         */
        public PerformanceResultBean countQPS(int name) {
            return countQPS(name, EMPTY, Time.getDate(), Time.getDate());
        }
    
        /**
         * 将数据转化成string数组
         *
         * @param part   数据
         * @param total  基准数据,默认最大的中位数
         * @param length
         * @return
         */
        public static String[] getPercent(int part, int total, int length) {
            int i = part * 8 * length / total;
            int prefix = i / 8;
            int suffix = i % 8;
            String s = getManyString(PERCENT[8], prefix) + (prefix == length ? EMPTY : PERCENT[suffix] + getManyString(SPACE_1, length - prefix - 1));
            return s.split(EMPTY);
        }
    
    
    }
    

    • 郑重声明:“FunTester”首发,欢迎关注交流,禁止第三方转载。

    技术类文章精选

    无代码文章精选

  • 相关阅读:
    Android.08.PopupWindow
    Android.07.AlertDialog
    Win 10 3D查看器 三维模型资源获取
    WebAssembly 系列(六)WebAssembly 的现在与未来
    《JavaScript高级程序设计》读书笔记(一)
    《JavaScript高级程序设计》读书笔记(二)
    WebAssembly 系列(五)为什么 WebAssembly 更快?
    《JavaScript DOM 编程艺术》读书笔记
    2.5维地图前端js api对比分析
    三维地图前端js api对比分析
  • 原文地址:https://www.cnblogs.com/FunTester/p/12677914.html
Copyright © 2020-2023  润新知