/* * Bitronix Transaction Manager * * Copyright (c) 2011, Juergen Kellerer. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program 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 distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package bitronix.tm.journal.nio; import bitronix.tm.TransactionManagerServices; import bitronix.tm.journal.JournalRecord; import bitronix.tm.journal.NullJournal; import bitronix.tm.utils.Uid; import bitronix.tm.utils.UidGenerator; import org.junit.After; import org.junit.Before; import org.junit.Test; import javax.transaction.Status; import java.io.IOException; import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import static java.util.Collections.nCopies; import static org.junit.Assert.assertEquals; /** * Common peformance & load test to be used with all journal implementations. * * @author juergen kellerer, 2011-04-30 */ public abstract class AbstractJournalPerformanceTest extends AbstractJournalTest { private static Set<String> resources = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList( "pooled-datasource", "jms-connection-pool", "xa-aware-cache" ))); private static List<Set<String>> resourceNameSets = new ArrayList<Set<String>>(); static { for (String resource : resources) resourceNameSets.add(Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(resource)))); } protected int getLogCallsPerEmitter() { return 1000; } @Before public void setUp() throws Exception { TransactionManagerServices.getConfiguration().setForcedWriteEnabled(true); } @After public void tearDown() throws Exception { TransactionManagerServices.getConfiguration().setForcedWriteEnabled(true); } @Test public void testLogPerformance() throws Exception { journal.open(); int concurrency = 128; UidGenerator.generateUid(); ExecutorService executorService = Executors.newFixedThreadPool(concurrency); // Warming threads. (Note: The case is required to avoid build errors in Java5) List<Future<Object>> futures = executorService.invokeAll((Collection) nCopies(concurrency * 64, new Callable<Object>() { public Object call() throws Exception { return UidGenerator.generateUid(); } })); for (Future<Object> future : futures) { future.get(); } // Testing now try { List<Callable<Integer>> tests = new ArrayList<Callable<Integer>>(); TransactionEmitter concurrentDanglingEmitter = new TransactionEmitter(true); tests.add(concurrentDanglingEmitter); for (int i = 1; i < concurrency; i++) tests.add(new TransactionEmitter(false)); long time = System.currentTimeMillis(), logCalls = 0; // Create dangling ids in the main emitter (at the beginning to see whether entries may get lost). TransactionEmitter firstDanglingEmitter = new TransactionEmitter(true); logCalls += firstDanglingEmitter.call(); List<Uid> danglingUids = firstDanglingEmitter.getGeneratedUids(); // Wait on the the emitters. for (Future<Integer> future : executorService.invokeAll(tests)) logCalls += future.get(); journal.force(); danglingUids.addAll(concurrentDanglingEmitter.getGeneratedUids()); double seconds = ((double) System.currentTimeMillis() - time) / 1000; System.out.printf("%s (threads=%d, fsync=%s): %d transactions, took %.2f seconds (%.2f tx/s)%n", getClass().getSimpleName(), concurrency, TransactionManagerServices.getConfiguration().isForcedWriteEnabled(), logCalls, seconds, logCalls / seconds); handleDanglingRecords(danglingUids); } finally { executorService.shutdown(); } } @Test public void testLogPerformanceWithoutFsync() throws Exception { TransactionManagerServices.getConfiguration().setForcedWriteEnabled(false); testLogPerformance(); } private void handleDanglingRecords(List<Uid> danglingUids) throws IOException { journal.close(); journal.open(); Map map = journal.collectDanglingRecords(); int sizeBefore = map.size(); for (Uid uid : danglingUids) { JournalRecord removed = (JournalRecord) map.remove(uid); if (removed == null) continue; // cleanup journal.log(Status.STATUS_COMMITTED, uid, removed.getUniqueNames()); } int matchedUids = sizeBefore - map.size(); System.out.printf("Created %d dangling records, the journal contained %d of them.%n", danglingUids.size(), matchedUids); journal.close(); journal.open(); if (!(journal instanceof NullJournal)) { assertEquals("Not all dangling IDs were matched.", danglingUids.size(), matchedUids); assertEquals("Not all dangling IDs were cleaned up.", sizeBefore - matchedUids, journal.collectDanglingRecords().size()); } } private class TransactionEmitter implements Callable<Integer> { boolean createDangling; List<Uid> generatedUids; private TransactionEmitter(boolean createDangling) { this.createDangling = createDangling; } public List<Uid> getGeneratedUids() { return generatedUids; } public Integer call() throws Exception { final int logCalls = getLogCallsPerEmitter(); generatedUids = new ArrayList<Uid>(logCalls); for (int i = 0; i < logCalls; i++) { Uid uid = UidGenerator.generateUid(); journal.log(Status.STATUS_ACTIVE, uid, resources); journal.log(Status.STATUS_PREPARING, uid, resources); journal.log(Status.STATUS_PREPARED, uid, resources); journal.log(Status.STATUS_COMMITTING, uid, resources); journal.force(); if (!createDangling) { for (Set<String> nameSet : resourceNameSets) journal.log(Status.STATUS_COMMITTED, uid, nameSet); } generatedUids.add(uid); } return logCalls; } } }