package org.radargun.jmx;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.management.MBeanServerConnection;
import javax.management.Notification;
import javax.management.NotificationFilterSupport;
import javax.management.NotificationListener;
import javax.management.remote.JMXConnectionNotification;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import org.radargun.logging.Log;
import org.radargun.logging.LogFactory;
import org.radargun.utils.TimeService;
/**
*
* Periodically polls for values exposed via JMX on multiple nodes.
*
* @author Michal Linhard <mlinhard@redhat.com>
*
*/
public abstract class JMXPoller implements NotificationListener {
public static final String DEFAULT_SERVICE_URL_TEMPLATE = "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi";
private static Log log = LogFactory.getLog(JMXPoller.class);
private List<InetSocketAddress> jmxEndpoints;
private long queryTimeout;
private ConcurrentHashMap<InetSocketAddress, JMXConnector> connectors;
private String serviceUrlTemplate;
public static class Result {
public Exception connectError;
public Exception pollError;
public Object value;
public Result(Exception connectError, Exception pollError, Object value) {
this.connectError = connectError;
this.pollError = pollError;
this.value = value;
}
}
/**
*
* Create a new JMXPoller.
*
* @param jmxEndpoints
* jmx endpoints in form host:port
* @param logicalNodeNames
* logical node names, list corresponding to jmx endpoints with size and positions. may
* be null, in that case logical names won't be used.
* @param queryTimeout
* @param serviceUrlTemplate
*/
protected JMXPoller(List<InetSocketAddress> jmxEndpoints, long queryTimeout, String serviceUrlTemplate) {
this.jmxEndpoints = jmxEndpoints;
this.queryTimeout = queryTimeout;
this.connectors = new ConcurrentHashMap<InetSocketAddress, JMXConnector>(jmxEndpoints.size());
this.serviceUrlTemplate = serviceUrlTemplate;
}
protected JMXPoller(List<InetSocketAddress> jmxEndpoints, long queryTimeout) {
this(jmxEndpoints, queryTimeout, DEFAULT_SERVICE_URL_TEMPLATE);
}
/**
* Override to poll for certain JMX attributes.
*
* @param connection
* Connection to a JMX endpoint.
* @param nodeName
* Logical node name, if logical node names are not used, then endpoint string
* host:port;
* @return Custom response object.
* @throws Exception
*/
protected abstract Object pollNode(MBeanServerConnection connection, String nodeName, int nodeIdx) throws Exception;
protected String endpointToString(InetSocketAddress endpoint) {
return endpoint.getHostName() + ":" + endpoint.getPort();
}
private void discardConnector(InetSocketAddress endpoint, JMXConnector connector) {
connectors.remove(endpoint);
if (connector != null) {
try {
connector.close();
} catch (IOException e1) {
log.trace("Error while closing connector", e1);
}
}
}
public synchronized List<Result> poll() {
final Result[] results = new Result[jmxEndpoints.size()];
Thread[] tryPoll = new Thread[jmxEndpoints.size()];
for (int i = 0; i < jmxEndpoints.size(); i++) {
final InetSocketAddress endpoint = jmxEndpoints.get(i);
final String node = endpointToString(endpoint);
final int nodeIdx = i;
tryPoll[i] = new Thread("tryPoll-" + node) {
@Override
public void run() {
JMXConnector connector = null;
MBeanServerConnection connection = null;
try {
connector = connect(endpoint);
connection = connector.getMBeanServerConnection();
} catch (Exception e) {
log.trace("Discarding connector to endpoint " + endpoint + " because of an exception.", e);
discardConnector(endpoint, connector);
results[nodeIdx] = new Result(e, null, null);
return;
}
try {
results[nodeIdx] = new Result(null, null, pollNode(connection, node, nodeIdx));
} catch (Exception e) {
discardConnector(endpoint, connector);
results[nodeIdx] = new Result(null, e, null);
}
}
};
tryPoll[i].start();
}
long waitEnd = TimeService.currentTimeMillis() + queryTimeout;
boolean broken = false;
for (int i = 0; i < tryPoll.length; i++) {
try {
long maxJoinWait = waitEnd - TimeService.currentTimeMillis();
if (maxJoinWait <= 0) {
broken = true;
break;
}
tryPoll[i].join(maxJoinWait);
} catch (InterruptedException e) {
if (results[i] == null) {
results[i] = new Result(null, e, null);
}
}
}
if (broken) {
for (int i = 0; i < tryPoll.length; i++) {
tryPoll[i].interrupt();
}
}
// return current snapshot of the map, the results map may get modified
// by an unfinished thread
Result[] a = Arrays.copyOf(results, results.length);
for (int i = 0; i < a.length; i++) {
if (a[i] == null) {
a[i] = new Result(null, null, null);
}
}
return Arrays.asList(a);
}
public synchronized void closeConnections() {
final Set<JMXConnector> connectors1 = new HashSet<JMXConnector>(connectors.values());
connectors.clear();
new Thread(new Runnable() {
@Override
public void run() {
for (JMXConnector ctor : connectors1) {
try {
ctor.close();
} catch (Exception e) {
log.trace("Error while closing JMXConnector", e);
}
}
}
}, "JMXPoller.closeConnections").start();
}
private JMXConnector connect(final InetSocketAddress endpoint) throws Exception {
JMXConnector cachedConnector = connectors.get(endpoint);
if (cachedConnector != null) {
return cachedConnector;
}
JMXServiceURL serviceURL = new JMXServiceURL(String.format(this.serviceUrlTemplate, endpoint.getHostName(),
endpoint.getPort()));
JMXConnector newConnector = JMXConnectorFactory.newJMXConnector(serviceURL, null);
try {
newConnector.connect();
} catch (Exception e) {
newConnector.close();
throw e;
}
if (log.isTraceEnabled()) {
log.trace("created new connector " + newConnector + " to " + endpoint);
}
JMXConnector oldConnector = connectors.putIfAbsent(endpoint, newConnector);
if (oldConnector != null) {
newConnector.close();
cachedConnector = oldConnector;
} else {
NotificationFilterSupport closedFilter = new NotificationFilterSupport();
closedFilter.enableType(JMXConnectionNotification.CLOSED);
newConnector.addConnectionNotificationListener(this, closedFilter, endpoint);
cachedConnector = newConnector;
}
return cachedConnector;
}
public List<InetSocketAddress> getEndpoints() {
return jmxEndpoints;
}
@Override
public void handleNotification(Notification notification, Object node) {
if (log.isTraceEnabled()) {
log.trace("Notification received: " + notification + " handback: " + node);
}
connectors.remove(node);
}
}