/*
* ProActive Parallel Suite(TM):
* The Open Source library for parallel and distributed
* Workflows & Scheduling, Orchestration, Cloud Automation
* and Big Data Analysis on Enterprise Grids & Clouds.
*
* Copyright (c) 2007 - 2017 ActiveEon
* Contact: contact@activeeon.com
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation: version 3 of
* the License.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If needed, contact us to obtain a release under GPL Version 2 or 3
* or a different license than the AGPL.
*/
package org.ow2.proactive.resourcemanager.frontend;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.log4j.Logger;
import org.objectweb.proactive.Body;
import org.objectweb.proactive.InitActive;
import org.objectweb.proactive.RunActive;
import org.objectweb.proactive.Service;
import org.objectweb.proactive.api.PAActiveObject;
import org.objectweb.proactive.core.ProActiveException;
import org.objectweb.proactive.core.UniqueID;
import org.objectweb.proactive.core.body.request.Request;
import org.objectweb.proactive.core.util.wrapper.BooleanWrapper;
import org.objectweb.proactive.extensions.annotation.ActiveObject;
import org.ow2.proactive.resourcemanager.authentication.Client;
import org.ow2.proactive.resourcemanager.common.RMConstants;
import org.ow2.proactive.resourcemanager.common.event.*;
import org.ow2.proactive.resourcemanager.core.RMCore;
import org.ow2.proactive.resourcemanager.core.history.NodeHistory;
import org.ow2.proactive.resourcemanager.core.jmx.RMJMXHelper;
import org.ow2.proactive.resourcemanager.core.properties.PAResourceManagerProperties;
import org.ow2.proactive.resourcemanager.db.RMDBManager;
import org.ow2.proactive.resourcemanager.exception.RMException;
import org.ow2.proactive.resourcemanager.utils.AtomicRMStatisticsHolder;
/**
* Active object designed for the Monitoring of the Resource Manager.
* This class provides a way for a monitor to ask at
* Resource Manager to throw events
* generated by nodes and nodes sources management. RMMonitoring dispatch
* events thrown by {@link RMCore} to all its monitors.
*
*
* @see org.ow2.proactive.resourcemanager.frontend.RMEventListener
*
* @author The ProActive Team
* @since ProActive Scheduling 0.9
*/
@ActiveObject
public class RMMonitoringImpl implements RMMonitoring, RMEventListener, InitActive, RunActive {
private static final Logger logger = Logger.getLogger(RMMonitoringImpl.class);
// Attributes
private RMCore rmcore;
private Map<UniqueID, EventDispatcher> dispatchers;
private transient ExecutorService eventDispatcherThreadPool;
/** Resource Manager's statistics */
public static final AtomicRMStatisticsHolder rmStatistics = new AtomicRMStatisticsHolder();
// ----------------------------------------------------------------------//
// CONSTRUTORS
/** ProActive empty constructor */
public RMMonitoringImpl() {
}
/**
* Creates the RMMonitoring active object.
* @param rmcore Stub of the RMCore active object.
*/
public RMMonitoringImpl(RMCore rmcore) {
this.dispatchers = new HashMap<>();
this.rmcore = rmcore;
}
/**
* @see org.objectweb.proactive.InitActive#initActivity(org.objectweb.proactive.Body)
*/
public void initActivity(Body body) {
try {
PAActiveObject.registerByName(PAActiveObject.getStubOnThis(), RMConstants.NAME_ACTIVE_OBJECT_RMMONITORING);
eventDispatcherThreadPool = Executors.newFixedThreadPool(PAResourceManagerProperties.RM_MONITORING_MAX_THREAD_NUMBER.getValueAsInt());
} catch (ProActiveException e) {
logger.debug("Cannot register RMMonitoring. Aborting...", e);
PAActiveObject.terminateActiveObject(true);
}
}
/**
* Method controls the execution of every request.
* Tries to keep this active object alive in case of any exception.
*/
public void runActivity(Body body) {
Service service = new Service(body);
while (body.isActive()) {
try {
Request request = service.blockingRemoveOldest();
if (request != null) {
try {
service.serve(request);
} catch (Throwable e) {
logger.error("Cannot serve request: " + request, e);
}
}
} catch (InterruptedException e) {
logger.warn("runActivity interrupted", e);
}
}
}
private class EventDispatcher implements Runnable {
protected Client client;
protected RMEventListener listener;
protected LinkedList<RMEvent> events;
protected List<RMEventType> eventTypes = null;
protected AtomicBoolean inProcess = new AtomicBoolean(false);
protected long counter = 0;
public EventDispatcher(Client client, RMEventListener listener, RMEventType[] eventTypes) {
this.client = client;
this.listener = listener;
if (eventTypes != null && eventTypes.length > 0) {
this.eventTypes = Arrays.asList(eventTypes);
}
this.events = new LinkedList<>();
}
public void run() {
int numberOfEventDelivered = 0;
long timeStamp = System.currentTimeMillis();
if (logger.isDebugEnabled()) {
logger.debug("Initializing " + Thread.currentThread() + " for events delivery to client '" + client +
"'");
}
while (true) {
RMEvent event = null;
synchronized (events) {
if (logger.isDebugEnabled()) {
logger.debug(events.size() + " pending events for the client '" + client + "'");
}
if (events.size() > 0) {
event = events.removeFirst();
}
}
if (event != null) {
deliverEvent(event);
numberOfEventDelivered++;
} else {
break;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Finishing delivery in " + Thread.currentThread() + " to client '" + client + "'. " +
numberOfEventDelivered + " events were delivered in " +
(System.currentTimeMillis() - timeStamp) + " ms");
}
inProcess.set(false);
}
private void deliverEvent(RMEvent event) {
//dispatch event
long timeStamp = System.currentTimeMillis();
if (logger.isDebugEnabled()) {
logger.debug("Dispatching event '" + event.toString() + "' to client " + client);
}
try {
if (event instanceof RMNodeEvent) {
RMNodeEvent nodeEvent = (RMNodeEvent) event;
listener.nodeEvent(nodeEvent);
} else if (event instanceof RMNodeSourceEvent) {
RMNodeSourceEvent sourceEvent = (RMNodeSourceEvent) event;
listener.nodeSourceEvent(sourceEvent);
} else {
listener.rmEvent(event);
}
long time = System.currentTimeMillis() - timeStamp;
if (logger.isDebugEnabled()) {
logger.debug("Event '" + event.toString() + "' has been delivered to client " + client + " in " +
time + " ms");
}
} catch (Exception e) {
// probably listener was removed or disconnected
logger.warn("Cannot send events to " + client, e);
synchronized (dispatchers) {
dispatchers.remove(client.getId());
logger.warn(client + " was removed from listeners");
}
}
}
public void queueEvent(RMEvent event) {
synchronized (events) {
if (eventTypes == null || eventTypes.contains(event.getEventType())) {
try {
// clone event object to set a different counter for each client
RMEvent cloneEvent = (RMEvent) event.clone();
cloneEvent.setCounter(++counter);
events.add(cloneEvent);
if (inProcess.get()) {
if (logger.isDebugEnabled()) {
logger.debug("Communication to the client " + client +
" is in progress in one thread of the thread pool.");
}
} else {
inProcess.set(true);
eventDispatcherThreadPool.submit(this);
}
} catch (CloneNotSupportedException ex) {
logger.error(ex.getMessage(), ex);
}
}
}
}
}
private class GroupEventDispatcher extends EventDispatcher {
public GroupEventDispatcher(Client client, RMEventListener stub, RMEventType[] events) {
super(client, stub, events);
}
public void run() {
long timeStamp = System.currentTimeMillis();
if (logger.isDebugEnabled()) {
logger.debug("Initializing " + Thread.currentThread() + " for events delivery to client '" + client +
"'");
}
while (true) {
LinkedList<RMEvent> toDeliver = new LinkedList<>();
synchronized (events) {
if (logger.isDebugEnabled()) {
logger.debug(events.size() + " pending events for the client '" + client + "'");
}
if (events.size() > 0) {
toDeliver.clear();
toDeliver.addAll(events);
events.clear();
}
}
if (toDeliver.size() > 0) {
if (deliverEvents(toDeliver)) {
if (logger.isDebugEnabled()) {
logger.debug("Finishing delivery in " + Thread.currentThread() + " to client '" + client +
"'. " + toDeliver.size() + " events were delivered in " +
(System.currentTimeMillis() - timeStamp) + " ms");
}
}
} else {
break;
}
}
inProcess.set(false);
}
private boolean deliverEvents(Collection<RMEvent> events) {
//dispatch event
long timeStamp = System.currentTimeMillis();
if (logger.isDebugEnabled()) {
for (RMEvent event : events) {
logger.debug("Dispatching events '" + event.toString() + "' to client " + client);
}
}
try {
((RMGroupEventListener) listener).notify(events);
long time = System.currentTimeMillis() - timeStamp;
if (logger.isDebugEnabled()) {
logger.debug("Events has been delivered to client " + client + " in " + time + " ms");
}
} catch (Exception e) {
// probably listener was removed or disconnected
logger.warn("Cannot send events to " + client, e);
synchronized (dispatchers) {
dispatchers.remove(client.getId());
logger.warn(client + " was removed from listeners");
}
return false;
}
return true;
}
}
/** Register a new Resource manager listener.
* Way to a monitor object to ask at RMMonitoring to throw
* RM events to it.
* @param stub a listener object which implements {@link RMEventListener}
* interface.
* @param events list of wanted events that must be received.
* @return RMInitialState snapshot of RM's current state : nodes and node sources.
* */
public RMInitialState addRMEventListener(RMEventListener stub, RMEventType... events) {
UniqueID id = PAActiveObject.getContext().getCurrentRequest().getSourceBodyID();
logger.debug("Adding the RM listener for " + id.shortString());
synchronized (dispatchers) {
Client client = null;
synchronized (RMCore.clients) {
client = RMCore.clients.get(id);
}
if (client == null) {
throw new IllegalArgumentException("Unknown client " + id.shortString());
}
if (stub instanceof RMGroupEventListener) {
this.dispatchers.put(id, new GroupEventDispatcher(client, stub, events));
} else {
this.dispatchers.put(id, new EventDispatcher(client, stub, events));
}
}
return rmcore.getRMInitialState();
}
/**
* Removes a listener from RMMonitoring. Only listener itself must call this method
*/
public void removeRMEventListener() throws RMException {
UniqueID id = PAActiveObject.getContext().getCurrentRequest().getSourceBodyID();
String shortId = id.shortString();
if (removeRMEventListener(id)) {
logger.debug("Removing the RM listener for " + shortId);
} else {
throw new RMException("Unknown listener found: " + shortId);
}
}
public boolean removeRMEventListener(UniqueID id) throws RMException {
if (dispatchers == null || dispatchers.isEmpty()) {
return false;
}
synchronized (dispatchers) {
return dispatchers.remove(id) != null;
}
}
@Deprecated
public boolean isAlive() {
return true;
}
public void queueEvent(RMEvent event) {
//dispatch event
if (logger.isDebugEnabled()) {
logger.debug(event.toString() + " event");
}
synchronized (dispatchers) {
for (EventDispatcher dispatcher : dispatchers.values()) {
dispatcher.queueEvent(event);
}
}
}
/**
* @see org.ow2.proactive.resourcemanager.frontend.RMEventListener#nodeEvent(org.ow2.proactive.resourcemanager.common.event.RMNodeEvent)
*/
public void nodeEvent(RMNodeEvent event) {
RMMonitoringImpl.rmStatistics.nodeEvent(event);
RMDBManager.getInstance().saveNodeHistory(new NodeHistory(event));
queueEvent(event);
}
/**
* @see org.ow2.proactive.resourcemanager.frontend.RMEventListener#nodeSourceEvent(org.ow2.proactive.resourcemanager.common.event.RMNodeSourceEvent)
*/
public void nodeSourceEvent(RMNodeSourceEvent event) {
queueEvent(event);
}
/**
* @see org.ow2.proactive.resourcemanager.frontend.RMEventListener#rmEvent(org.ow2.proactive.resourcemanager.common.event.RMEvent)
*/
public void rmEvent(RMEvent event) {
RMMonitoringImpl.rmStatistics.rmEvent(event);
queueEvent(event);
}
/**
* Stop and remove monitoring active object
*/
public BooleanWrapper shutdown() {
//throwing shutdown event
rmEvent(new RMEvent(RMEventType.SHUTDOWN));
PAActiveObject.terminateActiveObject(false);
RMJMXHelper.getInstance().shutdown();
// initiating shutdown
eventDispatcherThreadPool.shutdown();
try {
// waiting until all clients will be notified
eventDispatcherThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
logger.warn("", e);
}
return new BooleanWrapper(true);
}
public Logger getLogger() {
return logger;
}
/**
* Gets the current snapshot of the resource manager state providing
* detailed nodes and node source information.
*
* To obtain summary of the resource manager state use {@link ResourceManager}.getState()
*
* @return the current state of the resource manager
*/
public RMInitialState getState() {
return rmcore.getRMInitialState();
}
}