/*
* JBoss, Home of Professional Open Source
* Copyright 2009 Red Hat Inc. and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.infinispan.profiling;
import org.infinispan.Cache;
import org.infinispan.config.Configuration;
import org.infinispan.manager.CacheContainer;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.test.AbstractInfinispanTest;
import org.infinispan.test.TestingUtil;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import javax.transaction.TransactionManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
/**
* Test for benchmarking the performance of deadlock detection code. Performance is measured as number of successful
* transactions per minute.
* <pre>
* Test description:
* We use a fixed size pool of keys ({@link #KEY_POOL_SIZE}) on which each transaction operates. A number of threads ({@link #THREAD_COUNT})
* repeatedly starts transactions and tries to acquire locks on a random subset of this pool, by executing put
* operations on each key. If all locks were successfully acquired then the tx tries to commit: only if it succeeds this tx is counted as successful.
* The number of elements in this subset is the transaction size ({@link #TX_SIZE}). The greater transaction
* size is, the higher chance for deadlock situation to occur.
* On each thread these transactions are being repeatedly executed (each time on a different, random key set) for a given time
* interval ({@link #BENCHMARK_DURATION}). At the end, the number of successful transactions from each thread is cumulated, and this
* defines throughput (successful tx) per time unit (by default one minute).
* </pre>
* There are two different benchmark methods, one for local cache {@link #testLocalDifferentTxSize()} and one for replicated caches
* {@link #testReplDifferentTxSize()}.
*
*
* @author Mircea.Markus@jboss.com
*/
@Test(groups = "profiling", enabled = true, testName = "profiling.DeadlockDetectionPerformanceTest")
public class DeadlockDetectionPerformanceTest extends AbstractInfinispanTest {
private static final Log log = LogFactory.getLog(DeadlockDetectionPerformanceTest.class);
public static final int KEY_POOL_SIZE = 10;
public static int TX_SIZE = 5;
public static int THREAD_COUNT = 5;
public static final long BENCHMARK_DURATION = 60000;
public static boolean NO_COLISION = false;
public static boolean USE_DLD = true;
public static List<String> keyPool;
@BeforeTest
public void generateKeyPool() {
keyPool = new ArrayList<String>();
for (int i = 0; i < KEY_POOL_SIZE; i++) {
keyPool.add("key" + i);
}
}
@Test(invocationCount = 10, enabled = false)
public void testLocalDifferentTxSize() throws Exception {
USE_DLD = false;
for (int i = 2; i < KEY_POOL_SIZE; i++) {
TX_SIZE = i;
runLocalTest();
}
USE_DLD = true;
for (int i = 2; i < KEY_POOL_SIZE; i++) {
TX_SIZE = i;
runLocalTest();
}
}
@Test(invocationCount = 5, enabled = false)
public void testLocalDifferentTxSizeNoCollision() throws Exception {
NO_COLISION = true;
USE_DLD = false;
for (int i = 2; i < KEY_POOL_SIZE; i++) {
TX_SIZE = i;
runLocalTest();
}
USE_DLD = true;
for (int i = 2; i < KEY_POOL_SIZE; i++) {
TX_SIZE = i;
runLocalTest();
}
}
@Test(invocationCount = 10, enabled = false)
public void testReplDifferentTxSize() throws Exception {
THREAD_COUNT = 2;
USE_DLD = false;
for (int i = 2; i < KEY_POOL_SIZE; i++) {
TX_SIZE = i;
runDistributedTest();
}
USE_DLD = true;
for (int i = 2; i < KEY_POOL_SIZE; i++) {
TX_SIZE = i;
runDistributedTest();
}
}
@Test(invocationCount = 5, enabled = false)
public void testReplDifferentTxSizeDldOnly() throws Exception {
THREAD_COUNT = 3;
USE_DLD = true;
for (int i = 2; i < KEY_POOL_SIZE; i++) {
TX_SIZE = i;
runDistributedTest();
}
}
private void runDistributedTest() throws Exception {
EmbeddedCacheManager cm = null;
List<EmbeddedCacheManager> containers = new ArrayList<EmbeddedCacheManager>();
try {
CountDownLatch startLatch = new CountDownLatch(1);
List<ExecutorThread> executorThreads = new ArrayList<ExecutorThread>();
for (int i = 0; i < THREAD_COUNT; i++) {
cm = TestCacheManagerFactory.createClusteredCacheManager();
Configuration configuration = getConfiguration();
configuration.setCacheMode(Configuration.CacheMode.REPL_SYNC);
cm.defineConfiguration("test", configuration);
Cache distCache = cm.getCache("test");
ExecutorThread executorThread = new ExecutorThread(startLatch, distCache);
executorThreads.add(executorThread);
containers.add(cm);
}
TestingUtil.blockUntilViewsReceived(10000, containers.toArray(new CacheContainer[containers.size()]));
startLatch.countDown();
Thread.sleep(BENCHMARK_DURATION);
joinThreadsAndPrintResult(executorThreads);
} finally {
log.trace("About to kill cache managers: " + containers);
TestingUtil.killCacheManagers(containers);
}
}
private void runLocalTest() throws Exception {
EmbeddedCacheManager cm = TestCacheManagerFactory.createLocalCacheManager(false);
try {
Configuration configuration = getConfiguration();
cm.defineConfiguration("test", configuration);
Cache localCache = cm.getCache("test");
CountDownLatch startLatch = new CountDownLatch(1);
List<ExecutorThread> executorThreads = new ArrayList<ExecutorThread>();
for (int i = 0; i < THREAD_COUNT; i++) {
ExecutorThread executorThread = new ExecutorThread(startLatch, localCache);
executorThreads.add(executorThread);
}
startLatch.countDown();
Thread.sleep(BENCHMARK_DURATION);
joinThreadsAndPrintResult(executorThreads);
} finally {
TestingUtil.killCacheManagers(cm);
}
}
private void joinThreadsAndPrintResult(List<ExecutorThread> executorThreads) throws InterruptedException {
int totalSuccess = 0;
int totalFailures = 0;
for (int i = 0; i < THREAD_COUNT; i++) {
ExecutorThread executorThread = executorThreads.get(i);
executorThread.join();
totalSuccess += executorThread.getSuccessfullTx();
totalFailures += executorThread.getFailedTx();
}
System.out.println("Use DDL? " + USE_DLD + " TX_SIZE = " + TX_SIZE + " totalSuccess = " + totalSuccess);
System.out.println("Use DDL? " + USE_DLD + " TX_SIZE = " + TX_SIZE + " totalFailures = " + totalFailures);
System.out.println("-------------------------------");
}
private Configuration getConfiguration() {
Configuration configuration = new Configuration();
configuration.setTransactionManagerLookupClass(DummyTransactionManagerLookup.class.getName());
configuration.setEnableDeadlockDetection(USE_DLD);
configuration.setUseLockStriping(false);
return configuration;
}
public static class ExecutorThread extends Thread {
private volatile CountDownLatch startLatch;
private volatile int successfullTx;
private volatile int failedTx;
private volatile Cache cache;
private volatile TransactionManager txManager;
static int TX_INDEX = 0;
public ExecutorThread(CountDownLatch startLatch, Cache cache) {
super("EXECUTOR-THREAD-" + TX_INDEX++);
this.startLatch = startLatch;
this.cache = cache;
txManager = TestingUtil.getTransactionManager(cache);
start();
}
@Override
public void run() {
long start = System.currentTimeMillis();
try {
startLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
while ((start + BENCHMARK_DURATION) - System.currentTimeMillis() > 0) {
try {
txManager.begin();
List<String> keysToUpdate = getKeysPerTx();
for (String key : keysToUpdate) {
cache.put(key, "value");
}
txManager.commit();
successfullTx++;
} catch (Throwable e) {
failedTx++;
}
}
info("Exiting thread " + getName() + " which lived " + (System.currentTimeMillis() - start) + " milliseconds");
}
private void info(String s) {
System.out.println( "[" + getName() + "] " + s);
log.trace(s);
}
public int getFailedTx() {
return failedTx;
}
public int getSuccessfullTx() {
return successfullTx;
}
}
private static List<String> getKeysPerTx() {
Random rnd = new Random();
Set<String> result = new HashSet<String>();
while (result.size() < TX_SIZE) {
String key = keyPool.get(rnd.nextInt(KEY_POOL_SIZE));
result.add(key);
}
ArrayList resultList = new ArrayList(result);
if (!NO_COLISION) {
Collections.shuffle(resultList);
}
return resultList;
}
}