/*
* 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, either 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$
* Last modified by $Author$
* $Date$
*/
package tigase.stats;
//~--- non-JDK imports --------------------------------------------------------
import tigase.conf.ConfiguratorAbstract;
import tigase.disco.ServiceEntity;
import tigase.disco.ServiceIdentity;
import tigase.server.AbstractComponentRegistrator;
import tigase.server.Command;
import tigase.server.Iq;
import tigase.server.Packet;
import tigase.server.ServerComponent;
import tigase.sys.ShutdownHook;
import tigase.sys.TigaseRuntime;
import tigase.util.ElementUtils;
import tigase.xml.Element;
import tigase.xml.XMLUtils;
import tigase.xmpp.BareJID;
import tigase.xmpp.JID;
import tigase.xmpp.StanzaType;
//~--- JDK imports ------------------------------------------------------------
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.ObjectName;
//~--- classes ----------------------------------------------------------------
/**
* Class StatisticsCollector
*
*
* Created: Tue Nov 22 07:07:11 2005
*
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev$
*/
public class StatisticsCollector extends
AbstractComponentRegistrator<StatisticsContainer> implements ShutdownHook {
/**
*
*/
public static final String STATS_ARCHIVIZERS = "--stats-archiv";
public static final String STATS_HISTORY = "--stats-history";
/**
*
*/
public static final String STATS_ARCHIVIZERS_PROP_KEY = "stats-archiv";
public static final String STATS_HISTORY_SIZE_PROP_KEY = "stats-history-size";
public static final int STATS_HISTORY_SIZE_PROP_VAL = 8640;
public static final String STATS_UPDATE_INTERVAL_PROP_KEY = "stats-update-interval";
public static final long STATS_UPDATE_INTERVAL_PROP_VAL = 10l;
/** Field description */
public static final String STATISTICS_MBEAN_NAME =
"tigase.stats:type=StatisticsProvider";
private static final String STATS_XMLNS = "http://jabber.org/protocol/stats";
private static final Logger log = Logger.getLogger(StatisticsCollector.class.getName());
// ~--- fields ---------------------------------------------------------------
private ServiceEntity serviceEntity = null;
private StatisticsProvider sp = null;
private Map<String, StatisticsArchivizerIfc> archivizers =
new ConcurrentSkipListMap<String, StatisticsArchivizerIfc>();
private ArchivizerRunner arch_runner = new ArchivizerRunner();
// private ServiceEntity stats_modules = null;
private Level statsLevel = Level.INFO;
private Timer statsArchivTasks = new Timer("stats-archivizer-tasks", true);
private TimerTask initializationCompletedTask = null;
private int historySize = 0;
private long updateInterval = 10;
// ~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param component
*/
@Override
public void componentAdded(StatisticsContainer component) {
ServiceEntity item = serviceEntity.findNode(component.getName());
if (item == null) {
item =
new ServiceEntity(getName(), component.getName(), "Component: "
+ component.getName());
item.addFeatures(CMD_FEATURES);
item.addIdentities(new ServiceIdentity("automation", "command-node", "Component: "
+ component.getName()));
serviceEntity.addItems(item);
}
}
/**
* Method description
*
*
* @param component
*/
@Override
public void componentRemoved(StatisticsContainer component) {
}
// ~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public StatisticsList getAllStats() {
StatisticsList list = new StatisticsList(Level.ALL);
getAllStats(list);
return list;
}
/**
* Method description
*
*
* @param list
*/
public void getAllStats(StatisticsList list) {
for (StatisticsContainer comp : components.values()) {
getComponentStats(comp.getName(), list);
}
int totalQueuesWait = 0;
long totalQueuesOverflow = 0;
for (StatisticsContainer comp : components.values()) {
totalQueuesWait += list.getValue(comp.getName(), "Total queues wait", 0);
totalQueuesOverflow += list.getValue(comp.getName(), "Total queues overflow", 0L);
}
list.add("total", "Total queues wait", totalQueuesWait, Level.INFO);
list.add("total", "Total queues overflow", totalQueuesOverflow, Level.INFO);
}
/**
* Method description
*
*
* @param name
* @param list
*/
public void getComponentStats(String name, StatisticsList list) {
StatisticsContainer stats = components.get(name);
if (stats != null) {
stats.getStatistics(list);
}
}
/**
* Method description
*
*
* @return
*/
public List<String> getComponentsNames() {
return new ArrayList<String>(components.keySet());
}
/**
* Method description
*
*
* @param params
*
* @return
*/
@Override
public Map<String, Object> getDefaults(Map<String, Object> params) {
Map<String, Object> defs = super.getDefaults(params);
String statsArchivizers = (String) params.get(STATS_ARCHIVIZERS);
if ((statsArchivizers != null) && !statsArchivizers.isEmpty()) {
String[] archivs = statsArchivizers.split(",");
defs.put(STATS_ARCHIVIZERS_PROP_KEY, archivs);
}
int hSize = historySize;
long updateInt = updateInterval;
String stats_history = (String) params.get(STATS_HISTORY);
if (stats_history != null) {
String[] st_pars = stats_history.split(",");
try {
hSize = Integer.parseInt(st_pars[0]);
} catch (Exception ex) {
log.config("Invalid statistics history size settings: " + st_pars[0]);
}
if (st_pars.length > 1) {
try {
updateInt = Long.parseLong(st_pars[1]);
} catch (Exception ex) {
log.config("Invalid statistics update interval: " + st_pars[1]);
}
}
}
defs.put(STATS_HISTORY_SIZE_PROP_KEY, hSize);
defs.put(STATS_UPDATE_INTERVAL_PROP_KEY, updateInt);
return defs;
}
/**
* Method description
*
*
* @param from
*
* @return
*/
@Override
public List<Element> getDiscoFeatures(JID from) {
return null;
}
/**
* Method description
*
*
* @param node
* @param jid
* @param from
*
* @return
*/
@Override
public Element getDiscoInfo(String node, JID jid, JID from) {
if ((jid != null) && getName().equals(jid.getLocalpart()) && isAdmin(from)) {
return serviceEntity.getDiscoInfo(node);
}
return null;
}
/**
* Method description
*
*
* @param node
* @param jid
* @param from
*
* @return
*/
@Override
public List<Element> getDiscoItems(String node, JID jid, JID from) {
if (isAdmin(from)) {
if (getName().equals(jid.getLocalpart()) || getComponentId().equals(jid)) {
List<Element> items = serviceEntity.getDiscoItems(node, jid.toString());
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Processing discoItems for node: {0}, result: {1}",
new Object[] { node, (items == null) ? null : items.toString() });
}
return items;
} else {
if (node == null) {
Element item =
serviceEntity.getDiscoItem(null,
BareJID.toString(getName(), jid.toString()));
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Processing discoItems, result: {0}",
((item == null) ? null : item.toString()));
}
return Arrays.asList(item);
} else {
return null;
}
}
}
return null;
}
/**
* Method description
*
*
* @return
*/
@Override
public String getName() {
return super.getName();
}
/**
* Method description
*
*
* @param component
*
* @return
*/
@Override
public boolean isCorrectType(ServerComponent component) {
return component instanceof StatisticsContainer;
}
// ~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param packet
* @param results
*/
@Override
public void processPacket(final Packet packet, final Queue<Packet> results) {
if (!packet.isCommand() || (packet.getType() == StanzaType.result)) {
return;
}
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "{0} command received: {1}", new Object[] {
packet.getCommand().name(), packet });
}
Iq iqc = (Iq) packet;
BareJID stanzaFromBare = iqc.getStanzaFrom().getBareJID();
JID stanzaFrom = JID.jidInstance( stanzaFromBare );
if ( !isAdmin( stanzaFrom ) ){
Packet result = iqc.commandResult( Command.DataType.result );
Command.addTextField( result, "Error", "You do not have enough permissions to manage this domain" );
results.offer( result );
return;
}
switch (iqc.getCommand()) {
case GETSTATS: {
// Element statistics = new Element("statistics");
Element iq =
ElementUtils.createIqQuery(iqc.getStanzaTo(), iqc.getStanzaFrom(),
StanzaType.result, iqc.getStanzaId(), STATS_XMLNS);
Element query = iq.getChild("query");
StatisticsList stats = getAllStats();
if (stats != null) {
for (StatRecord record : stats) {
Element item = new Element("stat");
item.addAttribute("name",
record.getComponent() + "/" + record.getDescription());
item.addAttribute("units", record.getUnit());
item.addAttribute("value", record.getValue());
query.addChild(item);
} // end of for ()
} // end of if (stats != null && stats.count() > 0)
Packet result = Packet.packetInstance(iq, iqc.getStanzaTo(), iqc.getStanzaFrom());
// Command.setData(result, statistics);
results.offer(result);
break;
}
case OTHER: {
if (iqc.getStrCommand() == null) {
return;
}
String nick = iqc.getTo().getLocalpart();
if (!getName().equals(nick)) {
return;
}
Command.Action action = Command.getAction(iqc);
if (action == Command.Action.cancel) {
Packet result = iqc.commandResult(null);
results.offer(result);
return;
}
String tmp_val = Command.getFieldValue(iqc, "Stats level");
if (tmp_val != null) {
statsLevel = Level.parse(tmp_val);
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "statsLevel parsed to: {0}", statsLevel.getName());
}
}
StatisticsList list = new StatisticsList(statsLevel);
if (iqc.getStrCommand().equals("stats")) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Getting all stats for level: {0}",
statsLevel.getName());
}
getAllStats(list);
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "All stats for level loaded: {0}", statsLevel.getName());
}
} else {
String[] spl = iqc.getStrCommand().split("/");
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Getting stats for component: {0}, level: {1}",
new Object[] { spl[1], statsLevel.getName() });
}
getComponentStats(spl[1], list);
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Stats loaded for component: {0}, level: {1}",
new Object[] { spl[1], statsLevel.getName() });
}
}
Packet result = iqc.commandResult(Command.DataType.form);
if (list != null) {
for (StatRecord rec : list) {
if (rec.getType() == StatisticType.LIST) {
Command.addFieldMultiValue(result,
XMLUtils.escape(rec.getComponent() + "/" + rec.getDescription()),
rec.getListValue());
} else {
Command.addFieldValue(result,
XMLUtils.escape(rec.getComponent() + "/" + rec.getDescription()),
XMLUtils.escape(rec.getValue()));
}
}
}
Command.addFieldValue(
result,
"Stats level",
statsLevel.getName(),
"Stats level",
new String[] { Level.INFO.getName(), Level.FINE.getName(),
Level.FINER.getName(), Level.FINEST.getName() }, new String[] {
Level.INFO.getName(), Level.FINE.getName(), Level.FINER.getName(),
Level.FINEST.getName() });
results.offer(result);
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Returning stats result: {0}", result);
}
break;
}
default:
break;
} // end of switch (packet.getCommand())
}
// ~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param name
*/
@Override
public void setName(String name) {
super.setName(name);
serviceEntity = new ServiceEntity(name, "stats", "Server statistics");
serviceEntity.addIdentities(new ServiceIdentity("component", "stats",
"Server statistics"), new ServiceIdentity("automation", "command-node",
"All statistics"), new ServiceIdentity("automation", "command-list",
"Statistics retrieving commands"));
serviceEntity.addFeatures(DEF_FEATURES);
serviceEntity.addFeatures(CMD_FEATURES);
}
/**
* Method description
*
*
* @param props
*/
@Override
public void setProperties(Map<String, Object> props) {
super.setProperties(props);
String[] archivs = (String[]) props.get(STATS_ARCHIVIZERS_PROP_KEY);
if (archivs != null) {
initStatsArchivizers(archivs, props);
}
if (props.get(STATS_HISTORY_SIZE_PROP_KEY) != null) {
historySize = (Integer) props.get(STATS_HISTORY_SIZE_PROP_KEY);
}
if (props.get(STATS_UPDATE_INTERVAL_PROP_KEY) != null) {
updateInterval = (Long) props.get(STATS_UPDATE_INTERVAL_PROP_KEY);
}
}
// ~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public String shutdown() {
StatisticsList allStats = getAllStats();
StringBuilder sb = new StringBuilder(4096);
for (StatRecord statRecord : allStats) {
sb.append(statRecord.toString()).append('\n');
}
return sb.toString();
}
protected void statsUpdated() {
synchronized (arch_runner) {
arch_runner.notifyAll();
}
}
// ~--- get methods ----------------------------------------------------------
private Map<String, Object> getArchivizerConf(String name, Map<String, Object> props) {
Map<String, Object> result = new LinkedHashMap<String, Object>(4);
String key_start = STATS_ARCHIVIZERS_PROP_KEY + "/" + name + "/";
for (Map.Entry<String, Object> entry : props.entrySet()) {
if (entry.getKey().startsWith(key_start)) {
String key = entry.getKey().substring(key_start.length());
log.log(Level.CONFIG, "Found {0} property: {1} = {2}", new Object[] { name, key,
entry.getValue() });
result.put(key, entry.getValue());
}
}
return result;
}
@Override
public void initializationCompleted() {
super.initializationCompleted();
try {
sp = new StatisticsProvider(this, historySize, updateInterval);
String objName = STATISTICS_MBEAN_NAME;
ObjectName on = new ObjectName(objName);
ManagementFactory.getPlatformMBeanServer().registerMBean(sp, on);
ConfiguratorAbstract.putMXBean(objName, sp);
} catch (Exception ex) {
log.log(Level.SEVERE, "Can not install Statistics MXBean: ", ex);
}
TigaseRuntime.getTigaseRuntime().addShutdownHook(this);
if (initializationCompletedTask != null) {
initializationCompletedTask.run();
}
}
private void initStatsArchivizers(final String[] archivs,
final Map<String, Object> props) {
for (String stat_arch_key : archivizers.keySet()) {
StatisticsArchivizerIfc stat_arch = archivizers.remove(stat_arch_key);
if (stat_arch != null) {
stat_arch.release();
}
}
initializationCompletedTask = new TimerTask() {
public void run() {
for (String arch_prop : archivs) {
try {
String[] arch_prop_a = arch_prop.split(":");
String arch_class = arch_prop_a[0];
String arch_name = arch_prop_a[1];
final StatisticsArchivizerIfc stat_arch =
(StatisticsArchivizerIfc) Class.forName(arch_class).newInstance();
stat_arch.init(getArchivizerConf(arch_name, props));
long freq = -1;
if (arch_prop_a.length > 2) {
try {
freq = Long.parseLong(arch_prop_a[2]);
} catch (Exception e) {
freq = -1;
}
}
// Some archivizers run in regular intervals of time
// some others run each time statistics collection has completed.
if (freq > 0) {
statsArchivTasks.schedule(new TimerTask() {
@Override
public void run() {
stat_arch.execute(sp);
}
}, freq * 1000, freq * 1000);
} else {
archivizers.put(arch_name, stat_arch);
}
log.log(Level.CONFIG, "Loaded statistics archivizer: {0} for class: {1}",
new Object[] { arch_name, arch_class });
} catch (Exception e) {
log.log(Level.SEVERE, "Can't initialize statistics archivizer: " + arch_prop,
e);
}
}
}
};
}
// ~--- inner classes --------------------------------------------------------
private class ArchivizerRunner extends Thread {
private boolean stopped = false;
// ~--- constructors -------------------------------------------------------
private ArchivizerRunner() {
super("stats-archivizer");
setDaemon(true);
start();
}
// ~--- methods ------------------------------------------------------------
/**
* Method description
*
*/
@Override
public void run() {
while (!stopped) {
try {
synchronized (this) {
this.wait();
}
for (Map.Entry<String, StatisticsArchivizerIfc> archiv_entry : archivizers
.entrySet()) {
archiv_entry.getValue().execute(sp);
}
} catch (InterruptedException ex) {
// Ignore...
}
}
}
}
}
// ~ Formatted in Sun Code Convention
// ~ Formatted by Jindent --- http://www.jindent.com