package net.jxta.impl.cm;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import net.jxta.id.IDFactory;
import net.jxta.peer.PeerID;
import net.jxta.peergroup.PeerGroup;
import net.jxta.peergroup.PeerGroupID;
import net.jxta.test.util.JUnitRuleMockery;
import org.jmock.Expectations;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public abstract class AbstractSrdiIndexBackendLoadTest {
@Rule
public JUnitRuleMockery mockContext = new JUnitRuleMockery();
@Rule
public TemporaryFolder testFileStore = new TemporaryFolder();
private File storeRoot;
private String oldSrdiImplName;
@Before
public void setUp() throws Exception {
storeRoot = testFileStore.getRoot();
assertNotNull(storeRoot);
oldSrdiImplName = System.getProperty(Srdi.SRDI_INDEX_BACKEND_SYSPROP);
System.setProperty(Srdi.SRDI_INDEX_BACKEND_SYSPROP, getSrdiIndexBackendClassname());
}
@After
public void tearDown() throws Exception {
if(oldSrdiImplName != null) {
System.setProperty(Srdi.SRDI_INDEX_BACKEND_SYSPROP, oldSrdiImplName);
} else {
System.clearProperty(Srdi.SRDI_INDEX_BACKEND_SYSPROP);
}
}
protected abstract String getSrdiIndexBackendClassname();
@Test
public void testAddPerformance() throws IOException {
Srdi index = new Srdi(createGroup(PeerGroupID.defaultNetPeerGroupID, "group"), "testIndex");
File resultsFile = File.createTempFile("perftest_" + index.getBackendClassName(), ".csv", new File("."));
FileWriter writer = null;
try {
writer = new FileWriter(resultsFile);
add10000Records(index, writer);
measureAddRemoveTime(index, writer);
} finally {
if(writer != null) {
writer.close();
}
}
index.stop();
}
/**
* Adds 100 records for a new random peer ID, then removes that peer ID, recording the time taken per
* operation. Repeats this for 1000 peers.
*/
private void measureAddRemoveTime(Srdi index, FileWriter writer) throws IOException {
List<Long> addTimes = new LinkedList<Long>();
List<Long> removeTimes = new LinkedList<Long>();
StatsTracker addTracker = new StatsTracker();
StatsTracker removeTracker = new StatsTracker();
for(int peerNum=0; peerNum < 100; peerNum++) {
PeerID pid = IDFactory.newPeerID(PeerGroupID.defaultNetPeerGroupID);
for(int opNum=0; opNum < 100; opNum++) {
long addStart = System.nanoTime();
index.add(Double.toString(Math.random()), Double.toString(Math.random()), Double.toString(Math.random()), pid, Long.MAX_VALUE);
long addEnd = System.nanoTime();
addTimes.add(addEnd - addStart);
addTracker.addResult(addEnd - addStart);
}
long removeStart = System.nanoTime();
index.remove(pid);
long removeEnd = System.nanoTime();
removeTimes.add(removeEnd - removeStart);
removeTracker.addResult(removeEnd - removeStart);
}
writer.write("add time,");
for(long addTime : addTimes) {
writer.write(Long.toString(addTime));
writer.write(',');
}
writer.write("\r\n,remove time,");
for(long removeTime : removeTimes) {
writer.write(Long.toString(removeTime));
writer.write(',');
}
writer.write("\r\n");
System.out.println("Add stats: mean=" + (addTracker.getMean() / 1000000.0) + "ms, std dev=" + (addTracker.getStdDev() / 1000000.0) + "ms");
System.out.println("Remove stats: mean=" + (removeTracker.getMean() / 1000000.0) + "ms, std dev=" + (removeTracker.getStdDev() / 1000000.0) + "ms");
}
/**
* Adds 10000 entirely random records to the index to warm it up, recording the overall
* time taken, though this is not likely to be a conclusive statistic to rely on.
*/
private void add10000Records(Srdi index, FileWriter writer) throws IOException {
long phase1Start = System.nanoTime();
for(int pk=0; pk < 10; pk++) {
for(int attr=0; attr < 10; attr++) {
for(int value=0; value < 10; value++) {
for(int pid = 0; pid < 10; pid++) {
index.add(Integer.toString(pk), Integer.toString(attr), Integer.toString(value), IDFactory.newPeerID(PeerGroupID.defaultNetPeerGroupID), Long.MAX_VALUE);
}
}
}
}
long phase1End = System.nanoTime();
double timeInMs = (phase1End - phase1Start) / 1000000.0;
writer.write("setup time," + timeInMs);
writer.write("\r\n");
}
private PeerGroup createGroup(final PeerGroupID groupId, final String name) {
final PeerGroup group = mockContext.mock(PeerGroup.class, name);
mockContext.checking(new Expectations() {{
ignoring(group).getStoreHome(); will(returnValue(storeRoot.toURI()));
ignoring(group).getPeerGroupName(); will(returnValue(name));
ignoring(group).getPeerGroupID(); will(returnValue(groupId));
// ignoring(group).getHomeThreadGroup(); will(returnValue(Thread.currentThread().getThreadGroup()));
}});
return group;
}
/*
* This is an important performance test, as real world usage of the SRDI index often contains many values for
* the same key and attribute. If these queries are not fast, overall system performance is hugely degraded.
*/
@Test(timeout=30000)
public void testQuery_manyValuesForSameKeyAndAttribute() throws IOException {
Srdi index = new Srdi(createGroup(PeerGroupID.defaultNetPeerGroupID, "group"), "duplicatesTestIndex");
String primaryKey = "pk";
String attribute = "attr";
int numAlreadyAdded = 0;
// add entries with the same primary key and attribute, but with different values and peer IDs
for(int stage=1; stage <= 3; stage++) {
int numEntries = (int) Math.pow(10, stage+2);
System.out.println("Testing with " + numEntries);
StatsTracker queryTimeTracker = new StatsTracker();
System.out.println("Adding test entries");
for(int i=numAlreadyAdded; i < numEntries; i++) {
index.add(primaryKey, attribute, "value" + i, IDFactory.newPeerID(PeerGroupID.defaultNetPeerGroupID), Long.MAX_VALUE);
}
System.out.println("Finished adding test entries");
System.out.println("Performing queries");
Random r = new Random();
// perform random queries to determine the average query time
for(int i=0; i < 1000; i++) {
long startTime = System.nanoTime();
List<PeerID> result = index.query(primaryKey, attribute, "value" + r.nextInt(numEntries), 1);
assertEquals(1, result.size());
long endTime = System.nanoTime();
queryTimeTracker.addResult(endTime - startTime);
}
System.out.printf("Query times - mean: %.1f ns, min: %.1f, max: %.1f, stdev: %.3f\n" , queryTimeTracker.getMean(), queryTimeTracker.getMin(), queryTimeTracker.getMax(), queryTimeTracker.getStdDev());
}
}
}