/*
* Tigase Jabber/XMPP Server
* Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org>
*
* This program 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. Look for COPYING file in the top folder.
* If not, see http://www.gnu.org/licenses/.
*
* $Rev: 2411 $
* Last modified by $Author: kobit $
* $Date: 2010-10-27 20:27:58 -0600 (Wed, 27 Oct 2010) $
*
*/
package tigase.stats;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.MBeanServerConnection;
import javax.management.MBeanServerInvocationHandler;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectionNotification;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import tigase.util.DataTypes;
/**
* @author Artur Hefczyc Created Jun 3, 2011
*/
public class JavaJMXProxyOpt implements NotificationListener {
private static final Logger log = Logger.getLogger(JavaJMXProxyOpt.class.getName());
private String id = null;
private String hostname = null;
private int port = -1;
private String userName = null;
private String password = null;
private String urlPath = null;
private JMXServiceURL jmxUrl = null;
private JMXConnector jmxc = null;
private long delay = -1;
private long interval = -1;
private boolean loadHistory = false;
private boolean initialized = false;
private StatisticsProviderMBean tigBean = null;
private MBeanServerConnection server = null;
private StatisticsUpdater updater = null;
private Date lastDisconnectTime = null;
private int cpuNo = 0;
private List<JMXProxyListenerOpt> listeners = new LinkedList<JMXProxyListenerOpt>();
private Set<String> metrics = new LinkedHashSet<String>();
private Map<String, LinkedList<Object>> history = null;
private String sysDetails = "No data yet...";
public JavaJMXProxyOpt(String id, String hostname, int port, String userName,
String password, long delay, long interval, boolean loadHistory) {
this.id = id;
this.hostname = hostname;
this.port = port;
this.userName = userName;
this.password = password;
this.delay = delay;
this.interval = interval;
this.urlPath = "/jndi/rmi://" + this.hostname + ":" + this.port + "/jmxrmi";
this.loadHistory = loadHistory;
System.out.println("Created: " + id + ":" + hostname + ":" + port);
}
public void addJMXProxyListener(JMXProxyListenerOpt listener) {
listeners.add(listener);
String[] dataIds = listener.getDataIds();
if (dataIds != null && dataIds.length > 0) {
for (String did : dataIds) {
metrics.add(did);
}
}
}
public void connect() throws Exception {
this.jmxUrl = new JMXServiceURL("rmi", "", 0, this.urlPath);
String[] userCred = new String[] { userName, password };
HashMap<String, Object> env = new HashMap<String, Object>();
env.put(JMXConnector.CREDENTIALS, userCred);
jmxc = JMXConnectorFactory.newJMXConnector(jmxUrl, env);
jmxc.addConnectionNotificationListener(this, null, null);
jmxc.connect();
}
public Map<String, String> getAllStats(int level) {
if (tigBean != null) {
return tigBean.getAllStats(level);
}
return null;
}
public Map<String, String> getComponentStats(String compName, int level) {
if (tigBean != null) {
return tigBean.getComponentStats(compName, level);
}
return null;
}
public List<String> getComponentsNames() {
if (tigBean != null) {
return tigBean.getComponentsNames();
}
return null;
}
public String getId() {
return id;
}
public void start() {
if (updater == null) {
updater = new StatisticsUpdater();
System.out.println("Started: " + id + ":" + hostname + ":" + port);
}
}
@Override
public void handleNotification(Notification notification, Object handback) {
if (notification.getType().equals(JMXConnectionNotification.OPENED)) {
System.out.println("Connected: " + id + ":" + hostname + ":" + port);
try {
server = jmxc.getMBeanServerConnection();
ObjectName obn = new ObjectName(StatisticsCollector.STATISTICS_MBEAN_NAME);
tigBean =
MBeanServerInvocationHandler.newProxyInstance(server, obn,
StatisticsProviderMBean.class, false);
if (history == null) {
if (loadHistory) {
String[] metrics_arr = metrics.toArray(new String[metrics.size()]);
history = tigBean.getStatsHistory(metrics_arr);
System.out.println(hostname
+ " loaded history, size: "
+ (history != null && history.get(metrics_arr[0]) != null ? history.get(
metrics_arr[0]).size() : "null"));
} else {
System.out.println(hostname + " loading history switched off.");
}
if (history == null) {
history = new LinkedHashMap<String, LinkedList<Object>>();
for (String m : metrics) {
LinkedList<Object> list = new LinkedList<Object>();
history.put(m, list);
}
}
} else {
System.out.println(hostname + " history already loaded, skipping.");
}
for (JMXProxyListenerOpt jMXProxyListener : listeners) {
jMXProxyListener.connected(id, this);
}
start();
} catch (Exception e) {
e.printStackTrace();
}
return;
}
if (notification.getType().equals(JMXConnectionNotification.CLOSED)) {
server = null;
tigBean = null;
lastDisconnectTime = new Date();
for (JMXProxyListenerOpt jMXProxyListener : listeners) {
jMXProxyListener.disconnected(id);
}
return;
}
if (notification.getType().equals(JMXConnectionNotification.FAILED)) {
System.out.println("Reconnection to {hostName} failed...");
return;
}
System.out.println("Unsupported JMX notification: {notification.getType()}");
}
public boolean isConnected() {
return tigBean != null;
}
public boolean isInitialized() {
return isConnected() && initialized;
}
public void update() {
if (tigBean != null) {
// This doesn't ever change so it is enough to query it once
if (cpuNo == 0) {
cpuNo = tigBean.getCPUsNumber();
}
Map<String, Object> curMetrics =
tigBean.getCurStats(metrics.toArray(new String[metrics.size()]));
for (Map.Entry<String, Object> e : curMetrics.entrySet()) {
LinkedList<Object> list = history.get(e.getKey());
if (list != null) {
list.add(e.getValue());
if (list.size() > 1) {
list.removeFirst();
}
}
}
sysDetails = tigBean.getSystemDetails();
initialized = true;
}
}
private class StatisticsUpdater {
private Timer updateTimer = null;
private StatisticsUpdater() {
updateTimer = new Timer("stats-updater", true);
// updateTimer.scheduleAtFixedRate(new TimerTask() {
updateTimer.schedule(new TimerTask() {
@Override
public void run() {
try {
if (server == null) {
connect();
}
if (server != null) {
update();
}
} catch (IOException e) {
Throwable cause = e;
while (cause.getCause() != null) {
cause = cause.getCause();
}
String disconnected = "";
if (lastDisconnectTime != null) {
long disconnectedInterval =
(System.currentTimeMillis() - lastDisconnectTime.getTime())
/ (1000 * 60);
disconnected =
", disconnected: " + lastDisconnectTime + ", " + disconnectedInterval
+ " minutes ago.";
}
log.log(Level.WARNING, "{0}, {1}, retrying in {2} seconds{3}", new Object[] {
cause.getMessage(), hostname, interval / 1000, disconnected });
// log.log(Level.FINEST, e.getMessage(), e);
} catch (Exception e) {
log.log(Level.WARNING, "Problem retrieving statistics: ", e);
}
}
}, delay, interval);
}
}
/**
* @param string
* @return
*/
public Object[] getMetricHistory(String key) {
List<Object> result = history.get(key);
if (result != null) {
switch (DataTypes.decodeTypeIdFromName(key)) {
case 'I':
return result.toArray(new Integer[result.size()]);
case 'L':
return result.toArray(new Long[result.size()]);
case 'F':
return result.toArray(new Float[result.size()]);
case 'D':
return result.toArray(new Double[result.size()]);
default:
return result.toArray(new String[result.size()]);
}
}
return null;
}
public String getHostname() {
return hostname;
}
/**
* @param string
* @return
*/
public Object getMetricData(String key) {
LinkedList<Object> h = history.get(key);
if (h != null) {
return h.getLast();
}
return null;
}
/**
* @return
*/
public String getSystemDetails() {
return sysDetails;
}
}