/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.cache;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertTrue;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.Configuration;
import org.fudgemsg.FudgeContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.engine.ComputationTargetSpecification;
import com.opengamma.engine.value.ComputedValue;
import com.opengamma.engine.value.ValueProperties;
import com.opengamma.engine.value.ValuePropertyNames;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.id.UniqueId;
import com.opengamma.transport.socket.ServerSocketFudgeConnectionReceiver;
import com.opengamma.transport.socket.SocketFudgeConnection;
import com.opengamma.util.ThreadUtils;
import com.opengamma.util.ehcache.EHCacheUtils;
import com.opengamma.util.fudgemsg.OpenGammaFudgeContext;
import com.opengamma.util.test.TestGroup;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
/**
* A test of the remote View Computation Cache Source infrastucture operating
* over proper sockets.
*/
@Test(groups = {TestGroup.INTEGRATION, "ehcache"})
public class ServerSocketRemoteViewComputationCacheTest {
private static final Logger s_logger = LoggerFactory.getLogger(ServerSocketRemoteViewComputationCacheTest.class);
private static final FudgeContext s_fudgeContext = OpenGammaFudgeContext.getInstance();
private static final int NUM_THREADS = 5;
private static final int NUM_LOOKUPS = 1000;
private static final int FLUSH_DELAY = 600;
private ViewComputationCacheSource _cacheSource;
private ServerSocketFudgeConnectionReceiver _serverSocket;
private SocketFudgeConnection _socket;
private CacheManager _cacheManager;
@BeforeClass
public void setUpClass() {
_cacheManager = EHCacheUtils.createTestCacheManager(getClass());
}
@AfterClass
public void tearDownClass() {
EHCacheUtils.shutdownQuiet(_cacheManager);
}
@BeforeMethod
public void setUp() {
EHCacheUtils.clear(_cacheManager);
}
//-------------------------------------------------------------------------
private void setupCacheSource(final boolean lazyReads, final int cacheSize, final int flushDelay) {
InMemoryViewComputationCacheSource cache = new InMemoryViewComputationCacheSource(s_fudgeContext);
ViewComputationCacheServer server = new ViewComputationCacheServer(cache);
_serverSocket = new ServerSocketFudgeConnectionReceiver(cache.getFudgeContext(), server, Executors
.newCachedThreadPool());
_serverSocket.setLazyFudgeMsgReads(lazyReads);
_serverSocket.start();
_socket = new SocketFudgeConnection(cache.getFudgeContext());
_socket.setFlushDelay(flushDelay);
try {
_socket.setInetAddress(InetAddress.getLocalHost());
} catch (UnknownHostException e) {
throw new OpenGammaRuntimeException("", e);
}
_socket.setPortNumber(_serverSocket.getPortNumber());
RemoteCacheClient client = new RemoteCacheClient(_socket);
Configuration configuration = new Configuration();
CacheConfiguration cacheConfig = new CacheConfiguration();
cacheConfig.setMaxElementsInMemory(cacheSize);
configuration.setDefaultCacheConfiguration(cacheConfig);
_cacheSource = new RemoteViewComputationCacheSource(client, new DefaultFudgeMessageStoreFactory(
new InMemoryBinaryDataStoreFactory(), s_fudgeContext), _cacheManager);
}
private void shutDown() {
if (_socket != null) {
_socket.stop();
}
if (_serverSocket != null) {
_serverSocket.stop();
}
_cacheSource = null;
_socket = null;
_serverSocket = null;
}
@SuppressWarnings("unchecked")
private void assertLikelyValue(final Object value) {
assertTrue(value instanceof List<?>);
for (Object valueElement : (List<Object>) value) {
assertTrue(valueElement instanceof double[]);
}
}
private Object createValue(final Random rand) {
final double[][] data = new double[rand.nextInt(100)][rand.nextInt(100)];
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < data[i].length; j++) {
data[i][j] = rand.nextDouble();
}
}
return data;
}
private Pair<Double, Double> multiThreadedTestImpl() {
final Random rand = new Random();
final AtomicBoolean failed = new AtomicBoolean(false);
final AtomicLong getTime = new AtomicLong(0);
final AtomicLong putTime = new AtomicLong(0);
List<Thread> threads = new ArrayList<Thread>();
UniqueId cycle0Id = UniqueId.of("Test", "MultiThreadedTestViewCycle", "0");
UniqueId cycle1Id = UniqueId.of("Test", "MultiThreadedTestViewCycle", "1");
for (int i = 0; i < NUM_THREADS; i++) {
// Half the threads on one cycle, half on another
final UniqueId cycleId = ((i & 1) == 0) ? cycle0Id : cycle1Id;
Thread t = new Thread(new Runnable() {
@Override
public void run() {
final ViewComputationCache cache = _cacheSource.getCache(cycleId, "default");
try {
long tGet = 0;
long tPut = 0;
for (int j = 0; j < NUM_LOOKUPS; j++) {
int randomValue = rand.nextInt(100);
String valueName = "Value" + randomValue;
ValueSpecification valueSpec = new ValueSpecification("Test Value",
ComputationTargetSpecification.of(UniqueId.of("Kirk", valueName)), ValueProperties.with(ValuePropertyNames.FUNCTION, "mockFunctionId").get());
boolean putValue = true;
if (j > 0) {
// Don't try and get on the first attempt as the cache probably isn't created at the server
tGet -= System.nanoTime();
Object ultimateValue = cache.getValue(valueSpec);
tGet += System.nanoTime();
if (ultimateValue != null) {
assertLikelyValue(ultimateValue);
putValue = rand.nextDouble() < 0.3;
}
}
if (putValue) {
ComputedValue cv = new ComputedValue(valueSpec, createValue(rand));
tPut -= System.nanoTime();
cache.putSharedValue(cv);
tPut += System.nanoTime();
}
}
s_logger.debug("Get = {}ms, Put = {}ms", (double) tGet / 1e6, (double) tPut / 1e6);
getTime.addAndGet(tGet);
putTime.addAndGet(tPut);
} catch (Throwable e) {
s_logger.error("Failed", e);
failed.set(true);
}
}
});
threads.add(t);
}
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
ThreadUtils.safeJoin(t, 10000L);
}
assertFalse("One thread failed. Check logs.", failed.get());
final double get = (double) getTime.get() / (1e6 * NUM_THREADS * NUM_LOOKUPS);
final double put = (double) putTime.get() / (1e6 * NUM_THREADS * NUM_LOOKUPS);
s_logger.info("{} get operations @ {}ms", NUM_THREADS * NUM_LOOKUPS, get);
s_logger.info("{} put operations @ {}ms", NUM_THREADS * NUM_LOOKUPS, put);
return Pairs.of(get, put);
}
@Test
public void multiThreadedTestLazyReadsNoCache() {
setupCacheSource(true, 1, FLUSH_DELAY);
multiThreadedTestImpl();
shutDown();
}
@Test
public void multiThreadedTestLazyReadsFullCache() {
setupCacheSource(true, 100, FLUSH_DELAY);
multiThreadedTestImpl();
shutDown();
}
@Test
public void multiThreadedTestFullReadsNoCache() {
setupCacheSource(false, 1, FLUSH_DELAY);
multiThreadedTestImpl();
shutDown();
}
@Test
public void multiThreadedTestFullReadsFullCache() {
setupCacheSource(false, 100, FLUSH_DELAY);
multiThreadedTestImpl();
shutDown();
}
@Test(enabled = false)
public void varyingFlushDelays() {
final int[] delays = new int[] {400, 500, 600, 700};
// Repeat to allow for timing discrepencies
for (int i = 0; i < 5; i++) {
final StringBuilder result = new StringBuilder();
for (int delay : delays) {
setupCacheSource(true, 1, delay);
final Pair<Double, Double> times = multiThreadedTestImpl();
shutDown();
result.append("\r\n").append("Delay=").append(delay).append("ms. Get=").append(times.getFirst()).append(
"ms, Put=").append(times.getSecond()).append("ms");
}
s_logger.info("{}", result);
}
}
}