/*
* IronJacamar, a Java EE Connector Architecture implementation
* Copyright 2015, Red Hat Inc, and individual contributors
* as indicated by the @author tags. See the copyright.txt file 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 Eclipse Public License 1.0 as
* published by the Free Software Foundation.
*
* 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 Eclipse
* Public License for more details.
*
* You should have received a copy of the Eclipse 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.ironjacamar.perf;
import org.ironjacamar.embedded.Embedded;
import org.ironjacamar.embedded.dsl.resourceadapters20.api.ResourceAdaptersDescriptor;
import org.ironjacamar.rars.ResourceAdapterFactory;
import org.ironjacamar.rars.perf.PerfConnection;
import org.ironjacamar.rars.perf.PerfConnectionFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.resource.spi.TransactionSupport.TransactionSupportLevel;
import javax.transaction.Status;
import javax.transaction.UserTransaction;
import org.jboss.shrinkwrap.api.spec.ResourceAdapterArchive;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Basic performance tests
*
* @author <a href="jesper.pedersen@ironjacamar.org">Jesper Pedersen</a>
*/
public abstract class Performance
{
/** Settings */
private static final int[] CLIENTS = {1, 10, 25, 50, 100, 150, 200, 250, 300};
private static final int[] POOL_SIZES = {1, 10, 25, 50, 100, 150, 200, 250, 300};
private static final boolean DO_RAMP_UP = true;
private static final int RAMP_UP_ITERATIONS = 1;
private static final int TRANSACTIONS_PER_CLIENT = 200;
private static final boolean STATISTICS = false;
private static final boolean RECORD_ENLISTMENT_TRACES = false;
private static final boolean USE_TRANSACTION_FOR_NOTRANSACTION = true;
private static final boolean USE_CCM = false;
private static final long TX_BEGIN_DURATION = 0L;
private static final long TX_COMMIT_DURATION = 0L;
/** Embedded */
protected static Embedded embedded = null;
/** Data */
private static SortedMap<String, SortedMap<Integer, Integer>> data =
new TreeMap<String, SortedMap<Integer, Integer>>();
private ResourceAdapterArchive resourceAdapter;
private ResourceAdaptersDescriptor resourceAdapterActivation;
private static ExecutorService es;
/**
* Create .rar
* @return The resource adapter archive
*/
public ResourceAdapterArchive createRar()
{
return ResourceAdapterFactory.createPerfRar();
}
/**
* Create deployment
* @param tsl The transaction support
* @param poolSize The pool size
* @return The resource adapter descriptor
*/
private ResourceAdaptersDescriptor createDeployment(TransactionSupportLevel tsl, int poolSize)
{
return ResourceAdapterFactory.createPerfDeployment(tsl, USE_CCM, TX_BEGIN_DURATION, TX_COMMIT_DURATION, poolSize);
}
/**
* Create an XATransaction deployment
* @param poolSize The pool size
* @return The resource adapter descriptor
*/
public ResourceAdaptersDescriptor createXATxDeployment(int poolSize)
{
return createDeployment(TransactionSupportLevel.XATransaction, poolSize);
}
/**
* Create a LocalTransaction deployment
* @param poolSize The pool size
* @return The resource adapter descriptor
*/
public ResourceAdaptersDescriptor createLocalTxDeployment(int poolSize)
{
return createDeployment(TransactionSupportLevel.LocalTransaction, poolSize);
}
/**
* Create a NoTransaction deployment
* @param poolSize The pool size
* @return The resource adapter descriptor
*/
public ResourceAdaptersDescriptor createNoTxDeployment(int poolSize)
{
return createDeployment(TransactionSupportLevel.NoTransaction, poolSize);
}
/**
* Ramp up
*
* @param dashRaXml The deployment metadata
* @param poolSize The pool size
*/
public void rampUp(ResourceAdaptersDescriptor dashRaXml, int poolSize)
{
this.resourceAdapter = createRar();
this.resourceAdapterActivation = dashRaXml;
Context context = null;
try
{
embedded.deploy(resourceAdapter);
embedded.deploy(resourceAdapterActivation);
if (DO_RAMP_UP)
{
int cycles = RAMP_UP_ITERATIONS * poolSize;
context = new InitialContext();
UserTransaction ut = (UserTransaction)context.lookup("java:/UserTransaction");
assertNotNull(ut);
PerfConnectionFactory cf = (PerfConnectionFactory)context.lookup("java:/eis/PerfConnectionFactory");
assertNotNull(cf);
CountDownLatch done = new CountDownLatch(cycles);
List<Client> clientList = new ArrayList<Client>(cycles);
for (int i = 0; i < cycles; i++)
{
clientList.add(new Client(cf, ut, done));
}
es.invokeAll(clientList);
done.await();
}
}
catch (Throwable t)
{
t.printStackTrace(System.err);
afterRun();
beforeRun();
}
finally
{
if (context != null)
{
try
{
context.close();
}
catch (NamingException ne)
{
// Ignore
}
}
}
}
/**
* Ramp down
* @param type The type
* @param clients The number of clients
*/
public void rampDown(String type, int clients)
{
try
{
embedded.undeploy(resourceAdapter);
embedded.undeploy(resourceAdapterActivation);
}
catch (Throwable t)
{
t.printStackTrace(System.err);
}
finally
{
resourceAdapter = null;
resourceAdapterActivation = null;
}
}
/**
* Base
*
* @param clients The number of clients
* @param useTx Use transactions
* @return The result
* @throws Throwable Thrown in case of an error
*/
public int testBase(int clients, boolean useTx) throws Throwable
{
int result = 0;
Context context = null;
try
{
context = new InitialContext();
UserTransaction ut = (UserTransaction)context.lookup("java:/UserTransaction");
assertNotNull(ut);
PerfConnectionFactory cf = (PerfConnectionFactory)context.lookup("java:/eis/PerfConnectionFactory");
assertNotNull(cf);
CountDownLatch done = new CountDownLatch(clients);
List<Client> clientList = new ArrayList<Client>(clients);
for (int i = 0; i < clients; i++)
{
clientList.add(new Client(cf, useTx ? ut : null, done));
}
long start = System.nanoTime();
List<Future<Integer>> futures = es.invokeAll(clientList);
done.await();
long end = System.nanoTime();
double millis = (end - start) / 1000000.0;
if (millis <= 0.0)
millis = 1.0;
double seconds = millis / 1000.0;
int totalTxs = 0;
for (Future<Integer> f : futures)
{
totalTxs += f.get(1, TimeUnit.SECONDS).intValue();
}
double txPerSec = totalTxs / seconds;
result = (int)Math.ceil(txPerSec);
}
catch (Throwable t)
{
t.printStackTrace(System.err);
afterRun();
beforeRun();
fail(t.getMessage());
}
finally
{
if (context != null)
{
try
{
context.close();
}
catch (NamingException ne)
{
// Ignore
}
}
}
return result;
}
/**
* Client
*/
static class Client implements Callable<Integer>
{
private PerfConnectionFactory pcf;
private UserTransaction ut;
private CountDownLatch done;
/**
* Constructor
* @param pcf The perf connection factory
* @param ut The user transaction
* @param done Done counter
*/
Client(PerfConnectionFactory pcf, UserTransaction ut, CountDownLatch done)
{
this.pcf = pcf;
this.ut = ut;
this.done = done;
}
/**
* {@inheritDoc}
*/
public Integer call()
{
int success = 0;
PerfConnection pc = null;
try
{
for (int i = 0; i < TRANSACTIONS_PER_CLIENT; i++)
{
if (ut != null)
ut.begin();
pc = pcf.getConnection();
pc.close();
pc = null;
if (ut != null)
ut.commit();
success++;
}
}
catch (Throwable t)
{
System.err.println("Thread: " + Thread.currentThread().getName() + ", " + t.getMessage());
t.printStackTrace(System.err);
if (pc != null)
{
pc.error();
pc = null;
}
if (ut != null)
{
try
{
int status = ut.getStatus();
if (status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK)
{
ut.rollback();
}
}
catch (Exception inner)
{
System.err.println("Rollback: Thread: " + Thread.currentThread().getName());
inner.printStackTrace(System.err);
}
}
}
finally
{
done.countDown();
}
return Integer.valueOf(success);
}
}
/**
* No
*
* @throws Throwable Thrown in case of an error
*/
@Test(timeout = 180000)
public void testNo() throws Throwable
{
for (int i = 0; i < CLIENTS.length; i++)
{
rampUp(createNoTxDeployment(POOL_SIZES[i]), POOL_SIZES[i]);
insertResult("NoTransaction", CLIENTS[i], testBase(CLIENTS[i], USE_TRANSACTION_FOR_NOTRANSACTION));
rampDown("NoTransaction", CLIENTS[i]);
}
}
/**
* Local
*
* @throws Throwable Thrown in case of an error
*/
@Test(timeout = 180000)
public void testLocal() throws Throwable
{
for (int i = 0; i < CLIENTS.length; i++)
{
rampUp(createLocalTxDeployment(POOL_SIZES[i]), POOL_SIZES[i]);
insertResult("LocalTransaction", CLIENTS[i], testBase(CLIENTS[i], true));
rampDown("LocalTransaction", CLIENTS[i]);
}
}
/**
* XA
*
* @throws Throwable Thrown in case of an error
*/
@Test(timeout = 180000)
public void testXA() throws Throwable
{
for (int i = 0; i < CLIENTS.length; i++)
{
rampUp(createXATxDeployment(POOL_SIZES[i]), POOL_SIZES[i]);
insertResult("XATransaction", CLIENTS[i], testBase(CLIENTS[i], true));
rampDown("XATransaction", CLIENTS[i]);
}
}
/**
* Insert result
* @param type The type
* @param clients The number of clients
* @param result The result
*/
private static void insertResult(String type, int clients, int result)
{
SortedMap<Integer, Integer> section = data.get(type);
if (section == null)
section = new TreeMap<Integer, Integer>();
section.put(Integer.valueOf(clients), Integer.valueOf(result));
data.put(type, section);
}
/**
* Dump data
*/
static void dumpData()
{
Iterator<Map.Entry<String, SortedMap<Integer, Integer>>> it = data.entrySet().iterator();
while (it.hasNext())
{
Map.Entry<String, SortedMap<Integer, Integer>> entry = it.next();
Iterator<Map.Entry<Integer, Integer>> entryIt = entry.getValue().entrySet().iterator();
while (entryIt.hasNext())
{
Map.Entry<Integer, Integer> result = entryIt.next();
System.out.println("PERF-DATA: " + entry.getKey() + "," + result.getKey() + "," + result.getValue());
}
}
}
/**
* Print settings
*/
static void printSettings()
{
System.out.println("Clients: " + Arrays.toString(CLIENTS));
System.out.println("Pool sizes: " + Arrays.toString(POOL_SIZES));
System.out.println("Threads: " + CLIENTS[CLIENTS.length - 1]);
System.out.println("RampUp: " + DO_RAMP_UP);
System.out.println("RampUp iterations: " + RAMP_UP_ITERATIONS);
System.out.println("Transactions: " + TRANSACTIONS_PER_CLIENT);
System.out.println("Statistics: " + STATISTICS);
System.out.println("Use TX for NoTransaction: " + USE_TRANSACTION_FOR_NOTRANSACTION);
System.out.println("Transaction begin duration: " + TX_BEGIN_DURATION);
System.out.println("Transaction commit duration: " + TX_COMMIT_DURATION);
}
/**
* beforeRun
*/
static void beforeRun()
{
es = Executors.newFixedThreadPool(CLIENTS[CLIENTS.length - 1], new PerformanceThreadFactory());
}
/**
* afterRun
*/
static void afterRun()
{
es.shutdown();
es = null;
}
/**
* Performance thread factory
*/
static class PerformanceThreadFactory implements ThreadFactory
{
private AtomicInteger counter;
/**
* Constructor
*/
PerformanceThreadFactory()
{
counter = new AtomicInteger(0);
}
/**
* {@inheritDoc}
*/
public Thread newThread(Runnable r)
{
StringBuilder sb = new StringBuilder().append("Performance client ").append(counter.incrementAndGet());
return new Thread(r, sb.toString());
}
}
}