/*
ALMA - Atacama Large Millimiter Array
* Copyright (c) European Southern Observatory, 2012
*
* 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.acs.alarm.gui.senderpanel;
import java.util.ArrayList;
import java.util.Properties;
import java.util.concurrent.ArrayBlockingQueue;
import alma.acs.alarm.gui.senderpanel.SenderPanelUtils.AlarmDescriptorType;
import alma.acs.alarm.gui.senderpanel.SenderPanelUtils.Triplet;
import alma.acs.container.ContainerServices;
import alma.acs.logging.AcsLogLevel;
import alma.acsErrTypeAlarmSourceFactory.ACSASFactoryNotInitedEx;
import alma.acsErrTypeAlarmSourceFactory.SourceCreationErrorEx;
/**
* Sends alarms in parallel by sending each alarm to a different sender.
* <P>
* There is upper limit in the number of alarms sent each second in {@link #maxNumberOfAlarmsPerSecond}
* and implemented by a {@link ArrayBlockingQueue} bounded to {@link #maxNumberOfAlarmsPerSecond}.
* <P>
* The alarms are sent by the thread that loops once per second (aprox) and empties the queue.
* <P>
* The {@link ArrayBlockingQueue} control the max number of alarms queued and ready
* to be sent in the next second. However it does not prevent to add new alarms while
* the thread is sending alarms (i.e. the thread gets a alarm out of the queue so
* a new room is available and the send pushes a new alarm). <BR>
* To be sure that the number of alarms sent each second is at most {@link #maxNumberOfAlarmsPerSecond},
* the thread counts the number of alarms it get out of the queue at each iteration.
*
* @author acaproni
* @version $Id: ParallelAlarmSender.java,v 1.2 2012/12/14 18:10:48 acaproni Exp $
* @since ACS 11.0
*/
public class ParallelAlarmSender implements Runnable {
/**
* An alarm to send.
* <P>
* {@link AlarmToSend} objects are put in a queue to be retrieved
* and published asynchronously by the thread.
*
* @author acaproni
*
*/
public static class AlarmToSend {
/**
* The triplet
*/
public final Triplet triplet;
/**
* The user properties
*/
public final Properties userProperties;
/**
* The alarm descriptor
*/
public final AlarmDescriptorType descriptor;
/**
* Constructor
*
* @param triplet The triplet
* @param userProperties The user properties
* @param descriptor The descriptor
*/
public AlarmToSend(Triplet triplet,Properties userProperties,AlarmDescriptorType descriptor) {
if (triplet==null) {
throw new IllegalArgumentException("The Triplet can't be null");
}
if (descriptor==null) {
throw new IllegalArgumentException("The descriptor can't be null");
}
this.triplet=triplet;
this.userProperties=userProperties;
this.descriptor=descriptor;
}
}
/**
* The senders of alarms
*/
private final ArrayList<AlarmSender> senders;
/**
* <code>numOfSender</code> select the sender to send an alarm
* by cycling through all the senders.
*/
private int numOfSender=0;
/**
* The ContainerServices
*/
private final ContainerServices contSvcs;
/**
* The time (msec) between two log messages showing the number
* of alarms sent by the class
*/
private final long TIME_BETWEEN_LOGS=60000;
/**
* The max number of alarms that can be sent each minute.
* From the last log I read from the AOS I got 3133 logs/minute.
* (3500 alarms per minute is around 60 alarms per second)
*/
private final int maxNumberOfAlarmsPerSecond=60;
/**
* / Log a message every minute reporting the number of alarms published
*/
private long lastLogMessageTime=System.currentTimeMillis();
/**
* The total number of alarms sent by this sender
*/
private int numOfAlarmsSent=0;
/**
* The total number of alarms sent by this sender in the last minute
*/
private int numOfAlarmsSentInLastMinute=0;
/**
* The thread that runs this Runnable
*/
private final Thread thread;
/**
* Signal the thread to terminate
*/
private volatile boolean terminateThread=false;
/**
* The alarms to send in a second.
* <P>
* The array blocking queue ensures the maximum number of logs sent per second
* because it blocks if a ut is called when the queue is full.
*
*/
private final ArrayBlockingQueue<AlarmToSend> alarmsToSend;
/**
* Constructor
*
* @param svcs The {@link ContainerServices}
* @param numOfSenders The number of {@link AlarmSender} to run in parallel
* @param timeBetweenAlarms The time between 2 send by each {@link AlarmSender}
*
* @throws SourceCreationErrorEx
* @throws ACSASFactoryNotInitedEx
*/
public ParallelAlarmSender(ContainerServices svcs, int numOfSenders, long timeBetweenAlarms)
throws ACSASFactoryNotInitedEx, SourceCreationErrorEx {
if (svcs==null) {
throw new IllegalArgumentException("The ContainerServices can't be null");
}
if (numOfSenders<=1) {
throw new IllegalArgumentException("The number of thread must be greater the 1");
}
this.contSvcs=svcs;
this.alarmsToSend= new ArrayBlockingQueue<AlarmToSend>(maxNumberOfAlarmsPerSecond);
senders = new ArrayList<AlarmSender>(numOfSenders);
for (int t=0; t<numOfSenders; t++) {
senders.add(new AlarmSender(svcs, "AlarmSender_"+t,timeBetweenAlarms));
}
thread=new Thread(this, this.getClass().getName());
thread.setDaemon(true);
}
/**
* Start the threads.
*/
public void start() {
thread.start();
for (AlarmSender sender: senders) {
sender.start();
}
}
/**
* Close the sender and the threads
*/
public void close() {
terminateThread=true;
thread.interrupt();
for (AlarmSender sender: senders) {
sender.close();
}
}
/**
* Add a listener
*
* @param listener The listener to notify when an alarm has been sent
*/
public void addListener(AlarmSentListener listener) {
if (listener==null) {
throw new IllegalArgumentException("The listener can't be null");
}
for (AlarmSender sender: senders) {
sender.addListener(listener);
}
}
/**
* Remove a listener
*
* @param listener The listener to notify when an alarm has been sent
* @return <code>true</code> if this set contained the specified element
*/
public void removeListener(AlarmSentListener listener) {
if (listener==null) {
throw new IllegalArgumentException("The listener can't be null");
}
for (AlarmSender sender: senders) {
sender.removeListener(listener);
}
}
/**
* Send the specified alarm to on of the {@link AlarmSender} to be sent to the service.
* <P>
* This method return immediately: each alarm is queued and sent later on by the thread.
* <P>If the queue of alarms contains more then {@value #maxAlarmsToQueue}, the caller waits until
* the thread frees the queue.
*
* @param triplet The triplet in the form FF,FM,FC
* @param The descriptor
* @param props The user properties
* @throws InterruptedException If interrupted while awaiting to put the alarm in the queue
* @see AlarmSender#send(alma.acs.alarm.gui.senderpanel.SenderPanelUtils.Triplet, alma.acs.alarm.gui.senderpanel.SenderPanelUtils.AlarmDescriptorType, java.util.Properties)
*/
public void send(Triplet triplet, AlarmDescriptorType descriptor, Properties props) throws InterruptedException {
if (terminateThread) {
return;
}
alarmsToSend.put(new AlarmToSend(triplet, props, descriptor));
}
@Override
public void run() {
while (!terminateThread) {
int count=0; // number of alarms sent in this iteration
while (count<maxNumberOfAlarmsPerSecond && !terminateThread) {
AlarmToSend alarm = alarmsToSend.poll();
if (alarm==null) {
// No more alarms in queue;
break;
}
try {
senders.get(numOfSender).send(alarm);
numOfSender = (numOfSender + 1) % senders.size();
numOfAlarmsSent++;
numOfAlarmsSentInLastMinute++;
count++;
} catch (InterruptedException ie) {
continue;
}
}
long now=System.currentTimeMillis();
if (now-lastLogMessageTime>TIME_BETWEEN_LOGS) {
int alarmsQueued=0;
for (AlarmSender sender: senders) {
alarmsQueued+=sender.alarmsWaiting();
}
lastLogMessageTime=now;
contSvcs.getLogger().log(AcsLogLevel.INFO, "Tot. alarms sent: "+numOfAlarmsSent+", Tot. alarms waiting to be sent: "+alarmsQueued+", in the last minute: "+numOfAlarmsSentInLastMinute);
numOfAlarmsSentInLastMinute=0;
}
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {}
}
}
}