/*
* ALMA - Atacama Large Millimiter Array (c) European Southern Observatory, 2006
*
* 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 alma.lasersource.test;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import cern.laser.source.alarmsysteminterface.FaultState;
import cern.laser.source.alarmsysteminterface.impl.ASIMessageHelper;
import cern.laser.source.alarmsysteminterface.impl.XMLMessageHelper;
import cern.laser.source.alarmsysteminterface.impl.message.ASIMessage;
import com.cosylab.acs.jms.ACSJMSMessageEntity;
import alma.acs.component.client.ComponentClientTestCase;
import alma.acs.nc.AcsEventSubscriber;
import alma.acscommon.ACS_NC_DOMAIN_ALARMSYSTEM;
import alma.acsnc.EventDescription;
import alma.alarmsystem.source.ACSAlarmSystemInterface;
import alma.alarmsystem.source.ACSAlarmSystemInterfaceFactory;
import alma.alarmsystem.source.ACSFaultState;
/**
* A stress test: it sends a great number of alarm sources and checks
* - if all of them have been published
* - they arrived in the right order
* - their contents match with the sent fault states
*
* The FaultStates are sent in the same order their are published if the sending
* is done with one source per each FS.
* If the source is only one for all the alarms, then it applies a kind of
* caching and the FSs do not arrive in the same order the are published.
* In the latter case, the FSs published and the FSs received are both sorted
* by FM and compared.
*
* @author acaproni
*
*/
public class SourceStressTest extends ComponentClientTestCase implements AcsEventSubscriber.Callback<ACSJMSMessageEntity> {
/**
* The relevant fields of a fault state to compare against
* the fault states received from the NC
*
* @author acaproni
*
*/
private class MiniFaultState implements Comparable<MiniFaultState> {
// The fields of the fault state
public final String FF, FM;
public final int FC;
public final long msec;
public final String description; // ACTIVE/Terminate
public Timestamp timestamp;
public MiniFaultState() {
FF=SourceStressTest.FF+(countFF++);
FM=SourceStressTest.FM+(countFM++);
FC=Math.abs(rnd.nextInt());
msec=System.currentTimeMillis();
timestamp=new Timestamp(msec);
if (rnd.nextInt()%2==0) {
description=FaultState.ACTIVE;
} else {
description=FaultState.TERMINATE;
}
assertNotNull(FF);
assertNotNull(FM);
assertNotNull(description);
assertNotNull(timestamp);
}
/**
* @see <code>java.lang.Comparable</code>
*/
@Override
public int compareTo(MiniFaultState o) {
return FM.compareTo(o.FM);
}
}
/**
* A class to store the FaultStates received from the NC
* This calls is needed to reimplement Comparable
*
* @author acaproni
*
*/
private class FaultStateReceived implements Comparable {
public final FaultState faultState;
public FaultStateReceived(FaultState fs) {
if (fs==null) {
throw new IllegalArgumentException("The FaultState can't be null");
}
faultState=fs;
}
@Override
public int compareTo(Object o) {
if (! (o instanceof FaultStateReceived)) {
System.err.println("Unexpected type "+o.getClass().getName());
}
return faultState.getMember().compareTo(((FaultStateReceived)o).faultState.getMember());
}
}
// The consumer
private volatile AcsEventSubscriber<ACSJMSMessageEntity> m_consumer;
// The number of fault states to send with the same source
private static final int NUM_OF_FS_TO_SEND_ONE_SOURCE = 10000;
// The number of fault states to send with the more sources
// Changing this number remember that CERN code SynchroBuffer
// create a thread per each source
private static final int NUM_OF_FS_TO_SEND_MORE_SOURCES = 1000;
// The NC name to listen for published fault states
private static final String m_channelName = "CMW.ALARM_SYSTEM.ALARMS.SOURCES.ALARM_SYSTEM_SOURCES";
// The fault states have a random FF and a random FM each of which
// is generated by appending a random number to the following strings
private static String FF = "Family";
private static String FM = "Member";
// The random number generator to create FF and FM
private static Random rnd = new Random(System.currentTimeMillis());
// The source
private ACSAlarmSystemInterface alarmSource;
// The fault states received from the NC
private List<FaultStateReceived> receivedFS;
// The fault state ready to be published
private List<MiniFaultState> statesToPublish;
/**
* This is to generate the name of the fault member sequentially
* by postponing the number to a string
*/
private static int countFM=0;
/**
* This is to generate the name of the fault family sequentially
* by postponing the number to a string
*/
private static int countFF=100000;
/**
* Constructor
* @throws Exception
*/
public SourceStressTest() throws Exception {
super("Source stress test");
}
/**
*
* @see alma.acs.component.client.ComponentClientTestCase#tearDown()
*/
protected void setUp() throws Exception {
// TODO Auto-generated method stub
super.setUp();
assertNotNull(getContainerServices());
m_consumer = getContainerServices().createNotificationChannelSubscriber(
m_channelName, ACS_NC_DOMAIN_ALARMSYSTEM.value, ACSJMSMessageEntity.class);
assertNotNull("Error instantiating the consumer",m_consumer);
m_consumer.addSubscription(this);
m_consumer.startReceivingEvents();
alarmSource = ACSAlarmSystemInterfaceFactory.createSource();
assertNotNull("Error instantiating the source",alarmSource);
receivedFS= Collections.synchronizedList(new ArrayList<FaultStateReceived>());
assertNotNull(receivedFS);
}
/**
*
* @see alma.acs.component.client.ComponentClientTestCase#tearDown()
*/
protected void tearDown() throws Exception {
m_consumer.disconnect();
super.tearDown();
}
@Override
public synchronized void receive(ACSJMSMessageEntity msg, EventDescription eventDescrip) {
ASIMessage asiMsg = null;
try {
asiMsg = XMLMessageHelper.unmarshal(msg.text);
} catch (Exception e) {
System.out.println("Exception caught while unmarshalling the msg "+e.getMessage());
e.printStackTrace();
// receiverError = e; // SendTest works with checking these errors in the test..
return;
}
Collection<FaultState>faultStates = ASIMessageHelper.unmarshal(asiMsg);
assertNotNull(faultStates);
for (FaultState fs: faultStates) {
assertNotNull(fs);
receivedFS.add(new FaultStateReceived(fs));
}
}
@Override
public Class<ACSJMSMessageEntity> getEventType() {
return ACSJMSMessageEntity.class;
}
/**
* Send a fault state to the NC.
* It uses the global source or build a new one depending on the
* parameter
*
* @param mfs The fault state to publish
* @param sameSource If true the same source is used to send the alarm
* if true a new source is built and the fault state is
* sent using this new source
*/
private void send(MiniFaultState mfs, boolean sameSource) throws Exception {
assertNotNull(mfs);
ACSFaultState fs = ACSAlarmSystemInterfaceFactory.createFaultState(mfs.FF, mfs.FM, mfs.FC);
assertNotNull("Error instantiating the FS",fs);
fs.setDescriptor(mfs.description);
fs.setUserTimestamp(mfs.timestamp);
if (sameSource) {
alarmSource.push(fs);
} else {
ACSAlarmSystemInterface newSource= ACSAlarmSystemInterfaceFactory.createSource();
assertNotNull(newSource);
newSource.push(fs);
}
}
private void checkFaultStates(MiniFaultState sent, FaultState recv) throws Exception {
assertEquals(sent.FF, recv.getFamily());
assertEquals(sent.FM, recv.getMember());
assertEquals(sent.FC, recv.getCode());
assertEquals(sent.description, recv.getDescriptor());
System.out.println("Sent "+sent.timestamp.getClass().getName()+", Recv "+recv.getUserTimestamp().getClass().getName());
assertEquals("Timestamp differ",sent.timestamp, recv.getUserTimestamp());
}
/**
* Build the data to publish and check for correctness
*
* @param len The number of items to biuld and put in the array
*/
private void buildData(int len) throws Exception {
statesToPublish= Collections.synchronizedList(new ArrayList<MiniFaultState>(len));
assertNotNull(statesToPublish);
// Build the array of state to publish
for (int t=0; t<len; t++) {
MiniFaultState fs = new MiniFaultState();
assertNotNull(fs);
statesToPublish.add(fs);
}
}
/**
* Wait until all the fault states are received or a timeout happened.
*
* @throws Exception in case of timeout
*/
private void waitForFSs() throws Exception {
int timeout = 60; // timeout in secs
int count=0;
int old=0; // The number of items read in the previous iteration
// Wait for all the alarms to be in the vector
while (receivedFS.size()<statesToPublish.size() && count<2*timeout) {
if (old!=receivedFS.size()) {
count=0;
old=receivedFS.size();
}
try {
Thread.sleep(500);
count++;
} catch (Exception e) {}
}
assertEquals(statesToPublish.size(), receivedFS.size());
}
/**
* Test by sending all the fault states using the same source.
* When all the alarms have arrived it checks for their
* correctness.
*
* NOTE: when using only on source, not all the FS arrive in the same
* order they are submitted to the source.
* For this reason the collection are sorted before comparing.
*
* @throws Exception
*/
public void testStressSameSource() throws Exception {
buildData(NUM_OF_FS_TO_SEND_ONE_SOURCE);
// Send the alarms
for (int t=0; t<statesToPublish.size(); t++) {
send(statesToPublish.get(t),true);
}
waitForFSs();
// Resort the collection before comparing
Collections.sort(receivedFS);
Collections.sort(statesToPublish);
for (int t=0; t< statesToPublish.size(); t++) {
checkFaultStates(statesToPublish.get(t), receivedFS.get(t).faultState);
}
}
}