package com.rubiconproject.oss.kv.server.main; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Option; import org.springframework.context.support.AbstractApplicationContext; import com.rubiconproject.oss.kv.KeyValueStore; import com.rubiconproject.oss.kv.transcoder.GzippingTranscoder; import com.rubiconproject.oss.kv.transcoder.StringTranscoder; import com.rubiconproject.oss.kv.transcoder.Transcoder; import com.rubiconproject.oss.kv.util.StreamUtils; public class ClientBenchmark extends BaseKVServerMain { private static final String[] defaultClientSpringPaths = new String[] { "/com/othersonline/kv/server/applicationContext-benchmark.xml" }; private Log log = LogFactory.getLog(getClass()); @Option(name = "--backend", usage = "backend to test") private String backend; @Option(name = "--concurrency", usage = "# of concurrent threads (default is 10)") private int concurrency = 10; @Option(name = "--repetitions", usage = "repetitions per thread (default is 100)") private int repetitions = 100; @Option(name = "--size", usage = "message size in bytes (default is 1024 * 10)") private int byteCount = 1024 * 10; @Option(name = "--gzip", usage = "use gzipping transcoder (default is false)") private boolean gzip = false; @Option(name = "--skip-get", usage = "do not call get() (default is false)") private boolean skipGet = false; @Option(name = "--skip-delete", usage = "do not call delete() (default is false)") private boolean skipDelete = false; public static void main(String[] args) throws Exception { ClientBenchmark cb = new ClientBenchmark(); int returnValue = cb.execute(args); System.exit(returnValue); } private int execute(String[] args) throws Exception { CmdLineParser parser = new CmdLineParser(this); parser.setUsageWidth(80); try { parser.parseArgument(args); AbstractApplicationContext ctx = getContext(defaultClientSpringPaths); KeyValueStore store = (KeyValueStore) ctx.getBean(backend); TestResult result = doTestStorageBackend(store, byteCount, concurrency, repetitions, !skipGet, !skipDelete); System.out.println("Backend\tDuration\tErrors"); System.out.println(String.format("%1$s\t%2$d\t%3$d", result.identifier, result.duration, result.errorCount)); return 0; } catch (CmdLineException e) { System.err.println("Usage: dshell [options...] arguments..."); parser.printUsage(System.err); log.error("Could not parse options:", e); return 1; } } private TestResult doTestStorageBackend(KeyValueStore store, int byteCount, int concurrency, int repetitions, boolean doGet, boolean doDelete) throws Exception { ExecutorService executor = Executors.newFixedThreadPool(concurrency); List<Future<TestResult>> futures = new ArrayList<Future<TestResult>>( concurrency); String fullContent = StreamUtils .getResourceAsString("/com/othersonline/kv/test/resources/lorem-ipsum.txt"); String content = fullContent.substring(0, byteCount); List<Callable<TestResult>> callables = new ArrayList<Callable<TestResult>>( concurrency); for (int i = 0; i < concurrency; ++i) { Callable<TestResult> c = new Callable<TestResult>() { private Random random = new Random(); private Transcoder stringTranscoder = new StringTranscoder(); private Transcoder gzipTranscoder = new GzippingTranscoder( stringTranscoder); private KeyValueStore store; private int repetitions = 1; private String content; private boolean gzip = false; private boolean doGet; private boolean doDelete; public Callable init(KeyValueStore store, int repetitions, String content, boolean gzip, boolean doGet, boolean doDelete) { this.store = store; this.repetitions = repetitions; this.content = content; this.gzip = gzip; this.doGet = doGet; this.doDelete = doDelete; return this; } public TestResult call() { TestResult result = new TestResult(store.getIdentifier()); Transcoder transcoder = (gzip) ? gzipTranscoder : stringTranscoder; long start = System.currentTimeMillis(); for (int i = 0; i < repetitions; ++i) { try { String key = String.format( "/some.key.%1$d.%2$d.txt", random.nextInt(), i); store.set(key, content, transcoder); if (doGet) { String s = (String) store.get(key, transcoder); } if (doDelete) store.delete(key); } catch (Exception e) { e.printStackTrace(); result.addError(); } } result.setDuration(System.currentTimeMillis() - start); return result; } }.init(store, repetitions, content, gzip, doGet, doDelete); callables.add(c); } long start = System.currentTimeMillis(); for (Callable<TestResult> c : callables) { Future<TestResult> future = executor.submit(c); futures.add(future); } TestResult result = new TestResult(store.getIdentifier()); for (Future<TestResult> future : futures) { TestResult tr = future.get(10l, TimeUnit.MINUTES); result.addErrors(tr.getErrorCount()); } result.setDuration(System.currentTimeMillis() - start); return result; } private static class TestResult { private String identifier; private long duration; private int errorCount = 0; public TestResult(String identifier) { this.identifier = identifier; } public String getIdentifier() { return identifier; } public long getDuration() { return duration; } public void setDuration(long duration) { this.duration = duration; } public int getErrorCount() { return errorCount; } public void addError() { errorCount += 1; } public void addErrors(int errors) { errorCount += errors; } } }