Dynamic increase and decrease of dynamic model [FunTester test framework]

thinking

First, abandon the original model structure and treat each multithreaded task as a manageable object. There needs to be an interrupt method, and then there is a global running state management class, including the ability to add, delete and terminate a single multithreaded task.

Trigger different management methods through an external factor: for example, add use cases, and then randomly clone a task from the task pool (a task will be selected later) and put it back into the task pool.

The local version of FunTester test framework takes keyboard input as an external factor, and the distributed service FunTester test framework takes interface request as an external factor. This demonstration of the local version.

The operation process is as follows:

  • Some small tasks constitute the basic pressure and start to be executed
  • Start an additional thread to process parameters passed by external factors
  • Control the increase, decrease or termination of multithreaded task pool through external factors

reform

Multithreaded task class

Firstly, the basic class of multithreaded tasks is modified. I re wrote a subclass com.fundster.base.constaint.fundthread of com.fundster.base.constaint.threadbase, which is specially used to create dynamic model tasks. There are two types of transformation here: 1. Add the termination attribute com.fundster.base.constant.fundthread#break_ Key and the corresponding method com.fundster.base.constant.fundthread#interrupt; 2. It simplifies the com.fundster.base.persistent.fundthread#run method and transforms it into a method that will run all the time to avoid termination in cloning.

package com.funtester.base.constaint;

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.Vector;

public abstract class FunThread<F> extends ThreadBase {

    private static final long serialVersionUID = 7878297575504772944L;

    private static final Logger logger = LogManager.getLogger();

    /**
     * Unified management of all surviving threads
     */
    private static Vector<FunThread> threads = new Vector<>();

    /**
     * The single thread interrupt switch is used to dynamically adjust the concurrency pressure. The default value is false
     */
    private boolean BREAK_KEY = false;

    public FunThread(F f, String name) {
        this.isTimesMode = true;
        this.threadName = name;
        this.limit = Integer.MAX_VALUE;
        this.f = f;
    }

    protected FunThread() {
        super();
    }


    @Override
    public void run() {
        before();
        while (!BREAK_KEY) {
            try {
                doing();
            } catch (Exception e) {
                logger.warn("Task execution failed!", e);
            }
        }
    }

    /**
     * Preparation before running the method to be tested
     */
    public void before() {
    }

    /**
     * The dynamic model does not end normally
     */
    protected void after() {
    }


    private static synchronized boolean checkName(String name) {
        for (FunThread thread : threads) {
            String threadName = thread.threadName;
            if (StringUtils.isAnyBlank(threadName, name) || threadName.equalsIgnoreCase(name)) {
                return false;
            }
        }
        return true;
    }

    /**
     * The copy object method is used to count the number of requests and successes when a single object is called by multiple threads. For complex situations < T >, the T type also needs to be rewritten to the clone method
     *
     * @return
     */
    @Override
    public abstract FunThread clone();

    /**
     * Thread termination is used to dynamically adjust concurrency pressure
     */
    public void interrupt() {
        BREAK_KEY = true;
    }


}

management function

The management function is currently written in the com.fundster.base.persistent.fundthread class. All running tasks are stored through a java.util.Vector collection as a task pool, adding methods of adding, deleting, querying, terminating and cloning.

/**
 * Used to terminate the test early in some cases
 */
public static synchronized void stop() {
    threads.forEach(f -> f.interrupt());
    threads.clear();
}

public static synchronized boolean addThread(FunThread base) {
    if (!checkName(base.threadName)) return false;
    return threads.add(base);
}

/**
 * Delete a task or stop it
 *
 * @param base
 */
public static synchronized void remoreThread(FunThread base) {
    base.interrupt();
    threads.remove(base);
}

public static synchronized FunThread find(String name) {
    for (int i = 0; i < threads.size(); i++) {
        FunThread funThread = threads.get(i);
        if (StringUtils.isNoneBlank(funThread.threadName, name) && funThread.threadName.equalsIgnoreCase(name)) {
            return funThread;
        }
    }
    return null;
}

public static synchronized void remoreThread(String name) {
    FunThread funThread = find(name);
    if (funThread == null) remoreThread(funThread);
}

public static synchronized FunThread getRandom() {
    return random(threads);
}

public static synchronized int aliveSize() {
    return threads.size();
}


Dynamic execution class

Because the execution class does not need statistics, it only needs to manage the task pool, so it is very simple.

package com.funtester.frame.execute;

import com.funtester.base.constaint.FunThread;
import com.funtester.base.interfaces.IFunController;
import com.funtester.config.HttpClientConstant;
import com.funtester.frame.SourceCode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;

/**
 * Startup class of dynamic pressure measurement model
 */
public class FunConcurrent extends SourceCode {

    private static Logger logger = LogManager.getLogger(FunConcurrent.class);

    /**
     * Task set
     */
    public List<FunThread> threads = new ArrayList<>();

    /**
     * Thread pool
     */
    public static ExecutorService executorService;

    public static IFunController controller;

    /**
     * @param threads Thread group
     */
    public FunConcurrent(List<FunThread> threads) {
        this.threads = threads;
        executorService = ThreadPoolUtil.createCachePool(HttpClientConstant.THREADPOOL_MAX);
    }

    private FunConcurrent() {

    }

    /**
     * Perform multithreaded tasks
     * By default, the thread object in the list is taken and thrown into the thread pool to complete multi-threaded execution. If there is no threadname,name defaults to desc + number of threads as the threadname and removes the end date
     */
    public void start() {
        if (controller == null) controller = new FunTester();
        new Thread(controller,"receiver").start();
        threads.forEach(f -> addTask(f));
    }

    public static void addTask(FunThread thread) {
        boolean b = FunThread.addThread(thread);
        logger.info("task{}add to{}", thread.threadName, b ? "success" : "fail");
        if (b) executorService.execute(thread);
    }

    public static void addTask() {
        FunThread thread = FunThread.getRandom();
        addTask(thread.clone());
    }

    public static void removeTask(FunThread thread) {
        logger.info("task{}Terminated", thread.threadName);
        FunThread.remoreThread(thread);
    }

    public static void removeTask(String name) {
        logger.info("task{}Terminated", name);
        FunThread.remoreThread(name);
    }

    public static void removeTask() {
        FunThread thread = FunThread.getRandom();
        removeTask(thread);
    }


}

Multithreaded class for handling external factors

My implementation here is relatively simple. I only realize the functions of adding one, subtracting one and terminating. Subsequently, I will increase the function of batch increase and decrease, and dynamically introduce pressure test tasks from Groovy scripts. Of course, this depends on more refined task pool management.

    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:
                        break;
                }
            }
        }

        @Override
        public void add() {
            addTask();
        }

        @Override
        public void reduce() {
            removeTask();
        }

        @Override
        public void over() {
            logger.info("Dynamic end task!");
            FunThread.stop();
        }

    }

The basic functions have been realized. Let's test it.

test

Test script

I use two tasks as basic tasks, then perform pressure test and output control cases through keyboard. The video demo version is at the back, or go to station B and follow me on the video number. They are all called fundtester.

package com.funtest.funthead;

import com.funtester.base.constaint.FunThread;
import com.funtester.frame.SourceCode;
import com.funtester.frame.execute.FunConcurrent;

import java.util.Arrays;

public class Ft extends SourceCode {

    public static void main(String[] args) {
        FunTester e2 = new FunTester("task A");
        FunTester e22 = new FunTester("task B");
        new FunConcurrent(Arrays.asList(e2, e22)).start();
    }

    private static class FunTester extends FunThread {

        public FunTester(String name) {
            super(null, name);
        }

        @Override
        protected void doing() throws Exception {
            sleep(3.0 + getRandomDouble());
            output(threadName + TAB + "Task is running!");
        }

        @Override
        public FunThread clone() {
            return new FunTester(this.threadName + "Clone");
        }

    }

}

console output

The following only shows some necessary information. The use case must be terminated manually or triggered by the outside world. The reason for cloning failure is that the task name is duplicate. The plan uses the task name as the flag of the task for management, so it cannot be duplicate. To avoid repetition, you can realize the randomness and uniqueness of name assignment in com.funtest.funhead.ft.funtester#clone.

INFO-> main Current user: oker´╝îWorking directory:/Users/oker/IdeaProjects/funtester/,System coding format:UTF-8,system Mac OS X edition:10.16
INFO-> main task task A Added successfully
INFO-> main task task B Added successfully
INFO-> FT-2   task B Task is running!
INFO-> FT-1   task A Task is running!
+
INFO-> Receiver input:+
INFO-> Receiver task task A Clone added successfully
INFO-> FT-2   task B Task is running!
INFO-> FT-1   task A Task is running!
INFO-> FT-3   task A Clone task is running!
+
INFO-> Receiver input:+
INFO-> Receiver task task A Clone addition failed
INFO-> FT-2   task B Task is running!
INFO-> FT-1   task A Task is running!
INFO-> FT-3   task A Clone task is running!
+
INFO-> Receiver input:+
INFO-> Receiver task task B Clone added successfully
INFO-> FT-3   task A Clone task is running!
INFO-> FT-2   task B Task is running!
INFO-> FT-4   task B Clone task is running!
INFO-> FT-1   task A Task is running!
_
INFO-> Receiver input:_
INFO-> FT-3   task A Clone task is running!
-
INFO-> Receiver input:-
INFO-> Receiver task task B The clone was terminated
INFO-> FT-4   task B Clone task is running!
INFO-> FT-2   task B Task is running!
INFO-> FT-1   task A Task is running!

Process finished with exit code 130 (interrupted by signal 2: SIGINT)

Posted by Andrew R on Fri, 19 Nov 2021 04:32:31 -0800