package voldemort.performance;
import java.io.File;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import voldemort.TestUtils;
import voldemort.serialization.IdentitySerializer;
import voldemort.serialization.StringSerializer;
import voldemort.server.VoldemortConfig;
import voldemort.store.StorageConfiguration;
import voldemort.store.StorageEngine;
import voldemort.store.Store;
import voldemort.store.bdb.BdbStorageConfiguration;
import voldemort.store.serialized.SerializingStore;
import voldemort.utils.ByteArray;
import voldemort.utils.CmdUtils;
import voldemort.utils.Props;
import voldemort.utils.ReflectUtils;
import voldemort.utils.Utils;
import voldemort.versioning.ObsoleteVersionException;
import voldemort.versioning.VectorClock;
import voldemort.versioning.Versioned;
import cern.jet.random.engine.DRand;
import cern.jet.random.engine.RandomEngine;
/**
* An in-process performance test for voldemort storage engines
*
* @author jay
*
*/
public class StorageEnginePerformanceTest {
public static void main(String[] args) throws Exception {
try {
OptionParser parser = new OptionParser();
parser.accepts("help", "print usage information");
parser.accepts("requests", "[REQUIRED] number of requests to execute")
.withRequiredArg()
.ofType(Integer.class);
parser.accepts("num-values", "[REQUIRED] number of values in the store")
.withRequiredArg()
.ofType(Integer.class);
parser.accepts("data-dir", "Data directory for storage data")
.withRequiredArg()
.describedAs("directory");
parser.accepts("threads", "number of threads").withRequiredArg().ofType(Integer.class);
parser.accepts("storage-configuration-class",
"[REQUIRED] class of the storage engine configuration to use [e.g. voldemort.store.bdb.BdbStorageConfiguration]")
.withRequiredArg()
.describedAs("class_name");
parser.accepts("props", "Properties file with configuration for the engine")
.withRequiredArg()
.describedAs("config.properties");
parser.accepts("value-size", "The size of the values in the store")
.withRequiredArg()
.describedAs("size")
.ofType(Integer.class);
parser.accepts("cache-width", "Percentage of requests to save as possible re-requests")
.withRequiredArg()
.describedAs("width")
.ofType(Integer.class);
parser.accepts("cache-hit-ratio",
"Percentage of requests coming from the last cache-width requests")
.withRequiredArg()
.describedAs("ratio")
.ofType(Double.class);
parser.accepts("clean-up", "Delete data directory when done.");
OptionSet options = parser.parse(args);
if(options.has("help")) {
parser.printHelpOn(System.out);
System.exit(0);
}
CmdUtils.croakIfMissing(parser, options, "requests");
final int numThreads = CmdUtils.valueOf(options, "threads", 10);
final int numRequests = (Integer) options.valueOf("requests");
final int numValues = (Integer) options.valueOf("num-values");
final int valueSize = CmdUtils.valueOf(options, "value-size", 1024);
final int cacheWidth = CmdUtils.valueOf(options, "cache-width", 100000);
final double cacheHitRatio = CmdUtils.valueOf(options, "cache-hit-ratio", 0.5);
final String propsFile = (String) options.valueOf("props");
final boolean cleanUp = options.has("clean-up");
final String storageEngineClass = CmdUtils.valueOf(options,
"storage-configuration-class",
BdbStorageConfiguration.class.getName())
.trim();
File dataDir = null;
if(options.has("data-dir"))
dataDir = new File((String) options.valueOf("data-dir"));
else
dataDir = TestUtils.createTempDir();
System.out.println("Data dir: " + dataDir);
// create the storage engine
Props props = new Props();
if(propsFile != null)
props = new Props(new File(propsFile));
props.put("node.id", 0);
props.put("data.directory", dataDir.getAbsolutePath());
props.put("voldemort.home", System.getProperty("user.dir"));
VoldemortConfig config = new VoldemortConfig(props);
StorageConfiguration storageConfig = (StorageConfiguration) ReflectUtils.callConstructor(ReflectUtils.loadClass(storageEngineClass),
new Object[] { config });
StorageEngine<ByteArray, byte[], byte[]> engine = storageConfig.getStore(TestUtils.makeStoreDefinition("test"),
TestUtils.makeSingleNodeRoutingStrategy());
@SuppressWarnings("unchecked")
final Store<String, byte[], byte[]> store = new SerializingStore(engine,
new StringSerializer(),
new IdentitySerializer(),
null);
final byte[] value = new byte[valueSize];
new Random().nextBytes(value);
// initialize test data
for(int i = 0; i < numValues; i++)
store.put(Integer.toString(i), Versioned.value(value), null);
// initialize cache lookback data
int[] recents = new int[cacheWidth];
System.out.println("Write test:");
CachedPerformanceTest writeTest = new CachedPerformanceTest(new PerformanceTest() {
@Override
public void doOperation(int index) {
try {
String key = Integer.toString(index);
List<Versioned<byte[]>> vs = store.get(key, null);
VectorClock version;
if(vs.size() == 0)
version = new VectorClock();
else
version = (VectorClock) vs.get(0).getVersion();
version.incrementVersion(0, 847584375);
store.put(key, Versioned.value(value, version), null);
} catch(ObsoleteVersionException e) {
// do nothing
} catch(RuntimeException e) {
e.printStackTrace();
throw e;
}
}
}, recents, numValues, cacheHitRatio);
writeTest.run(numRequests, numThreads);
writeTest.printStats();
System.out.println();
System.out.println("Read test:");
CachedPerformanceTest readTest = new CachedPerformanceTest(new PerformanceTest() {
@Override
public void doOperation(int index) {
store.get(Integer.toString(index), null);
}
}, recents, numValues, cacheHitRatio);
readTest.run(numRequests, numThreads);
readTest.printStats();
if(cleanUp)
Utils.rm(dataDir);
} catch(Exception e) {
e.printStackTrace();
System.exit(1);
}
}
public static class CachedPerformanceTest extends PerformanceTest {
private RandomEngine random = new DRand(1);
private PerformanceTest test;
private int[] cache;
private int size;
private double cachePercent;
private AtomicInteger uncached = new AtomicInteger(0);
public CachedPerformanceTest(PerformanceTest test,
int[] cache,
int size,
double cachePercent) {
this.test = test;
this.cache = cache;
this.size = size;
this.cachePercent = cachePercent;
}
@Override
public void doOperation(int index) throws Exception {
int req;
if(random.nextFloat() < cachePercent && index > 1) {
int maxValid = Math.min(Math.min(Math.max(cache.length, index - 1),
uncached.get() - 1), cache.length);
int cacheIndex = Math.abs(random.nextInt()) % maxValid;
req = cache[cacheIndex];
} else {
req = Math.abs(random.nextInt()) % size;
cache[uncached.getAndIncrement() % cache.length] = req;
}
test.doOperation(req);
}
}
}