/**
* Copyright 2024 Beijing Baolande Software Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Runtime Library Exception to the Apache 2.0 License:
*
* As an exception, if you use this Software to compile your source code and
* portions of this Software are embedded into the binary product as a result,
* you may redistribute such product without providing attribution as would
* otherwise be required by Sections 4(a), 4(b) and 4(d) of the License.
*/
package benchmark_client
let DEFAULT_HOST = "127.0.0.1"
let DEFAULT_PORT: UInt16 = 8090
let DEFAULT_THRED_COUNT = 100
let DEFAULT_TOTAL_REQUEST_COUNT = 10000000
let DEFAULT_MAX_CONNECTION = 1
let message = "Hello, world!"
let HOST_ARG = "--host"
let PORT_ARG = "--port"
let THREAD_COUNT_ARG = "--threadCount"
let TOTAL_REQUEST_COUNT = "--totalRequestCount"
let MAX_CONNECTIONS = "--maxConnections"
let LONG_ARG_LIST: Array<String> =
["host=", "port=", "threadCount=", "totalRequestCount=", "maxConnections="]
/**
*
* @author yangfuping
*/
main(args: Array<String>) {
// LoggerFactory.setLevel(LogLevel.TRACE)
println(
"Usage: benchmark_client/build/release/bin/main --host=127.0.0.1 --port=8090 --maxConnections=1 --threadCount=100 --totalRequestCount=10000000"
)
if (args.size > 0) {
println("Command line args: ${args}")
}
println("Start BenchMarkClient")
let argOpt = ArgOpt(args, "", LONG_ARG_LIST)
var host = DEFAULT_HOST
var port = DEFAULT_PORT
var threadCount = DEFAULT_THRED_COUNT
var totalRequestCount = DEFAULT_TOTAL_REQUEST_COUNT
var maxConnections = DEFAULT_MAX_CONNECTION
if (let Some(argHost) <- argOpt.getArg(HOST_ARG)) {
host = argHost
}
if (let Some(argPort) <- argOpt.getArg(PORT_ARG)) {
port = UInt16.parse(argPort)
}
if (let Some(argThreadCount) <- argOpt.getArg(THREAD_COUNT_ARG)) {
threadCount = Int64.parse(argThreadCount)
}
if (let Some(argTotalRequestCount) <- argOpt.getArg(TOTAL_REQUEST_COUNT)) {
totalRequestCount = Int64.parse(argTotalRequestCount)
}
if (let Some(argMaxConnections) <- argOpt.getArg(MAX_CONNECTIONS)) {
maxConnections = Int64.parse(argMaxConnections)
}
println("Server: ${host}:${port}")
println("Thread count: ${threadCount}")
println("Total request count: ${totalRequestCount}")
println("Max connections: ${maxConnections}")
let config = ClientEndpointConfig()
config.host = host
config.port = port
config.noDelay = true
config.readTimeout = Duration.second * 60
config.writeTimeout = Duration.second * 30
// 是否开启每个连接一个写线程
config.asyncWrite = true
// 是否开启每个连接一个专有的读线程
config.stickyRead = true
// 是否在读线程上执行编解码任务
config.execOnReadThread = true
config.minConnections = maxConnections
config.maxConnections = maxConnections
// 客户端使用的线程池
let threadPool = ThreadPoolFactory.createThreadPool(3, 128, 4096, Duration.minute * 2)
// 创建客户端Endpoint
let tcpEndpoint = ClientTcpEndpoint(config, threadPool)
// 使用4字节记录报文长度的编解码器
let lengthFrameEncoder = LengthBasedFrameEncoder(4)
let lengthFrameDecoder = LengthBasedFrameDecoder(4)
// 判断报文是否包含完整消息的MessageCompletedHandler
tcpEndpoint.setMessageCompletedHandler(lengthFrameDecoder)
// 解析报文长度的IoFilter
tcpEndpoint.addFilter(LengthBasedFrameCodec(lengthFrameEncoder, lengthFrameDecoder))
// 字符串和二进制数组转换的IoFilter
tcpEndpoint.addFilter(ByteAndStringCodec())
// 接收消息并缓存消息到队列中的IoFilter
let clientHandler = EchoRequestHandler()
tcpEndpoint.addFilter(clientHandler)
// 启动客户端Endpoint
tcpEndpoint.start()
println("Start ClientTcpEndpoint")
// 创建会话
let sessions = getSessions(tcpEndpoint, maxConnections)
// 发送消息,并收取对应的响应
for (i in 0..=100) {
let echoRequest = EchoRequest(i, "Message${i}")
println("Send message: ${echoRequest}")
sessions[i % sessions.size].writeAndFlushMessage(echoRequest)
try {
let echoResponse = echoRequest.waitForResponse()
println("Client receive message: ${echoResponse}")
} catch (ex: Exception) {
ex.printStackTrace()
}
}
let barrier = Barrier(threadCount + 1)
var executeNum = totalRequestCount / threadCount
if (totalRequestCount % threadCount > 0) {
executeNum = executeNum + 1
}
let loopCount = executeNum
let finalTotalRequestCount = totalRequestCount
// 启动压测线程
for (i in 1..=threadCount) {
spawn {
=>
let task = PerformanceTask(sessions, finalTotalRequestCount, loopCount, message, barrier)
task.run()
}
}
var startTime = DateTime.now()
barrier.wait()
let startDateFromat = formatDateTime(startTime);
println("${startDateFromat}, start bench mark");
// statistic tps
var lastCount = 0
var lastTime = startTime
while (true) {
sleep(Duration.second * 10);
if (TaskController.stopTime.load() != 0) {
break;
}
let currentCount = TaskController.totalInvokeCount.load()
let now = DateTime.now()
let formatDate = formatDateTime(now)
let delatCount = currentCount - lastCount
let tps = currentCount * 1000 / (now.toUnixTimeStamp().toMilliseconds() -
startTime.toUnixTimeStamp().toMilliseconds())
let delatTps = delatCount * 1000 / (now.toUnixTimeStamp().toMilliseconds() -
lastTime.toUnixTimeStamp().toMilliseconds())
lastCount = currentCount
lastTime = now
println("${formatDate}, total count: ${currentCount}, total tps: ${tps}, immediately tps: ${delatTps}")
if (totalRequestCount != -1 && (currentCount >= totalRequestCount)) {
if (TaskController.running.load()) {
TaskController.running.compareAndSwap(true, false)
}
}
}
let endTimeMills = TaskController.stopTime.load()
let endDateTime = DateTime.fromUnixTimeStamp(Duration.millisecond * endTimeMills)
let endDateFromat = formatDateTime(endDateTime);
let totalCount = TaskController.totalInvokeCount.load()
let totalTps = totalCount * 1000 / (endTimeMills - startTime.toUnixTimeStamp().toMilliseconds());
println("${endDateFromat}, total count: ${totalCount} final tps: ${totalTps}.")
println("Stop BenchMarkClient")
// GC后打HeapDump,查看是否有内存泄露
GC(heavy: false)
sleep(Duration.minute * 5)
}
/**
* 预先创建多个Session
*/
func getSessions(tcpEndpoint: ClientTcpEndpoint, count: Int64): Array<Session> {
let sessionList = ArrayList<Session>()
for (i in 0..count) {
let session = tcpEndpoint.createSession()
println("Created ${i + 1} session")
sessionList.add(session)
}
return sessionList.toArray()
}
func formatDateTime(dateTime: DateTime): String {
let year = dateTime.year
let month = dateTime.month.toInteger()
let day = dateTime.dayOfMonth
let hour = dateTime.hour
let minute = dateTime.minute
let second = dateTime.second
let millSecond = dateTime.nanosecond / (1000 * 1000)
return year.format("04") + "-" + month.format("02") + "-" + day.format("02") + "-" + hour.format("02") + ":" +
minute.format("02") + ":" + second.format("02") + "." + millSecond.format("03")
}