How to Build Accurate QPS and RT Samplers for Java Performance Tests

This article explains a refined approach for implementing QPS and response‑time samplers in a Java performance‑testing framework, covering static and dynamic load models, the use of LongAdder, thread‑count utilities, and detailed code examples for reliable metric collection.

FunTester
FunTester
FunTester
How to Build Accurate QPS and RT Samplers for Java Performance Tests

Idea

The original QPS sampler relied on an asynchronous utility ( com.funtester.frame.execute.Progress) to gather per‑thread response times and compute QPS from thread count, but sample size variations caused inaccurate results in mixed‑interface load tests. A new design separates static and dynamic load models.

For the static model, an RT sampler is re‑implemented using two switches in com.funtester.base.constaint.ThreadBase ( #INTERCEPT and #COUNT) to control sampling state via an asynchronous console thread.

For the dynamic model, QPS is measured with a LongAdder (as described in a related article) whose value is read by an async thread and reset each interval.

The dynamic thread model leverages ThreadBase#executeNum as a per‑thread counter, allowing a simple sum of alive thread objects without additional synchronization.

Implementation

Static Model

The sampler method collects response times, toggles the intercept flag, sleeps for the measurement window, logs QPS and RT statistics, and clears the collection.

/**
 * 取样器
 *
 * @param time
 */
public static void statistic(int time) {
    INTERCEPT = true;
    sleep(time);
    INTERCEPT = false;
    CountUtil.FunIndex index = CountUtil.index(interceptCosts);
    logger.info("当前QPS:{}", interceptCosts.size() / time);
    logger.info("当前RT:{}", index.toString());
    interceptCosts = new Vector<>();
}

The implementation uses com.funtester.utils.CountUtil#index to compute response‑time statistics.

Dynamic QPS Model

A previous Disruptor‑based dynamic QPS framework was abandoned due to high maintenance cost. The new framework logs both designed and actual QPS, along with active thread count, using a LongAdder that is reset each loop interval.

if (index++ % LOOP_INTERVAL == 0) {
    logger.info("当前设计QPS:{},实际QPS:{} 活跃线程数:{}", qps, total.sumThenReset() / LOOP_INTERVAL as int, executor.getActiveCount());
}

The counter com.funtester.frame.execute.FunEventConcurrent#total serves as the per‑task statistic holder, preparing the framework for future multi‑task scenarios.

Dynamic Thread Model

The thread model simply aggregates the executeNum values of all thread objects to obtain the current task count and QPS, without needing thread‑safety mechanisms.

/**
 * 获取实时当然任务池信息
 */
public static synchronized void printInfo() {
    long s = threads.stream().collect(Collectors.summarizingInt(f -> f.executeNum)).getSum();
    sleep(1);
    long e = threads.stream().collect(Collectors.summarizingInt(f -> f.executeNum)).getSum();
    logger.info("当前任务数:{} QPS:{}", aliveSize(), e - s);
}

An inner class FunTester implements IFunController and processes console commands ( +, -, *) to add, reduce, or stop tasks, printing pool information after each operation.

private static class FunTester implements IFunController {
    boolean key = true;
    @Override
    public void run() {
        while (key) {
            String input = getInput();
            switch (input) {
                case "+":
                    add();
                    break;
                case "-":
                    reduce();
                    break;
                case "*":
                    over();
                    key = false;
                    break;
                default:
                    if (Regex.isMatch(input, "(F|f)\\d+"))
                        THREAD_STEP = changeStringToInt(input.substring(1));
                    break;
            }
            FunThread.printInfo();
        }
    }
    @Override
    public void add() {
        range(THREAD_STEP).forEach(f -> { addTask(); sleep(0.5); });
    }
    @Override
    public void reduce() {
        range(THREAD_STEP).forEach(f -> { removeTask(); sleep(0.5); });
    }
    @Override
    public void over() {
        FunThread.stop();
        logger.info("动态结束任务!");
    }
}
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBackend DevelopmentPerformance TestingQPSRT Sampler
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.