/* * 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.exist.util.SystemExitCodes; import se.softhouse.jargo.Argument; import se.softhouse.jargo.ArgumentException; import se.softhouse.jargo.CommandLineParser; import se.softhouse.jargo.ParsedArguments; 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.*; import static org.exist.util.ArgumentUtil.getBool; import static se.softhouse.jargo.Arguments.*; /** */ 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 { final JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://"+address+":" + port + "/jmxrmi"); final Map<String, String[]> env = new HashMap<>(); final String[] creds = {"guest", "guest"}; env.put(JMXConnector.CREDENTIALS, creds); final JMXConnector jmxc = JMXConnectorFactory.connect(url, env); connection = jmxc.getMBeanServerConnection(); echo("Connected to MBean server."); } public void memoryStats() { try { final ObjectName name = new ObjectName("java.lang:type=Memory"); final 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 (final Exception e) { error(e); } } public void instanceStats() { try { echo("\nINSTANCE:"); final ObjectName name = new ObjectName("org.exist.management." + instance + ":type=Database"); final Long memReserved = (Long) connection.getAttribute(name, "ReservedMem"); echo(String.format("%25s: %10d k", "Reserved memory", memReserved.longValue() / 1024)); final Long memCache = (Long) connection.getAttribute(name, "CacheMem"); echo(String.format("%25s: %10d k", "Cache memory", memCache.longValue() / 1024)); final Long memCollCache = (Long) connection.getAttribute(name, "CollectionCacheMem"); echo(String.format("%25s: %10d k", "Collection cache memory", memCollCache.longValue() / 1024)); final String cols[] = { "MaxBrokers", "AvailableBrokers", "ActiveBrokers" }; echo(String.format("\n%17s %17s %17s", cols[0], cols[1], cols[2])); final AttributeList attrs = connection.getAttributes(name, cols); final Object values[] = getValues(attrs); echo(String.format("%17d %17d %17d", values[0], values[1], values[2])); final TabularData table = (TabularData) connection.getAttribute(name, "ActiveBrokersMap"); if (table.size() > 0) { echo("\nCurrently active threads:"); } for (final Iterator<?> i = table.values().iterator(); i.hasNext(); ) { final CompositeData data = (CompositeData) i.next(); echo(String.format("\t%20s: %3d", data.get("owner"), data.get("referenceCount"))); } } catch (final 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])); final 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 (final 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 (final Exception e) { error(e); } } public void lockTable() { echo("\nList of threads currently waiting for a lock:"); echo("-----------------------------------------------"); try { final TabularData table = (TabularData) connection.getAttribute(new ObjectName("org.exist.management:type=LockManager"), "WaitingThreads"); for (final Iterator<?> i = table.values().iterator(); i.hasNext(); ) { final 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")))); final String[] readers = (String[]) data.get("waitingForRead"); if (readers.length > 0) { echo(String.format("%20s: %s", "Wait for read", Arrays.toString(readers))); } final String[] writers = (String[]) data.get("waitingForWrite"); if (writers.length > 0) { echo(String.format("%20s: %s", "Wait for write", Arrays.toString(writers))); } } } catch (final MBeanException | AttributeNotFoundException | InstanceNotFoundException | ReflectionException | IOException | MalformedObjectNameException e) { error(e); } } public void sanityReport() { echo("\nSanity report"); echo("-----------------------------------------------"); try { final ObjectName name = new ObjectName("org.exist.management." + instance + ".tasks:type=SanityReport"); final String status = (String) connection.getAttribute(name, "Status"); final Date lastCheckStart = (Date) connection.getAttribute(name, "LastCheckStart"); final 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())));} final TabularData table = (TabularData) connection.getAttribute(name, "Errors"); for (final Iterator<?> i = table.values().iterator(); i.hasNext(); ) { final 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 (final MBeanException | AttributeNotFoundException | InstanceNotFoundException | ReflectionException | IOException | MalformedObjectNameException e) { error(e); } } public void jobReport() { echo("\nRunning jobs report"); echo("-----------------------------------------------"); try { final 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 (final Iterator<?> i = table.values().iterator(); i.hasNext(); ) { final 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 (final Iterator<?> i = table.values().iterator(); i.hasNext(); ) { final 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 (final MBeanException | AttributeNotFoundException | InstanceNotFoundException | ReflectionException | IOException | MalformedObjectNameException e) { error(e); } } private Object[] getValues(AttributeList attribs) { final 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 static final int DEFAULT_PORT = 1099; private static final int DEFAULT_WAIT_TIME = 0; /* general arguments */ private static final Argument<?> helpArg = helpArgument("-h", "--help"); /* connection arguments */ private static final Argument<String> addressArg = stringArgument("-a", "--address") .description("RMI address of the server") .required() .defaultValue("localhost") .build(); private static final Argument<Integer> portArg = integerArgument("-p", "--port") .description("RMI port of the server") .required() .defaultValue(DEFAULT_PORT) .build(); private static final Argument<String> instanceArg = stringArgument("-i", "--instance") .description("The ID of the database instance to connect to") .required() .defaultValue("exist") .build(); private static final Argument<Integer> waitArg = integerArgument("-w", "--wait") .description("while displaying server statistics: keep retrieving statistics, but wait the specified number of seconds between calls.") .defaultValue(DEFAULT_WAIT_TIME) .build(); /* display mode options */ private static final Argument<Boolean> cacheDisplayArg = optionArgument("-c", "--cache") .description("displays server statistics on cache and memory usage.") .defaultValue(false) .build(); private static final Argument<Boolean> locksDisplayArg = optionArgument("-l", "--locks") .description("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).") .defaultValue(false) .build(); /* display info options */ private static final Argument<Boolean> dbInfoArg = optionArgument("-d", "--db") .description("display general info about the db instance.") .defaultValue(false) .build(); private static final Argument<Boolean> memoryInfoArg = optionArgument("-m", "--memory") .description("display info on free and total memory. Can be combined with other parameters.") .defaultValue(false) .build(); private static final Argument<Boolean> sanityCheckInfoArg = optionArgument("-s", "--report") .description("retrieve sanity check report from the db") .defaultValue(false) .build(); private static final Argument<Boolean> jobsInfoArg = optionArgument("-j", "--jobs") .description("retrieve sanity check report from the db") .defaultValue(false) .build(); private enum Mode { STATS, LOCKS } @SuppressWarnings("unchecked") public static void main(final String[] args) { try { final ParsedArguments arguments = CommandLineParser .withArguments(addressArg, portArg, instanceArg, waitArg) .andArguments(cacheDisplayArg, locksDisplayArg) .andArguments(dbInfoArg, memoryInfoArg, sanityCheckInfoArg, jobsInfoArg) .andArguments(helpArg) .parse(args); process(arguments); } catch (final ArgumentException e) { System.out.println(e.getMessageAndUsage()); System.exit(SystemExitCodes.INVALID_ARGUMENT_EXIT_CODE); } } private static void process(final ParsedArguments arguments) { final String address = arguments.get(addressArg); final int port = Optional.ofNullable(arguments.get(portArg)).orElse(DEFAULT_PORT); final String dbInstance = arguments.get(instanceArg); final long waitTime = Optional.ofNullable(arguments.get(waitArg)).orElse(DEFAULT_WAIT_TIME); Mode mode = Mode.STATS; if(getBool(arguments, cacheDisplayArg)) { mode = Mode.STATS; } if(getBool(arguments, locksDisplayArg)) { mode = Mode.LOCKS; } final boolean displayInstance = getBool(arguments, dbInfoArg); final boolean displayMem = getBool(arguments, memoryInfoArg); final boolean displayReport = getBool(arguments, sanityCheckInfoArg); final boolean jobReport = getBool(arguments, jobsInfoArg); try { final JMXClient stats = new JMXClient(dbInstance); stats.connect(address,port); stats.memoryStats(); while (true) { switch (mode) { case STATS : stats.cacheStats(); break; case 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 (final InterruptedException e) { System.err.println("INTERRUPTED: " + e.getMessage()); } } } else {return;} } } catch (final IOException e) { e.printStackTrace(); } } }