package redis.client;
import com.google.common.util.concurrent.ListenableFuture;
import com.sampullara.cli.Args;
import com.sampullara.cli.Argument;
import redis.Command;
import redis.reply.Reply;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Clone redis-benchmark
*/
public class Benchmark {
private static final String help = "" +
"Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-P <pipelined>] [-d <data size>]\n" +
"\n" +
" -h <hostname> Server hostname (default 127.0.0.1)\n" +
" -p <port> Server port (default 6379)\n" +
" -c <clients> Number of parallel connections (default 50)\n" +
" -n <requests> Total number of requests (default 10000)\n" +
" -P <outstanding> Number of outstanding pipeline requests (defaults 1)\n" +
" -d <size> Data size of SET/GET value in bytes (default 3)\n";
@Argument
private static String h = "127.0.0.1";
@Argument
private static Integer p = 6379;
@Argument
private static Integer c = 50;
@Argument
private static Integer n = 10000;
@Argument
private static Integer d = 3;
@Argument
private static Integer P = 1;
private static final long NANOS_PER_MILLI = 1000000l;
private static final int MILLIS_PER_SECOND = 1000;
private static ExecutorService es = Executors.newCachedThreadPool();
private static void benchmark(final String title, final Command command) throws IOException, InterruptedException, ExecutionException {
System.out.println("====== " + title + " ======");
final ArrayList<AtomicInteger> bins = new ArrayList<AtomicInteger>() {
@Override
public synchronized AtomicInteger get(int index) {
if (index > size() - 1) {
int toadd = index - size() + 1;
for (int i = 0; i < toadd; i++) {
add(new AtomicInteger(0));
}
}
return super.get(index);
}
};
List<Callable<Void>> benchmarks = new ArrayList<Callable<Void>>(c);
for (int j = 0; j < c; j++) {
benchmarks.add(new Callable<Void>() {
@Override
public Void call() throws IOException, InterruptedException {
RedisClient redisClient = new RedisClient(h, p);
final Semaphore semaphore = new Semaphore(P);
for (int i = 0; i < n / c; i++) {
final long commandstart = System.nanoTime();
if (P == 1) {
redisClient.execute(title, command);
long commandend = System.nanoTime();
int bin = (int) ((commandend - commandstart) / NANOS_PER_MILLI);
bins.get(bin).incrementAndGet();
} else {
semaphore.acquire(1);
ListenableFuture<? extends Reply> pipeline = redisClient.pipeline(title, command);
pipeline.addListener(new Runnable() {
@Override
public void run() {
long commandend = System.nanoTime();
int bin = (int) ((commandend - commandstart) / NANOS_PER_MILLI);
bins.get(bin).incrementAndGet();
semaphore.release();
}
}, es);
}
}
semaphore.acquire(P);
redisClient.close();
return null;
}
});
}
long start = System.nanoTime();
List<Future<Void>> futures = es.invokeAll(benchmarks);
for (Future<Void> future : futures) {
future.get();
}
long end = System.nanoTime();
double seconds = ((double) end - start) / NANOS_PER_MILLI / MILLIS_PER_SECOND;
double rate = n / seconds;
System.out.printf(" %d requests completed in %.2f seconds\n", n, seconds);
System.out.printf(" %d parallel clients\n", c);
System.out.printf(" %d outstanding requests\n", P);
System.out.printf(" %d bytes payload\n", d);
System.out.println();
double total = 0;
int milli = 0;
for (AtomicInteger bin : bins) {
total += bin.intValue();
if (milli++ == 0) {
System.out.printf("%.2f%% < 1 millisecond\n", total * 100 / n);
} else {
System.out.printf("%.2f%% <= %d milliseconds\n", total * 100 / n, milli);
}
}
System.out.println();
System.out.printf("%.2f requests per second\n\n", rate);
}
public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
List<String> parse;
try {
parse = Args.parse(Benchmark.class, args);
if (parse.size() > 0) {
benchmark(parse.get(0), new Command(parse.toArray()));
} else {
byte[] key = "foo:rand:000000000000".getBytes();
byte[] counter = "counter:rand:000000000000".getBytes();
byte[] list = "mylist".getBytes();
byte[] set = "myset".getBytes();
byte[] data = new byte[d];
Object[] objects = new Object[21];
objects[0] = "MSET";
for (int i = 1; i < objects.length - 1; i += 2) {
objects[i] = key;
objects[i + 1] = data;
}
// Delete it all
RedisClient redisClient = new RedisClient(h, p);
redisClient.del(new Object[] { key, counter, list, set });
redisClient.close();
benchmark("PING (warmup)", new Command(new Object[]{"PING".getBytes()}));
benchmark("PING", new Command(new Object[]{"PING".getBytes()}));
benchmark("MSET", new Command(objects));
benchmark("SET", new Command("SET".getBytes(), key, data));
benchmark("GET", new Command("GET".getBytes(), key));
benchmark("INCR", new Command("INCR".getBytes(), counter));
benchmark("LPUSH", new Command("LPUSH".getBytes(), list, data));
benchmark("LPOP", new Command("LPOP".getBytes(), list));
benchmark("SADD", new Command("SADD".getBytes(), set, counter));
benchmark("SPOP", new Command("SPOP".getBytes(), set));
benchmark("LPUSH (again, in order to bench LRANGE)", new Command("LPUSH".getBytes(), list, data));
benchmark("LRANGE (first 100 elements)", new Command("LRANGE".getBytes(), list, "0".getBytes(), "99".getBytes()));
benchmark("LRANGE (first 300 elements)", new Command("LRANGE".getBytes(), list, "0".getBytes(), "299".getBytes()));
benchmark("LRANGE (first 450 elements)", new Command("LRANGE".getBytes(), list, "0".getBytes(), "449".getBytes()));
benchmark("LRANGE (first 600 elements)", new Command("LRANGE".getBytes(), list, "0".getBytes(), "599".getBytes()));
}
} catch (IllegalArgumentException e) {
System.out.print(help);
System.exit(1);
} finally {
es.shutdown();
}
}
}