Introduction
JMXTerm is a command line tool that mirrors the functionality of JConsole. There are some tricks involved in getting it working in a scripted environment. Before writing a script, you will want to bring up JMXTerm in interactive mode and work out the commands necessary to retrieve the required information.
By default, java applications are not likely to enable JMXRemote to allow access via the network, but we can connect directly to the Java process and access the JMX MBeans locally using the process ID. You can track down that PID using the netstat -tulpn command as shown if you wish. The screenshot below shows the process id which I identified by knowing the service port of 8080, as well as the fact that it is identified as java. You can also identify the process ID using JMXTerm.
InterActive JMXTerm
Launching JMXTerm is simple, the only prerequisite being.. you guessed it... Java. Launch it using the -jar option for Java. In the below example I will dissect the steps I used to grab that data from an MBean using JMXTerm. In the session below in blue, bold characters are the ones I typed.
Launch JMXTerm from the command prompt:
[ec2-user@ip-172-31-4-10 ~]$ java -jar jmxterm-1.0-alpha-4-uber.jar
After launch, you will see the JMXTerm Prompt, "$>". Type "help" for a list of commands.
$>help #following commands are available to use:
about - Display about page
bean - Display or set current selected MBean.
beans - List available beans under a domain or all domains
bye - Terminate console and exit
close - Close current JMX connection
domain - Display or set current selected domain.
domains - List all available domain names
exit - Terminate console and exit
get - Get value of MBean attribute(s)
help - Display available commands or usage of a command
info - Display detail information about an MBean
jvms - List all running local JVM processes
open - Open JMX session or display current connection
option - Set options for command session
quit - Terminate console and exit
run - Invoke an MBean operation
set - Set value of an MBean attribute
The first thing we need to do is make a connection to a JMX instance. As noted in the intro, we can pull that information before we launch JMXTerm, but we can also find it from the program using the jvms command:
$>jvms
7160 (m) - start.jar jetty.state=/data/jettyapps/chef-cms-app/jetty.state jetty-logging.xml jetty-started.xml start-log-file=/data/jettyapps/chef-cms-app/logs/start.log 15925 ( ) - jmxterm-1.0-alpha-4-uber.jar
So we see the PID as the first item on the response line, 7160, and we see this is the chef-cms-app. Next, we open that process:
$>open 7160
#Connection to 7160 is opened
Okay... we have opened a connection. We need to know what we have to work with. Use the command "beans" to get a list of available beans. (list below shortened as it was quite long)
$>beans
#domain = JMImplementation:
JMImplementation:type=MBeanServerDelegate
#domain = cms-app:
cms-app:service=AuditLogEventService,type=service
cms-app:service=AuthorService,type=service
...
cms-app:type=utility,utility=AuditLogEventController
…
#domain = com.amazonaws.management:
com.amazonaws.management:type=AwsSdkMetrics
#domain = com.sun.management:
com.sun.management:type=HotSpotDiagnostic
…
What we see from the output of beans is that there are a LOT of beans but we notice they are grouped by domains. We can see that the domain we care about in this case is cms-app. We need to select that domain:
$>domain cms-app
#domain is set to cms-app
If you type the "beans" command again, the list will only return the beans in the cms-app. The bean names are slightly unwieldy to type, but JMXTerm allows copy and paste (highlight to copy, right click to paste) so we can choose a particular bean. Note! Because we have SELECTED the cms-app domain, we do NOT need the cms-app prefix when choosing the bean! We saw above the first bean in the cms-app, service=AuditLogEventService,type=service
. Lets set that bean using the bean (bean not beans this time) command. Note the error using the full bean name at this level.
$>bean cms-app:service=AuditLogEventService,type=service
#IllegalArgumentException: Bean name cms-app:service=AuditLogEventService,type=service isn't valid
$>bean service=AuditLogEventService,type=service
#bean is set to cms-app:service=AuditLogEventService,type=service
Now we have a domain and a bean selected. We can use the info command to get a list of attributes and operations (and notifications if available) this bean provides:
$>info
#mbean = cms-app:service=AuditLogEventService,type=service
#class name = com.samsung.cms.AuditLogEventService
# attributes
%0 - Expose (java.lang.Object, w)
%1 - LastEventId (java.lang.Object, r)
%2 - MetaClass (groovy.lang.MetaClass, rw)
%3 - TransactionManager (org.springframework.transaction.PlatformTransactionManager, rw)
# operations
%0 - java.lang.Object getExpose()
%1 - java.lang.Object getLastEventId()
%2 - org.springframework.transaction.PlatformTransactionManager getTransactionManager()
%3 - void setExpose(java.lang.Object p1)
%4 - void setTransactionManager(org.springframework.transaction.PlatformTransactionManager p1)
#there's no notifications
With the list of the attributes and the operations, we can now interact with the bean. Assuming we want to see what the LastEventID
is, we would use the "get" command to grab it.
$>get LastEventId
#mbean = cms-app:service=AuditLogEventService,type=service:
LastEventId = 66251;
We can repeat the process for other beans, and we could use the SET command to change a value, if the bean allowed it.
So, we know how to grab the value from a bean manually, but what if we want to automate the process?
Scripting JMXTerm
Scripting JMXTerm may be done in several ways. You can pass arguments to the JMXTerm on the command line or via a pipe, but I didn't like having to call it repeatedly for each request. That said, one JMXTerm option "-i" permits the specification of an input command file, which would contain a list of commands to execute within JMXTerm. Sadly, this option does not work when we try to pipe the process id to JMXTerm which means a little extra effort if we want to avoid JMX Remoting. I have chosen to use a .jmx extension to identify a JMXTerm Script.
This command will work to execute the Jcom.jmx JMXTerm script, provided the Jcom.jmx contains a command to open to correct process ID.
/usr/bin/java -jar jmxterm-1.0-alpha-4-uber.jar -n -v silent -i /home/ec2-user/Jcom.jmx
The contents of the Jcom.jmx file mirror the commands entered in the JMXTerm session. The script file is missing the first line which is prepended by my script. If you wish to execute the script itself, you must begin with a line "open 7160" where 7160 is the id of the process you are accessing.
------A Jcom.jmx File
#Set the domain for Chef App
domain cms-app
#Set the first service with accessible attribute
bean service=AuditLogEventService,type=service
get LastEventId
get LastEventId
Because the Process ID of the JMX host process will change, I use a script to launch JMXTerm and create a modified JCom.jmx called Jcom.jmx.tmp which is then used as the actual input file and which contains the missing first line:
-----The modified Jcom.jmx.tmp file.
open 7160
#Set the domain for Chef App
domain cms-app
#Set the first service with accessible attribute
bean service=AuditLogEventService,type=service
get LastEventId
get LastEventId
#This script attempts to find the process ID of the JMX process, then dump bean info using Jcom.jmx file
if [ "$1" != "" ]; then
#If appname passed on commandline, bring up that process ID
AppName=$1
PID=`echo "jvms" | /usr/bin/java -jar jmxterm-1.0-alpha-4-uber.jar -n -v silent | grep $AppName | awk '{print $1;}'`
else
#Try to bring up a local JVM with the "(m)" (management flag?) set.
AppName="Unspecified JWM"
PID=`echo "jvms" | /usr/bin/java -jar jmxterm-1.0-alpha-4-uber.jar -n -v silent | grep "(m)" | awk '{print $1;}'`
fi
#Check if we have multiple results
echo -n "$PID" | while IFS= read -N 1 a; do
if [[ "$a" == $'\n' ]] ; then
echo "Multiple PIDs found for $AppName"
echo "$PID"
echo "Please specify a unique application identifier"
exit 10
fi
done
if [ $? -eq 10 ]; then exit 0; fi
if [ "$PID" == "" ]; then
#If we didnt get a result
echo "Unable to determine unique process id for $AppName"
exit
fi
echo "Using port $PID for $AppName"
#The Process ID will change, but we dont want to change our JMXTerm Script.
#Create a temporary script with the open command for the process id, and the other commands.
echo "open $PID" > /home/ec2-user/Jcom.jmx.tmp
cat /home/ec2-user/Jcom.jmx >> Jcom.jmx.tmp
#Call JMXTerm, and pass it the combined command file. Result will hold what is returned.
RESULT=`/usr/bin/java -jar jmxterm-1.0-alpha-4-uber.jar -n -v silent -i /home/ec2-user/Jcom.jmx.tmp`
echo "Results:"
echo "$RESULT"
#Remove the temp file to keep things clean.
rm /home/ec2-user/Jcom.jmx.tmp
JMXRemote
If you wish to access the JMX MBeans remotely, you will need to start your java session with the appropriate flags to enable remote access. The easiest way to do this is to disable security so ssl and authentication are not required. This solution is of limited usability in an AWS environment because JMX not only uses the specified port but also opens several other random ports. This makes configuring your security groups and NACLs unreasonably complex if you are spread out.
And example below launches a test jar I created that implements JMX on port 9999 with security disabled to permit JMXTerm remote connections.
java -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -jar /home/ec2-user/SystemConfigMBean.jar
Once you the session up and accessible on the network, you have connect with JMXTerm using hostname:port
instead of the Process ID.