/*******************************************************************************
* ALMA - Atacama Large Millimeter Array
* Copyright (c) ESO - European Southern Observatory, 2011
* (in the framework of the ALMA collaboration).
* All rights reserved.
*
* This library 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 library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*******************************************************************************/
package acs.benchmark.nc.client;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.both;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.Assert.assertThat;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import acs.benchmark.util.ContainerUtil;
import acs.benchmark.util.ContainerUtil.ContainerLogLevelSpec;
import alma.ACSErrTypeCommon.CouldntPerformActionEx;
import alma.ACSErrTypeCommon.wrappers.AcsJCouldntPerformActionEx;
import alma.acs.component.client.ComponentClient;
import alma.acs.logging.level.AcsLogLevelDefinition;
import alma.acs.pubsubtest.config.types.ImplLangT;
import alma.acs.util.AcsLocations;
import alma.benchmark.CHANNELNAME_CONTROL_REALTIME;
import alma.benchmark.CorbaNotifyConsumerOperations;
import alma.benchmark.CorbaNotifySupplierOperations;
import alma.benchmark.NcEventSpec;
import alma.maci.containerconfig.types.ContainerImplLangType;
/**
* A simple publisher-subscriber test that runs publisher and subscriber components
* on the local host.
* It uses the daemon / component test framework.
*
* @author hsommer
*/
public class LocalPubSubTest extends ComponentClient
{
/**
* Should normally be 'null' for localhost,
* but can be changed when using this test as a template for manual non-local tests.
*/
private static final String localhostName = null; //"alma-head";
private ContainerUtil containerUtil;
private PubSubComponentAccessUtil componentAccessUtil;
private final String supplierContainerName = "localSupplierContainer1";
ExecutorService singThrExec;
/**
* TODO: Check if this rule and the getMethodName() call in setUp() can be moved up to ComponentClient,
* if that adds a runtime dependency on junit, and how bad that would be.
* Probably we should add a class ComponentClientTestCaseJUnit4 that extends ComponentClient
* and only adds this testname business.
*/
@Rule
public TestName testName = new TestName();
public LocalPubSubTest() throws Exception {
super(null, AcsLocations.figureOutManagerLocation(), LocalPubSubTest.class.getSimpleName());
}
@Before
public void setUp() throws Exception {
String testMethodName = testName.getMethodName();
m_logger.info("----------------- " + testMethodName + " ----------------- ");
// run a local container. Logs are stored under $ACS_TMP/logs/
containerUtil = new ContainerUtil(getContainerServices());
containerUtil.loginToManager();
containerUtil.startContainer(localhostName, ContainerImplLangType.JAVA, supplierContainerName, null, true);
m_logger.info("Container '" + supplierContainerName + "' is ready.");
// configure container log levels
ContainerLogLevelSpec contLogLevelSpec = new ContainerLogLevelSpec(AcsLogLevelDefinition.WARNING, AcsLogLevelDefinition.DEBUG);
contLogLevelSpec.addNamedLoggerSpec("jacorb@"+supplierContainerName, AcsLogLevelDefinition.WARNING, AcsLogLevelDefinition.WARNING);
containerUtil.setContainerLogLevels(supplierContainerName, contLogLevelSpec);
assertThat(containerUtil.isContainerLoggedIn(supplierContainerName), is(true));
componentAccessUtil = new PubSubComponentAccessUtil(getContainerServices());
singThrExec = Executors.newSingleThreadExecutor(getContainerServices().getThreadFactory());
}
@After
public void tearDown() throws Exception {
if (componentAccessUtil != null) {
componentAccessUtil.releaseAllComponents(true);
}
if (containerUtil != null) {
containerUtil.stopContainer(localhostName, supplierContainerName);
containerUtil.logoutFromManager();
}
singThrExec.shutdownNow();
singThrExec.awaitTermination(1, TimeUnit.SECONDS);
super.tearDown();
}
/**
* Publishes 10.000 "MountStatusData" events on the NC "CONTROL_REALTIME",
* using a single supplier component "JavaSupplier-1" for sequential publishing at maximum rate.
* The call to the supplier component is synchronous, returning when all events are published.
* This allows us to measure the the publishing performance without sync'ing up through a subscriber.
*/
@Test
public void testOneSupplierNoSubscriber() throws Exception {
final int numEvents = 10000;
CorbaNotifySupplierOperations supplierComp = null;
try {
// Create dynamic supplier component
String componentName = "JavaSupplier-1";
supplierComp = componentAccessUtil.getDynamicSupplierComponent(componentName, supplierContainerName, ImplLangT.JAVA);
// supplier setup
String[] ncNames = new String[] {CHANNELNAME_CONTROL_REALTIME.value};
supplierComp.ncConnect(ncNames);
m_logger.info("Connected to NC " + ncNames[0]);
NcEventSpec[] ncEventSpecs = new NcEventSpec[] {
new NcEventSpec(ncNames[0],
new String[] {"MountStatusData"},
"" // don't care about antenna name here
) };
// Let publisher component publish these events
int callTimeInternalMillis = supplierComp.sendEvents(ncEventSpecs, -1, numEvents);
m_logger.info("Single supplier comp '" + componentName + "' sent " + numEvents
+ " MountStatusData events at max speed to NC '"
+ ncNames[0] + "' in " + callTimeInternalMillis + " ms.");
assertThat("Expecting at most 4 ms for publishing an event to a NotifyService.",
callTimeInternalMillis, lessThanOrEqualTo(numEvents * 4));
} finally {
// clean up
if (supplierComp != null) supplierComp.ncDisconnect();
if (componentAccessUtil != null) componentAccessUtil.releaseAllComponents(true);
}
}
/**
* Publishes >= 200 "MountStatusData" or "LightweightMountStatusData" events on the NC "CONTROL_REALTIME",
* using a single supplier component "JavaSupplier-1" for sequential publishing at a rate of 1 events every 48 ms,
* and a single subscriber component "JavaSubscriber" for counting the events.
* The call to the supplier component is asynchronous, returning while the publisher runs in "indefinite publishing" mode.
* Once the subscriber has received enough data, we terminate the supplier.
*/
@Test
public void testOneSupplierOneSubscriberMixedEvents() throws Throwable {
final int numEvents = 200;
final int eventPeriodMillis = 48;
String[] ncNames = new String[] {CHANNELNAME_CONTROL_REALTIME.value};
// mixed events spec
final NcEventSpec[] ncEventSpecs = new NcEventSpec[] {
new NcEventSpec(ncNames[0],
new String[] {"MountStatusData", "LightweightMountStatusData"},
"" // don't care about antenna name here
) };
// Create, configure and activate dynamic subscriber component.
// It must be running before we publish events, to make sure we don't lose any.
String subscriberContainerName = "localSubscriberContainer1";
String subscriberComponentName = "JavaSubscriber-1";
try {
containerUtil.startContainer(localhostName, ContainerImplLangType.JAVA, subscriberContainerName, null, true);
final CorbaNotifyConsumerOperations subscriberComp = componentAccessUtil.getDynamicSubscriberComponent(subscriberComponentName, subscriberContainerName, ImplLangT.JAVA);
subscriberComp.ncConnect(ncNames);
m_logger.info("Connected subscriber to NC " + ncNames[0]);
Callable<Integer> runSubscriber = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
m_logger.info("About to call subscriber#receiveEvents in a separate thread...");
return subscriberComp.receiveEvents(ncEventSpecs, 0, numEvents);
}
};
Future<Integer> subscriberCallFuture = singThrExec.submit(runSubscriber);
Thread.sleep(100); // to "ensure" that the subscriber is ready before we publish events
// Create dynamic supplier component
String componentName = "JavaSupplier-1";
CorbaNotifySupplierOperations supplierComp = componentAccessUtil.getDynamicSupplierComponent(
componentName, supplierContainerName, ImplLangT.JAVA);
// supplier setup
supplierComp.ncConnect(ncNames);
m_logger.info("Connected supplier to NC " + ncNames[0] + ". Will now send ~" + numEvents + " events, one every " + eventPeriodMillis + " ms.");
// Let publisher component publish events as long as it takes for the subscriber to get enough of them
supplierComp.sendEvents(ncEventSpecs, eventPeriodMillis, -1);
int subscriberReceptionTimeMillis = subscriberCallFuture.get(60, TimeUnit.SECONDS);
m_logger.info("Subscriber component done. It received " + numEvents + " events in " + subscriberReceptionTimeMillis + " ms.");
int expectedReceptionTimeMillis = numEvents * eventPeriodMillis;
assertThat("It should have taken around " + expectedReceptionTimeMillis + " ms to receive the events.",
subscriberReceptionTimeMillis,
is(both(greaterThan((int)(0.90 * expectedReceptionTimeMillis))).and(lessThan((int)(1.1 * expectedReceptionTimeMillis)))));
// stop supplier (it was in 'infinite' sending mode)
supplierComp.interrupt();
} catch (CouldntPerformActionEx ex) {
throw AcsJCouldntPerformActionEx.fromCouldntPerformActionEx(ex);
}
catch (ExecutionException ex) {
Throwable ex2 = ex.getCause();
if (ex2 instanceof CouldntPerformActionEx) {
throw AcsJCouldntPerformActionEx.fromCouldntPerformActionEx((CouldntPerformActionEx)ex2);
}
else {
throw ex2;
}
}
finally {
componentAccessUtil.releaseComponent(subscriberComponentName, true);
containerUtil.stopContainer(localhostName, subscriberContainerName);
// for supplier component and container we trust the tearDown method..
}
}
}