Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Java

JMXTerm: Interactive and Scripted

5.00/5 (1 vote)
7 Jun 2015CPOL5 min read 32.1K  
How to use basic JMXTerm functionality, and extend its usefulness through scripting.

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
C++
// My bash launcher for JMXTerm
#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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)