How to Build a Distributed Performance Testing Framework with FunTester and Docker
This article walks through the design and implementation of a Docker‑based distributed performance testing framework using FunTester, detailing the master‑scheduler, slave agents, test case packaging, code examples, and sample results for load‑testing a target service.
The article documents a practical implementation of a distributed performance testing framework built on the FunTester testing suite. The solution follows the scenario described in a previous design article and extends it with concrete code, Docker images, and a three‑component architecture: a master scheduler, slave test agents, and the target service under test.
Architecture Overview
Master Scheduler : Handles test case management and task distribution.
Slave Test Agent : Receives tasks from the master, executes the test cases, and reports results.
Server (Target Service) : Exposes the API endpoints that are exercised by the test agents.
Part 1 – Docker Image
The Docker image used for the framework is the same as described in the earlier article "Distributed Performance Testing Framework Validation (Part 1)"; the details are omitted here.
Part 2 – Master Scheduler Implementation
The master provides a simple HTTP interface that returns a test case (currently a raw JSON object) to slave agents. It uses a fixed‑thread, fixed‑request‑count load model.
Two demonstration demos are provided:
Expose the number of executions ( times), thread count ( thread), and ramp‑up time ( runup) as parameters.
Parameterize the request URL within the task, allowing a basic compatibility extension for single‑endpoint performance testing.
The master runs on top of the FunTester Moco server, which is currently in internal testing.
Master Script (Groovy)
package com.mocofun.moco.main
import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONObject
import com.funtester.base.bean.Result
import com.funtester.httpclient.FunLibrary
import com.funtester.httpclient.FunRequest
import com.mocofun.moco.MocoServer
class DcsServer2 extends MocoServer {
public static void main(String[] args) {
def server = getServer(12345)
def res = new JSONObject()
res.path = "com.funtester.main.DcsCase.main"
res.paramsType = ["java.lang.Integer", 10, "java.lang.Integer", 100, "java.lang.Integer", 2]
def success = Result.success(res)
output(success.toJson())
def res2 = new JSONObject()
res2.path = "com.funtester.main.DcsCase.main"
res2.paramsType = ["java.lang.Integer", 10, "java.lang.Integer", 100, "java.lang.Integer", 2, "java.lang.String", "http://192.168.80.169:12345/m"]
def success2 = Result.success(res2)
def res3 = new JSONObject()
res3.path = "com.funtester.main.DcsCase.main"
res.paramsType = ["java.lang.String", "10,100,1"]
def success3 = Result.success(res3)
server.get(urlStartsWith("/m")).response(obRes(success))
server.get(urlStartsWith("/t")).response(obRes(success2))
server.get(urlStartsWith("/f")).response(obRes(success3))
def run = run(server)
waitForKey("fun")
run.stop()
}
}The endpoint http://192.168.80.169:12345/m is the test service exposed by the FunTester Moco server.
Test Case Definition
The test case class com.funtester.main.DcsCase.main resides in a JAR and is invoked via reflection. The source code is:
import com.alibaba.fastjson.JSONObject
import com.funtester.config.Constant
import com.funtester.db.mysql.MySqlTest
import com.funtester.frame.execute.Concurrent
import com.funtester.frame.thread.RequestThreadTimes
import com.funtester.httpclient.ClientManage
import com.funtester.httpclient.FunLibrary
import com.funtester.utils.ArgsUtil
import org.apache.http.client.methods.HttpGet
class DcsCase extends FunLibrary {
public static void main(String[] args) {
ClientManage.init(60, 60, 0, "", 0);
ArgsUtil util = new ArgsUtil(args);
int thread = util.getIntOrdefault(0, 2);
int times = util.getIntOrdefault(1, 10);
Constant.RUNUP_TIME = util.getIntOrdefault(2, 10);
String url = util.getStringOrdefault(3, "http://192.168.80.169:12345/m");
MySqlTest.LOG_KEY = false;
JSONObject params = new JSONObject();
params.put("name", "FunTester");
params.put("password", "123456798");
HttpGet httpGet = getHttpGet(url, params);
RequestThreadTimes requestThreadTimes = new RequestThreadTimes(httpGet, times);
Concurrent funTester = new Concurrent(requestThreadTimes, thread, "FunTester test reflective execution");
funTester.start();
}
public static void main(String args) { main(args.split(COMMA)); }
public static void main(Integer a, Integer b, Integer c) { main(new String[]{a + EMPTY, b + EMPTY, c + EMPTY}); }
public static void main(Integer a, Integer b, Integer c, String url) { main(new String[]{a + EMPTY, b + EMPTY, c + EMPTY, url}); }
}Multiple overloaded main methods are provided to demonstrate different parameter passing techniques; primitive types are deliberately avoided.
Slave Agent Implementation
The slave continuously polls the master for tasks, parses the returned JSON, and invokes the specified method via reflection.
package com.funtest.groovytest
import com.alibaba.fastjson.JSONArray
import com.funtester.frame.execute.ExecuteSource
import com.funtester.httpclient.FunLibrary
class Dcs2 extends FunLibrary {
public static void main(String[] args) {
while (true) {
String url = "http://host.docker.internal:12345/m"
// Request the interface to obtain a raw test case
def get = getHttpGet(url)
def response = getHttpResponse(get)
if (response.getInteger("code") == 0) {
def data = response.getJSONObject("data")
def path = data.getString("path")
JSONArray array = data.getJSONArray("paramsType")
ExecuteSource.executeMethod(path, array.toArray())
}
sleep(5.0)
}
}
}The console output shows successful execution of 10 threads, 973 total requests, 0 errors, and performance metrics such as QPS and latency.
Server (Target Service) Logs
Request received:
GET /m HTTP/1.1
Host: 192.168.80.169:12345
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.6 (Java/1.8.0_281)
Accept-Encoding: gzip,deflate
content-length: 0
Response return:
HTTP/1.1 200
Content-Length: 18
Content-Type: text/plain; charset=utf-8
hello funtester!!!The article concludes with a performance chart (image omitted) and notes that further enhancements are planned for the third implementation phase.
Key Takeaways
Using Docker containers simplifies deployment of the master and slave components.
Packaging test cases inside a JAR and exposing them via a lightweight HTTP API enables flexible, language‑agnostic load generation.
Overloading main methods demonstrates how to pass complex parameter sets without relying on primitive types.
The framework currently supports a fixed‑thread, fixed‑request‑count model but can be extended to more sophisticated load patterns.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
