package org.infinispan.lucene.profiling; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.RAMDirectory; import org.infinispan.Cache; import org.infinispan.commons.util.Util; import org.infinispan.lucene.CacheTestSupport; import org.infinispan.lucene.directory.BuildContext; import org.infinispan.lucene.directory.DirectoryBuilder; import org.infinispan.manager.CacheContainer; import org.infinispan.manager.DefaultCacheManager; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.test.AbstractInfinispanTest; import org.infinispan.test.TestingUtil; import org.infinispan.test.fwk.TestResourceTracker; import org.testng.AssertJUnit; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * PerformanceCompareStressTest is useful to get an idea on relative performance between Infinispan * in local or clustered mode against a RAMDirectory or FSDirectory. To be reliable set a long * DURATION_MS and a number of threads similar to the use case you're interested in: results might * vary on the number of threads because of the lock differences. This is not meant as a benchmark * but used to detect regressions. * * This requires Lucene > 2.9.1 or Lucene > 3.0.0 because of * https://issues.apache.org/jira/browse/LUCENE-2095 * * @author Sanne Grinovero * @since 4.0 */ @Test(groups = "profiling", testName = "lucene.profiling.PerformanceCompareStressTest", sequential = true) public class PerformanceCompareStressTest extends AbstractInfinispanTest { private static final int NUM_NODES = 4; private static final String CONFIGURATION = "perf-udp.xml"; /** * The number of terms in the dictionary used as source of terms by the IndexWriter to produce * new documents */ private static final int DICTIONARY_SIZE = 800 * 1000; /** Concurrent Threads in tests */ private static final int READER_THREADS = 5; private static final int WRITER_THREADS = 1; private static final boolean INDEX_EXCLUSIVE = true; private static final int CHUNK_SIZE = 1024 * 1024; private static final String indexName = "tempIndexName"; private static final long DEFAULT_DURATION_MS = 30 * 60 * 1000; private static final boolean ASYNC_METADATA_WRITES = true; private static final boolean ASYNC_DELETES = false; private static final int ASYNC_DELETES_POOL_SIZE = 10; private long durationMs = DEFAULT_DURATION_MS; private final Map<Integer,EmbeddedCacheManager> cacheManagers = new HashMap<>(); private Properties results = null; private String currentMethod = null; @Test public void profileTestRAMDirectory() throws InterruptedException, IOException { RAMDirectory dir = new RAMDirectory(); stressTestDirectoryInternal(dir, dir, "RAMDirectory"); } @Test public void profileTestFSDirectory() throws InterruptedException, IOException { File indexDir = new File(TestingUtil.tmpDirectory(this.getClass()), indexName); boolean directoriesCreated = indexDir.mkdirs(); assert directoriesCreated : "couldn't create directory for FSDirectory test"; FSDirectory dir = FSDirectory.open(indexDir.toPath()); stressTestDirectoryInternal(dir, dir, "FSDirectory"); } @Test public void profileTestInfinispanDirectoryWithNetworkDelayZero() throws Exception { setNetworkDelay(0); Directory dir1 = buildDirectoryFromNode(1); Directory dir2 = buildDirectoryFromNode(3); stressTestDirectoryInternal(dir1, dir2, "InfinispanClustered-delayedIO:0"); verifyDirectoryState(); } @Test public void profileTestInfinispanDirectoryWithNetworkDelay1() throws Exception { setNetworkDelay(1); Directory dir1 = buildDirectoryFromNode(1); Directory dir2 = buildDirectoryFromNode(3); stressTestDirectoryInternal(dir1, dir2, "InfinispanClustered-delayedIO:1"); verifyDirectoryState(); setNetworkDelay(0); } @Test public void profileTestInfinispanDirectoryWithHighNetworkDelay4() throws Exception { setNetworkDelay(4); Directory dir1 = buildDirectoryFromNode(1); Directory dir2 = buildDirectoryFromNode(3); stressTestDirectoryInternal(dir1, dir2, "InfinispanClustered-delayedIO:4"); verifyDirectoryState(); setNetworkDelay(0); } @Test public void profileTestInfinispanDirectoryWithHighNetworkDelay20() throws Exception { setNetworkDelay(20); Directory dir1 = buildDirectoryFromNode(1); Directory dir2 = buildDirectoryFromNode(3); stressTestDirectoryInternal(dir1, dir2, "InfinispanClustered-delayedIO:20"); verifyDirectoryState(); setNetworkDelay(0); } @Test public void profileInfinispanLocalDirectory() throws InterruptedException, IOException { CacheContainer cacheContainer = CacheTestSupport.createLocalCacheManager(); try { Cache cache = cacheContainer.getCache(); Directory dir = DirectoryBuilder.newDirectoryInstance(cache, cache, cache, indexName).chunkSize(CHUNK_SIZE).create(); stressTestDirectoryInternal(dir, dir, "InfinispanLocal"); verifyDirectoryState(); } finally { cacheContainer.stop(); } } @Test(enabled=false)//to prevent invocations from some versions of TestNG public static void stressTestDirectory(Directory dir, String testLabel) throws InterruptedException, IOException { stressTestDirectory(dir, dir, testLabel, 120000l, null, null); } private void stressTestDirectoryInternal(Directory dirWriter, Directory dirReaders, String testLabel) throws InterruptedException, IOException { stressTestDirectory(dirWriter, dirReaders, testLabel, durationMs, results, currentMethod); } private void setNetworkDelay(int delay) throws Exception { for (int i=0; i<NUM_NODES; i++) { EmbeddedCacheManager cm = cacheManagers.get(i); //Any cache will do: TestingUtil.setDelayForCache(cm.getCache(), delay, delay); } System.out.println("Simulating network packet delay of: " + delay); } private ThreadFactory createThreadFactory() { return new ThreadFactory() { private AtomicInteger atomicInteger = new AtomicInteger(0); @Override public Thread newThread(Runnable runnable) { Thread thread = new Thread(runnable); thread.setName("File-deleter-" + atomicInteger.incrementAndGet()); return thread; } }; } private Executor createDeleteExecutor() { return new ThreadPoolExecutor( 0, ASYNC_DELETES_POOL_SIZE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), createThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); } private Directory buildDirectoryFromNode(int node) { EmbeddedCacheManager cm = cacheManagers.get(node); BuildContext context = DirectoryBuilder .newDirectoryInstance(cm.getCache("index_metadata"), cm.getCache("index_data"), cm.getCache("index_locks"), indexName) .writeFileListAsynchronously(ASYNC_METADATA_WRITES) .chunkSize(CHUNK_SIZE); if (ASYNC_DELETES) { context.deleteOperationsExecutor(createDeleteExecutor()); } return context.create(); } @Test(enabled=false)//to prevent invocations from some versions of TestNG private static void stressTestDirectory(Directory dirWriter, Directory dirReaders, String testLabel, long durationMs, Properties results, String currentMethod) throws InterruptedException, IOException { SharedState state = new SharedState(DICTIONARY_SIZE); CacheTestSupport.initializeDirectory(dirWriter); ExecutorService e = Executors.newFixedThreadPool(READER_THREADS + WRITER_THREADS); for (int i = 0; i < READER_THREADS; i++) { e.execute(new LuceneReaderThread(dirReaders, state)); } for (int i = 0; i < WRITER_THREADS; i++) { e.execute(INDEX_EXCLUSIVE ? new LuceneWriterExclusiveThread(dirWriter, state) : new LuceneWriterThread(dirWriter, state)); } e.shutdown(); System.out.println("Started test: " + testLabel); state.startWaitingThreads(); Thread.sleep(durationMs); long searchesCount = state.incrementIndexSearchesCount(0); long writerTaskCount = state.incrementIndexWriterTaskCount(0); state.quit(); boolean terminatedCorrectly = e.awaitTermination(20, TimeUnit.SECONDS); AssertJUnit.assertTrue(terminatedCorrectly); System.out.println("Test " + testLabel + " run in " + durationMs + "ms:\n\tSearches: " + searchesCount + "\n\t" + "Writes: " + writerTaskCount); if (results != null) { results.setProperty(currentMethod + ".label", testLabel); results.setProperty(currentMethod + ".searches", Long.toString(searchesCount)); results.setProperty(currentMethod + ".writes", Long.toString(writerTaskCount)); } } @BeforeMethod public void beforeTest() throws IOException { for (int i = 0; i < NUM_NODES; i++) { DefaultCacheManager node = new DefaultCacheManager(CONFIGURATION); node.start(); //Start all its caches: node.getCache("index_metadata").start(); node.getCache("index_data").start(); node.getCache("index_locks").start(); cacheManagers.put(i, node); } } @AfterMethod public void afterTest() { for (EmbeddedCacheManager node : cacheManagers.values()) { TestingUtil.killCacheManagers(node); } Util.recursiveFileRemove(indexName); } private void verifyDirectoryState() { for (EmbeddedCacheManager node : cacheManagers.values()) { // DirectoryIntegrityCheck.verifyDirectoryStructure(cache, indexName, true); } } /** * It's much better to compare performance out of the scope of TestNG by * running this directly as TestNG enables assertions. * * Select which tests to run: * -Dlucene.profiling.tests=profileInfinispanLocalDirectory,profileTestInfinispanDirectoryWithNetworkDelayZero * * Suggested test switches: * -Xmx4G -Xms4G -XX:+HeapDumpOnOutOfMemoryError -Xss512k -XX:HeapDumpPath=/tmp/java_heap -Djava.net.preferIPv4Stack=true -Djgroups.bind_addr=127.0.0.1 -XX:+UseLargePages -XX:LargePageSizeInBytes=2m * * With detailed GC logging: * -Xmx4G -Xms4G XX:+HeapDumpOnOutOfMemoryError -Xss256k -XX:HeapDumpPath=/tmp/java_heap -Djava.net.preferIPv4Stack=true -Djgroups.bind_addr=127.0.0.1 -XX:+UseLargePages -XX:LargePageSizeInBytes=2m -XX:+UseLargePages -XX:LargePageSizeInBytes=2m -Xloggc:gc-full.log -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime * * To enable flight recorder: * -XX:+UnlockCommercialFeatures -XX:+FlightRecorder */ public static void main(String[] args) throws Exception { String[] testMethods = System.getProperty("lucene.profiling.tests", "profileTestRAMDirectory,profileTestFSDirectory,profileInfinispanLocalDirectory,profileTestInfinispanDirectoryWithNetworkDelayZero").split(","); PerformanceCompareStressTest test = new PerformanceCompareStressTest(); TestResourceTracker.testThreadStarted(test); test.durationMs = new Long(System.getProperty("lucene.profiling.duration", String.valueOf(DEFAULT_DURATION_MS))); String outputFile = System.getProperty("lucene.profiling.output"); test.results = outputFile == null ? null : new Properties(); for (String testMethod : testMethods) { try { test.currentMethod = testMethod; Method m = PerformanceCompareStressTest.class.getMethod(testMethod); test.beforeTest(); try { m.invoke(test); } finally { test.afterTest(); } } catch (NoSuchMethodException e) { System.out.println("Couldn't find method " + testMethod); System.exit(1); } } if (test.results != null && !test.results.isEmpty()) { System.out.println("Writing results to " + outputFile + " ..."); TestingUtil.outputPropertiesToXML(outputFile, test.results); } } }