Skip to content

Commit

Permalink
Adding JMX MBean dumper (#1252)
Browse files Browse the repository at this point in the history
  • Loading branch information
StrikingThirteen authored and no2chem committed Apr 9, 2018
1 parent d927655 commit 5273578
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 0 deletions.
37 changes: 37 additions & 0 deletions cmdlets/src/main/java/org/corfudb/metrics/MBeanDumperApp.java
@@ -0,0 +1,37 @@
package org.corfudb.metrics;

import javax.management.AttributeList;
import javax.management.ObjectName;
import java.util.Map;

/**
* An app to dump the MBean object names and their corresponding attributes into disk.
* it uses JVM flags: corfu.jmx.service.url, dump.domains, dump.file to correspondingly
* determine JMX server's service URL, the domains for which to retrieve the MBeans
* and their attributes, and the file to export the results.
*
* Created by Sam Behnam on 3/30/18.
*/
public class MBeanDumperApp {

public static final String JMX_SERVICE_URL = "corfu.jmx.service.url";
public static final String DUMP_DOMAINS = "dump.domains";
public static final String DUMP_FILE = "dump.file";

public static void main(String[] args) throws Exception {
final String serviceURL = System.getProperty(JMX_SERVICE_URL);
final String domainsArg = System.getProperty(DUMP_DOMAINS);
final String dumpFile = System.getProperty(DUMP_FILE);

// null or empty domainsArgument translates to zero length domains array,
// otherwise it will be turned into an array using comma as delimiter
final String[] domains = (domainsArg == null || domainsArg.length() == 0) ?
new String[0] :
domainsArg.split(",");

final Map<ObjectName, AttributeList> mBeanAttributes =
MBeanUtils.getMBeanAttributes(serviceURL, domains);
MBeanUtils.dumpMBeanAttributes(mBeanAttributes, dumpFile);
}

}
156 changes: 156 additions & 0 deletions cmdlets/src/main/java/org/corfudb/metrics/MBeanUtils.java
@@ -0,0 +1,156 @@
package org.corfudb.metrics;

import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import javax.management.AttributeList;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* A utility class to access a JMX server. Provides methods to retrieve attributes
* as well as to export them to files.
*
* Created by Sam Behnam on 4/4/18.
*/

@Slf4j
class MBeanUtils {
private MBeanUtils() {
// prevent instantiation of this class
}

/**
* Fetches the MBean objects and their attributes from a JMX server at the provided
* service URL. It filters the results based on the provided domains. If the attributes
* for an MBean object can not be fetched it will emit a warning to the default logger
* and adds an empty entry for that object.
*
* @param serviceURL A well-formed JMX server's service URL. for example:
* "service:jmx:rmi:///jndi/rmi://SERVER-IP:PORT/jmxrmi"
*
* @param domains An array of domains for which attributes will be fetched from the
* JMX server. An empty array results in returning attributes for all
* domains.
*
* @return A map of MBean object names and their corresponding attributes.
*
* @throws IOException
* @throws MalformedObjectNameException
* @throws IntrospectionException
*/
static Map<ObjectName, AttributeList> getMBeanAttributes(@NonNull String serviceURL,
@NonNull String[] domains)
throws IOException,
MalformedObjectNameException,
IntrospectionException {

final JMXServiceURL url = new JMXServiceURL(serviceURL);
final Map<ObjectName, AttributeList> jmxDump;

try (JMXConnector connectorClient = JMXConnectorFactory.connect(url, null)) {
final MBeanServerConnection mBeanServerConnection = connectorClient.getMBeanServerConnection();
final Set<ObjectName> objectNames = getObjectNamesForDomains(mBeanServerConnection, domains);
jmxDump = new HashMap<>(objectNames.size());

for (ObjectName objectName : objectNames) {
AttributeList attributeList = new AttributeList();
try {
final MBeanInfo beanInfo = mBeanServerConnection.getMBeanInfo(objectName);
final MBeanAttributeInfo[] beanInfoAttributes = beanInfo.getAttributes();
String[] attributeNames = new String[beanInfoAttributes.length];
for (int i = 0; i < beanInfoAttributes.length; i++) {
attributeNames[i] = beanInfoAttributes[i].getName();
}
Arrays.sort(attributeNames);

attributeList = mBeanServerConnection.getAttributes(objectName, attributeNames);
} catch (InstanceNotFoundException |
ReflectionException |
IOException e) {
log.warn("Unable to fetch attributes for {}", objectName.toString());
}
jmxDump.put(objectName, attributeList);
}
}

return jmxDump;
}

/**
* Fetches the MBean object names from a JMX server. It filters the results
* by retrieving attributed for the provided domains
*
* @param mBeanServerConnection an open connection to a server.
* @param domains An array of domains for which attributes will be fetched from the
* JMX server. An empty array result in returning object names for all
* the domains.
* @return A set of MBean ObjectNames
*
* @throws IOException
* @throws MalformedObjectNameException
*/
private static Set<ObjectName> getObjectNamesForDomains(@NonNull MBeanServerConnection mBeanServerConnection,
@NonNull String[] domains)
throws IOException, MalformedObjectNameException {

// In case there are no domains, ObjectNames of all MBeans will be returned
if (domains.length == 0) {
return mBeanServerConnection.queryNames(null, null);
}

// In case domains are provided, all the ObjectNames within the provided domain will be returned
final Set<ObjectName> objectNames = new HashSet<>();
for (String domain : domains) {
final ObjectName objectNameFilter = new ObjectName(domain + ":*");
objectNames.addAll(mBeanServerConnection.queryNames(objectNameFilter, null));
}
return objectNames;
}

/**
* A convenience method for exporting a map of MBean object names and their attributes to
* a file.
*
* @param jmxDump A map of MBean object names along with their attributes.
* @param dumpFile A file name which along with operation timestamp will be
* used as the target dump file name.
*
* @throws FileNotFoundException
*/
static void dumpMBeanAttributes(@NonNull Map<ObjectName, AttributeList> jmxDump,
@NonNull String dumpFile)
throws FileNotFoundException {

final File file = new File(dumpFile + System.currentTimeMillis());

try (PrintStream printStream = new PrintStream(file)) {
for (Map.Entry<ObjectName, AttributeList> dumpEntry : jmxDump.entrySet()) {
final String mBeanAttributes = new StringBuilder()
.append(dumpEntry.getKey())
.append(":")
.append(dumpEntry.getValue())
.toString();
printStream.println(mBeanAttributes);
}
}
}
}
73 changes: 73 additions & 0 deletions scripts/metrics_dumper.sh
@@ -0,0 +1,73 @@
#!/usr/bin/env bash

if [ "$JAVA_HOME" != "" ]; then
JAVA="$JAVA_HOME/bin/java"
else
JAVA=java
fi

CORFUDBBINDIR="${CORFUDBBINDIR:-/usr/bin}"
CORFUDB_PREFIX="${CORFUDBBINDIR}/.."

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
SOURCE="$(readlink "$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"

if ls "${DIR}"/../cmdlets/target/*.jar > /dev/null 2>&1; then
CLASSPATH=("${DIR}"/../cmdlets/target/cmdlets-*-shaded.jar)
else
CLASSPATH=("${CORFUDB_PREFIX}"/share/corfu/lib/*.jar)
fi

# Windows (cygwin) support
case "`uname`" in
CYGWIN*) cygwin=true ;;
*) cygwin=false ;;
esac

if $cygwin
then
CLASSPATH=`cygpath -wp "$CLASSPATH"`
fi



usage() { echo "Usage: $0 [-d (uses default flags)] [-j <corfu.jmx.service.url>] [-m <dump.domains>] [-f <dump.file>]
default setting: (-Dcorfu.jmx.service.url=service:jmx:rmi:///jndi/rmi://localhost:6666/jmxrmi -Ddump.domains=corfu.metrics -Ddump.file=/tmp/metrics-dump)
corfu.jmx.service.url: the jmx service url. It should be in the form of (service:jmx:rmi:///jndi/rmi://ip-address:port/jmxrmi)
dump.domains: domains to be dumped. It should be in the form of comma separated string. If empty, all domains will be dumped.
dump.file: path to file for the dump." 1>&2; exit 1; }

default=""
jmxserver=""
metric=""
dump=""

while getopts ":j:m:f:d" opt; do
case $opt in
d) default="-Dcorfu.jmx.service.url=service:jmx:rmi:///jndi/rmi://localhost:6666/jmxrmi -Ddump.domains=corfu.metrics -Ddump.file=/tmp/metrics-dump"
echo "Using default flags: $default"
;;
j) jmxserver="-Dcorfu.jmx.service.url=$OPTARG"
;;
m) metric="-Ddump.domains=$OPTARG"
;;
f) dump="-Ddump.file=$OPTARG"
;;
*) usage
;;
esac
done

# default heap for dumper
DUMPER_HEAP="${DUMPER_HEAP:-256}"
export JVMFLAGS="-Xmx${DUMPER_HEAP}m $default $jmxserver $metric $dump"

echo $JVMFLAGS

RUN_AS=`basename $0`
"$JAVA" -cp "$CLASSPATH" $JVMFLAGS org.corfudb.metrics.MBeanDumperApp $*

0 comments on commit 5273578

Please sign in to comment.