/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-07 The eXist Project
* http://exist-db.org
*
* This program 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
* of the License, or (at your option) any later version.
*
* 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 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* $Id$
*/
package org.exist.management.client;
import org.apache.avalon.excalibur.cli.CLArgsParser;
import org.apache.avalon.excalibur.cli.CLOption;
import org.apache.avalon.excalibur.cli.CLOptionDescriptor;
import org.apache.avalon.excalibur.cli.CLUtil;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.TabularData;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
*/
public class JMXClient {
private MBeanServerConnection connection;
private String instance;
public JMXClient(String instanceName) {
this.instance = instanceName;
}
public void connect(String address,int port) throws IOException {
JMXServiceURL url =
new JMXServiceURL("service:jmx:rmi:///jndi/rmi://"+address+":" + port + "/jmxrmi");
Map<String, String[]> env = new HashMap<String, String[]>();
String[] creds = {"guest", "guest"};
env.put(JMXConnector.CREDENTIALS, creds);
JMXConnector jmxc = JMXConnectorFactory.connect(url, env);
connection = jmxc.getMBeanServerConnection();
echo("Connected to MBean server.");
}
public void memoryStats() {
try {
ObjectName name = new ObjectName("java.lang:type=Memory");
CompositeData composite = (CompositeData) connection.getAttribute(name, "HeapMemoryUsage");
if (composite != null) {
echo("\nMEMORY:");
echo(String.format("Current heap: %,12d k Committed memory: %,12d k",
((Long)composite.get("used")) / 1024, ((Long)composite.get("committed")) / 1024));
echo(String.format("Max memory: %,12d k", ((Long)composite.get("max")) / 1024));
}
} catch (Exception e) {
error(e);
}
}
public void instanceStats() {
try {
echo("\nINSTANCE:");
ObjectName name = new ObjectName("org.exist.management." + instance + ":type=Database");
Long memReserved = (Long) connection.getAttribute(name, "ReservedMem");
echo(String.format("%25s: %10d k", "Reserved memory", memReserved.longValue() / 1024));
Long memCache = (Long) connection.getAttribute(name, "CacheMem");
echo(String.format("%25s: %10d k", "Cache memory", memCache.longValue() / 1024));
Long memCollCache = (Long) connection.getAttribute(name, "CollectionCacheMem");
echo(String.format("%25s: %10d k", "Collection cache memory", memCollCache.longValue() / 1024));
String cols[] = { "MaxBrokers", "AvailableBrokers", "ActiveBrokers" };
echo(String.format("\n%17s %17s %17s", cols[0], cols[1], cols[2]));
AttributeList attrs = connection.getAttributes(name, cols);
Object values[] = getValues(attrs);
echo(String.format("%17d %17d %17d", values[0], values[1], values[2]));
TabularData table = (TabularData) connection.getAttribute(name, "ActiveBrokersMap");
if (table.size() > 0)
echo("\nCurrently active threads:");
for (Iterator<?> i = table.values().iterator(); i.hasNext(); ) {
CompositeData data = (CompositeData) i.next();
echo(String.format("\t%20s: %3d", data.get("owner"), data.get("referenceCount")));
}
} catch (Exception e) {
error(e);
}
}
public void cacheStats() {
try {
ObjectName name = new ObjectName("org.exist.management." + instance + ":type=CacheManager");
String cols[] = { "MaxTotal", "CurrentSize" };
AttributeList attrs = connection.getAttributes(name, cols);
Object values[] = getValues(attrs);
echo(String.format("\nCACHE [%8d pages max. / %8d pages allocated]", values[0], values[1]));
Set<ObjectName> beans = connection.queryNames(new ObjectName("org.exist.management." + instance + ":type=CacheManager.Cache,*"), null);
cols = new String[] {"Type", "FileName", "Size", "Used", "Hits", "Fails"};
echo(String.format("%10s %20s %10s %10s %10s %10s", cols[0], cols[1], cols[2], cols[3], cols[4], cols[5]));
for (Iterator<ObjectName> i = beans.iterator(); i.hasNext();) {
name = i.next();
attrs = connection.getAttributes(name, cols);
values = getValues(attrs);
echo(String.format("%10s %20s %,10d %,10d %,10d %,10d", values[0], values[1], values[2], values[3], values[4], values[5]));
}
echo("");
name = new ObjectName("org.exist.management." + instance + ":type=CollectionCacheManager");
cols = new String[] { "MaxTotal", "CurrentSize" };
attrs = connection.getAttributes(name, cols);
values = getValues(attrs);
echo(String.format("Collection Cache: %10d k max / %10d k allocated",
((Long)values[0] / 1024), ((Long)values[1] / 1024)));
} catch (Exception e) {
error(e);
}
}
public void lockTable() {
echo("\nList of threads currently waiting for a lock:");
echo("-----------------------------------------------");
try {
TabularData table = (TabularData) connection.getAttribute(new ObjectName("org.exist.management:type=LockManager"), "WaitingThreads");
for (Iterator<?> i = table.values().iterator(); i.hasNext(); ) {
CompositeData data = (CompositeData) i.next();
echo("Thread " + data.get("waitingThread"));
echo(String.format("%20s: %s", "Lock type", data.get("lockType")));
echo(String.format("%20s: %s", "Lock mode", data.get("lockMode")));
echo(String.format("%20s: %s", "Lock id", data.get("id")));
echo(String.format("%20s: %s", "Held by", Arrays.toString((String[]) data.get("owner"))));
String[] readers = (String[]) data.get("waitingForRead");
if (readers.length > 0) {
echo(String.format("%20s: %s", "Wait for read", Arrays.toString(readers)));
}
String[] writers = (String[]) data.get("waitingForWrite");
if (writers.length > 0) {
echo(String.format("%20s: %s", "Wait for write", Arrays.toString(writers)));
}
}
} catch (MBeanException e) {
error(e);
} catch (AttributeNotFoundException e) {
error(e);
} catch (InstanceNotFoundException e) {
error(e);
} catch (ReflectionException e) {
error(e);
} catch (IOException e) {
error(e);
} catch (MalformedObjectNameException e) {
error(e);
}
}
public void sanityReport() {
echo("\nSanity report");
echo("-----------------------------------------------");
try {
ObjectName name = new ObjectName("org.exist.management." + instance + ".tasks:type=SanityReport");
String status = (String) connection.getAttribute(name, "Status");
Date lastCheckStart = (Date) connection.getAttribute(name, "LastCheckStart");
Date lastCheckEnd = (Date) connection.getAttribute(name, "LastCheckEnd");
echo(String.format("%22s: %s", "Status", status));
echo(String.format("%22s: %s", "Last check start", lastCheckStart));
echo(String.format("%22s: %s", "Last check end", lastCheckEnd));
if (lastCheckStart != null && lastCheckEnd != null)
echo(String.format("%22s: %dms", "Check took", (lastCheckEnd.getTime() - lastCheckStart.getTime())));
TabularData table = (TabularData)
connection.getAttribute(name, "Errors");
for (Iterator<?> i = table.values().iterator(); i.hasNext(); ) {
CompositeData data = (CompositeData) i.next();
echo(String.format("%22s: %s", "Error code", data.get("errcode")));
echo(String.format("%22s: %s", "Description", data.get("description")));
}
} catch (MBeanException e) {
error(e);
} catch (AttributeNotFoundException e) {
error(e);
} catch (InstanceNotFoundException e) {
error(e);
} catch (ReflectionException e) {
error(e);
} catch (IOException e) {
error(e);
} catch (MalformedObjectNameException e) {
error(e);
}
}
public void jobReport() {
echo("\nRunning jobs report");
echo("-----------------------------------------------");
try {
ObjectName name = new ObjectName("org.exist.management." + instance + ":type=ProcessReport");
TabularData table = (TabularData)
connection.getAttribute(name, "RunningJobs");
String[] cols = new String[] { "ID", "Action", "Info" };
echo(String.format("%15s %30s %30s", cols[0], cols[1], cols[2]));
for (Iterator<?> i = table.values().iterator(); i.hasNext(); ) {
CompositeData data = (CompositeData) i.next();
echo(String.format("%15s %30s %30s", data.get("id"), data.get("action"), data.get("info")));
}
echo("\nRunning queries");
echo("-----------------------------------------------");
table = (TabularData)
connection.getAttribute(name, "RunningQueries");
cols = new String[] { "ID", "Type", "Key", "Terminating" };
echo(String.format("%10s %10s %30s %s", cols[0], cols[1], cols[2], cols[3]));
for (Iterator<?> i = table.values().iterator(); i.hasNext(); ) {
CompositeData data = (CompositeData) i.next();
echo(String.format("%15s %15s %30s %6s", data.get("id"), data.get("sourceType"), data.get("sourceKey"), data.get("terminating")));
}
} catch (MBeanException e) {
error(e);
} catch (AttributeNotFoundException e) {
error(e);
} catch (InstanceNotFoundException e) {
error(e);
} catch (ReflectionException e) {
error(e);
} catch (IOException e) {
error(e);
} catch (MalformedObjectNameException e) {
error(e);
}
}
private Object[] getValues(AttributeList attribs) {
Object[] v = new Object[attribs.size()];
for (int i = 0; i < attribs.size(); i++) {
v[i] = ((Attribute)attribs.get(i)).getValue();
}
return v;
}
private void echo(String msg) {
System.out.println(msg);
}
private void error(Exception e) {
System.err.println("ERROR: " + e.getMessage());
e.printStackTrace();
}
private final static int HELP_OPT = 'h';
private final static int CACHE_OPT = 'c';
private final static int DB_OPT = 'd';
private final static int WAIT_OPT = 'w';
private final static int LOCK_OPT = 'l';
private final static int MEMORY_OPT = 'm';
private final static int PORT_OPT = 'p';
private final static int INSTANCE_OPT = 'i';
private final static int ADDRESS_OPT = 'a';
private final static int SANITY_OPT = 's';
private final static int JOBS_OPT = 'j';
private final static CLOptionDescriptor OPTIONS[] = new CLOptionDescriptor[] {
new CLOptionDescriptor( "help", CLOptionDescriptor.ARGUMENT_DISALLOWED,
HELP_OPT, "print help on command line options and exit." ),
new CLOptionDescriptor( "cache", CLOptionDescriptor.ARGUMENT_DISALLOWED,
CACHE_OPT, "displays server statistics on cache and memory usage." ),
new CLOptionDescriptor( "db", CLOptionDescriptor.ARGUMENT_DISALLOWED,
DB_OPT, "display general info about the db instance." ),
new CLOptionDescriptor( "wait", CLOptionDescriptor.ARGUMENT_REQUIRED,
WAIT_OPT, "while displaying server statistics: keep retrieving statistics, but wait the " +
"specified number of seconds between calls." ),
new CLOptionDescriptor( "locks", CLOptionDescriptor.ARGUMENT_DISALLOWED,
LOCK_OPT, "lock manager: display locking information on all threads currently waiting for a lock on a resource " +
"or collection. Useful to debug deadlocks. During normal operation, the list will usually be empty (means: no " +
"blocked threads)." ),
new CLOptionDescriptor( "memory", CLOptionDescriptor.ARGUMENT_DISALLOWED,
MEMORY_OPT, "display info on free and total memory. Can be combined with other parameters." ),
new CLOptionDescriptor( "port", CLOptionDescriptor.ARGUMENT_REQUIRED,
PORT_OPT, "RMI port of the server"),
new CLOptionDescriptor( "address", CLOptionDescriptor.ARGUMENT_REQUIRED,
ADDRESS_OPT, "RMI address of the server"),
new CLOptionDescriptor( "instance", CLOptionDescriptor.ARGUMENT_REQUIRED,
INSTANCE_OPT, "the ID of the database instance to connect to"),
new CLOptionDescriptor( "report", CLOptionDescriptor.ARGUMENT_DISALLOWED,
SANITY_OPT, "retrieve sanity check report from the db"),
new CLOptionDescriptor( "jobs", CLOptionDescriptor.ARGUMENT_DISALLOWED,
JOBS_OPT, "list currently running jobs")
};
private final static int MODE_STATS = 0;
private final static int MODE_LOCKS = 1;
@SuppressWarnings("unchecked")
public static void main(String[] args) {
CLArgsParser optParser = new CLArgsParser( args, OPTIONS );
if(optParser.getErrorString() != null) {
System.err.println( "ERROR: " + optParser.getErrorString());
return;
}
String dbInstance = "exist";
long waitTime = 0;
List<CLOption> opts = optParser.getArguments();
int mode = -1;
int port = 1099;
String address = "localhost";
boolean displayMem = false;
boolean displayInstance = false;
boolean displayReport = false;
boolean jobReport = false;
for(CLOption option : opts) {
switch(option.getId()) {
case HELP_OPT :
System.out.println(CLUtil.describeOptions(OPTIONS).toString());
return;
case WAIT_OPT :
try {
waitTime = Integer.parseInt( option.getArgument() ) * 1000;
} catch( NumberFormatException e ) {
System.err.println("option -w|--wait requires a numeric argument");
return;
}
break;
case CACHE_OPT:
mode = MODE_STATS;
break;
case LOCK_OPT :
mode = MODE_LOCKS;
break;
case PORT_OPT :
try {
port = Integer.parseInt(option.getArgument());
} catch (NumberFormatException e) {
System.err.println("option -p|--port requires a numeric argument");
return;
}
break;
case ADDRESS_OPT :
try {
address = option.getArgument();
} catch (NumberFormatException e) {
System.err.println("option -a|--address requires a numeric argument");
return;
}
break;
case MEMORY_OPT :
displayMem = true;
break;
case DB_OPT :
displayInstance = true;
break;
case INSTANCE_OPT :
dbInstance = option.getArgument();
break;
case SANITY_OPT :
displayReport = true;
break;
case JOBS_OPT :
jobReport = true;
}
}
try {
JMXClient stats = new JMXClient(dbInstance);
stats.connect(address,port);
stats.memoryStats();
while (true) {
switch (mode) {
case MODE_STATS :
stats.cacheStats();
break;
case MODE_LOCKS :
stats.lockTable();
break;
}
if (displayInstance) stats.instanceStats();
if (displayMem) stats.memoryStats();
if (displayReport) stats.sanityReport();
if (jobReport) stats.jobReport();
if (waitTime > 0) {
synchronized (stats) {
try {
stats.wait(waitTime);
} catch (InterruptedException e) {
System.err.println("INTERRUPTED: " + e.getMessage());
}
}
} else
return;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}