package com.xiaomi.infra.chronos.benchmark;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.xiaomi.infra.chronos.client.ChronosClient;
/**
* The tool to benchmark ChronosServer and print the metrics.
*/
public class BenchmarkChronosServer {
private static final Log LOG = LogFactory.getLog(BenchmarkChronosServer.class);
private ChronosClient chronosClient;
private long startTime;
private AtomicInteger totalCountInteger = new AtomicInteger();
private AtomicInteger totalLatencyInteger = new AtomicInteger();
private long previousTimestamp = 0;
private long currentTimestamp;
private long failoverStartTime = 0;
private String failoverStartTimeString;
private boolean isFailover = false;
/**
* Construct BenchmarkChronosServer with zkQuorum and clusterName.
*
* @param zkQuorum the ZooKeeper quorum string for ChronosClientWatcher
* @throws IOException when error to construct ChronosClientWatcher
*/
public BenchmarkChronosServer(String zkQuorum, String clusterName) throws IOException {
chronosClient = new ChronosClient(zkQuorum, clusterName);
}
/**
* Benchmark ChronosServer and print metrics in another thread.
*/
public void startBenchmark() {
startTime = System.currentTimeMillis();
LOG.info("Start to benchmark chronos server");
Thread t = new Thread() { // new thread to output the metrics
@Override
public void run() {
final long collectPeriod = 10000;
LOG.info("Start another thread to export benchmark metrics every " + collectPeriod / 1000.0
+ " second");
int totalCount;
int totalLatency;
long exportTime;
int lastTotalCount = 0;
int lastTotalLatency = 0;
long lastExportTime = startTime;
while (true) {
try {
Thread.sleep(collectPeriod);
} catch (InterruptedException e) {
LOG.error("Interrupt when sleep to get benchmark metrics, exit immediately");
System.exit(0);
}
exportTime = System.currentTimeMillis();
totalCount = totalCountInteger.get();
totalLatency = totalLatencyInteger.get();
double totalCostTime = (exportTime - startTime) / 1000.0;
double costTime = (exportTime - lastExportTime) / 1000.0;
double qps = (totalCount - lastTotalCount) / costTime;
double latency = (totalLatency - lastTotalLatency) * 1.0 / (totalCount - lastTotalCount);
System.out.println("Total " + totalCostTime + ", in " + costTime + " seconds, qps: "
+ qps + ", latency: " + latency + "ms");
lastTotalCount = totalCount;
lastTotalLatency = totalLatency;
lastExportTime = exportTime;
}
}
};
t.setDaemon(true);
t.start();
while (true) {
try {
long start = System.currentTimeMillis();
currentTimestamp = chronosClient.getTimestamp();
totalCountInteger.incrementAndGet();
totalLatencyInteger.addAndGet((int) (System.currentTimeMillis() - start));
if (currentTimestamp <= previousTimestamp) { // check correctness
LOG.error("Fatal error to get a lower timestamp " + currentTimestamp + "(previous is "
+ previousTimestamp + "), exit immediately");
System.exit(0);
}
previousTimestamp = currentTimestamp;
if (isFailover == true) { // calculate failover time
double failoverTime = (System.currentTimeMillis() - failoverStartTime) / 1000.0;
System.out.println("After " + failoverStartTimeString + ", the total failover time is "
+ failoverTime + " seconds");
}
isFailover = false;
} catch (IOException e) {
LOG.error("Exception to get timestamp");
if (isFailover == false) {
failoverStartTime = System.currentTimeMillis();
failoverStartTimeString = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(
failoverStartTime));
LOG.info("Failover occurs at " + failoverStartTimeString);
}
isFailover = true;
}
}
}
/**
* The command-line tool to benchmark ChronosServer and print metrics.
* Usage: mvn exec:java -Dexec.mainClass="com.xiaomi.infra.chronos.benchmark.BenchmarkChronosServer" -Dexec.args="$zkQuorum $clientNumber"
*
* @param argv the first one is zkQuorum and the optional second one is clientNumber
*/
public static void main(String[] argv) {
if (argv.length < 2 || argv.length > 3) {
System.err.println("Wrong parameters, exit immediately");
return;
}
final String zkQuorum = argv[0];
final String clusterName = argv[1];
int clientNumber = 1;
if (argv.length == 3) {
clientNumber = Integer.parseInt(argv[2]);
}
for (int i = 0; i < clientNumber; ++i) {
new Thread() {
@Override
public void run() {
try {
BenchmarkChronosServer benchmark = new BenchmarkChronosServer(zkQuorum, clusterName);
benchmark.startBenchmark();
} catch (IOException e) {
System.err.println("Error to connect with ZooKeeper or ChronosServer to benchmark");
}
}
}.start();
}
}
}