FunTester 【连载 20】实时信息展示

FunTester · 2025年03月08日 · 最后由 FunTester 回复于 2025年03月10日 · 1713 次阅读

3.6 测试中信息实时展示

在性能测试中,实时展示测试数据是一个非常重要的功能。它可以帮助测试人员实时监控系统的性能表现,及时发现性能瓶颈或异常情况,从而做出相应的调整或停止测试,避免对系统造成不必要的损害。为了实现这一功能,我们需要对现有的性能测试引擎进行进一步的升级。

实时展示功能的核心需求

  1. 实时统计 TPS(每秒事务数)和平均耗时:在测试过程中,实时展示系统的 TPS 和平均响应时间,帮助测试人员了解系统的当前负载和性能表现。

  2. 异步线程监控:通过一个独立的异步线程,定期收集和展示性能数据,确保主线程的性能测试任务不受影响。

  3. 动态控制监控线程:在测试结束后,能够优雅地关闭监控线程,避免资源浪费。

实现实时展示功能的关键步骤

  1. 增加实时统计属性

    • 使用LongAdder来记录实时总耗时和总次数。LongAdder在高并发场景下性能优于AtomicLongAtomicInteger
    • 增加一个布尔类型的开关(realTimeKey),用于控制监控线程的启停。
  2. 创建监控线程

    • start()方法中创建一个独立的线程,用于定期收集和展示实时性能数据。
    • 该线程每隔 1 秒(或其他自定义时间间隔)统计一次 TPS 和平均耗时,并输出到控制台。
  3. 多线程任务类上报数据

    • 在多线程任务类的run()方法中,每次执行测试任务后,将耗时和执行次数上报到执行类的LongAdder属性中。
  4. 优雅关闭监控线程

    • 在测试结束后,将realTimeKey设置为false,并等待监控线程结束。

代码实现

以下是实现实时展示功能的关键代码片段:

执行类(TaskExecutor)
public class TaskExecutor {
    // 用于实时统计一段时间总耗时
    public static LongAdder realTimeCostTime = new LongAdder();

    // 用于实时统计一段时间总次数
    public static LongAdder realTimeCostTimes = new LongAdder();

    // 用于控制实时统计线程结束开关
    public boolean realTimeKey = true;

    /**
     * 开始执行任务
     */
    public void start() throws InterruptedException {
        // 创建实时统计线程
        Thread realTimeThread = new Thread(() -> {
            while (realTimeKey) {
                ThreadTool.sleep(1000); // 休眠1秒
                long sumCost = realTimeCostTime.sumThenReset(); // 重置总耗时
                long sumTimes = realTimeCostTimes.sumThenReset(); // 重置总次数
                System.out.println(String.format("实时统计TPS: %d, 平均耗时: %d", 
                    sumTimes, sumTimes == 0 ? 0 : sumCost / sumTimes));
            }
        });
        realTimeThread.start();

        // 启动Rump-Up阶段
        int gap = rumpUpTime * 1000 / tasks.size(); // 计算每个线程的启动间隔
        for (ThreadTask task : tasks) {
            poolExecutor.execute(task); // 提交线程池执行
            ThreadTool.sleep(gap); // 休眠,间隔提交多线程任务
        }

        rumpUpCountDownLatch.await(); // 等待Rump-Up计数器为0
        tasks.forEach(f -> f.countState = true); // 开启数据收集
        System.out.println("Rump-Up结束, 开始执行测试任务!");

        this.startTimestamp = System.currentTimeMillis(); // 记录开始时间
        stopCountDownLatch.await(); // 等待停止任务计数器为0
        this.endTimestamp = System.currentTimeMillis(); // 记录结束时间

        // 关闭线程池和实时统计线程
        this.poolExecutor.shutdown();
        realTimeKey = false; // 关闭实时统计线程
        realTimeThread.join(); // 等待实时统计线程结束

        handleData(); // 处理数据
    }
}
多线程任务类(ThreadTask)
public class ThreadTask implements Runnable {
    @Override
    public void run() {
        rumpUpCountDownLatch.countDown(); // 计数器减一
        try {
            before(); // 前置处理
            while (true) {
                if (ABORT.get() || needStop || executeNum >= totalNum) {
                    break; // 判断是否终止测试任务
                }
                try {
                    long start = System.currentTimeMillis(); // 记录开始时间
                    test(); // 测试方法
                    long end = System.currentTimeMillis(); // 记录结束时间
                    int delay = (int) (end - start);

                    // 上报实时数据
                    TaskExecutor.realTimeCostTime.add(delay);
                    TaskExecutor.realTimeCostTimes.add(1);

                    if (countState) {
                        executeNum++; // 记录执行次数
                        costTime.add(delay); // 记录耗时
                    }
                } catch (Exception e) {
                    if (countState) errorNum++; // 记录错误次数
                    e.printStackTrace();
                }
            }
            after(); // 后置处理
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            stopCountDownLatch.countDown(); // 计数器减一
        }
    }
}
启动脚本(TestEngineDemo)
public class TestEngineDemo {
    public static void main(String[] args) throws InterruptedException {
        int tasksNum = 2; // 任务数,即并发数量
        int totalNum = 30; // 单个任务的总执行次数
        List<ThreadTask> tasks = new ArrayList<>(); // 任务集合

        for (int i = 0; i < tasksNum; i++) {
            ThreadTask threadTask = new ThreadTask() {
                @Override
                public void test() {
                    ThreadTool.sleep(100); // 模拟业务操作
                }
            };
            threadTask.totalNum = totalNum; // 设置任务的总执行次数
            threadTask.costTime = new ArrayList<>(totalNum); // 设置任务的执行时间集合
            tasks.add(threadTask); // 将任务添加到任务集合
        }

        new TaskExecutor(tasks, "性能测试引擎演示", 4).start(); // 创建并发任务执行器并启动
    }
}

控制台输出示例

在测试过程中,控制台会实时输出类似以下内容:

实时统计TPS: 9, 平均耗时: 101
实时统计TPS: 10, 平均耗时: 102
实时统计TPS: 20, 平均耗时: 102
实时统计TPS: 19, 平均耗时: 102
Rump-Up结束, 开始执行测试任务!
实时统计TPS: 19, 平均耗时: 102
实时统计TPS: 20, 平均耗时: 103
实时统计TPS: 20, 平均耗时: 101

总结

通过增加实时展示功能,性能测试引擎能够更好地满足实际测试需求。测试人员可以实时监控系统的性能表现,及时发现并解决问题,从而更高效地完成性能测试任务。

FunTester 原创精华
【连载】从 Java 开始性能测试
故障测试与 Web 前端
服务端功能测试
性能测试专题
Java、Groovy、Go
白盒、工具、爬虫、UI 自动化
理论、感悟、视频
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 2 条回复 时间 点赞

额,我一般是 websocket 或者 sse 推送到前端

小狄子 回复

我们是上报 Prometheus,grafana 展示。

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