How to retrieve JVM information using JRuby

A few weeks ago, I posted a small example showing how to programatically retrieve the JVM Management and Monitoring information. Jeff Mesnil also recently wrote a two parts blog article showing how to write a JMX client using JRuby.

Piecing these two articles together I'm going to show how to prototype this using JRuby in jconsole Script Shell plugin.

JConsole Script Shell Plugin

Since Java SE 6, JConsole exposes a plugin API that will let you develop your own Plugin Tab. Java SE 6 comes with a scripting demo that contains a generic Script Shell tab, which uses JSR 223 ScriptEngine API to instantiate the ScriptEngine of your choice. Sundar already blogged about using JavaScript and Groovy with this Script Shell tab. I will also update my entry about BeanShell, showing how to use the BeanShell ScriptEngine with JConsole.

But what interest me today is to show how to expand on Jeff's article in order to show how you could prototype your ruby script to access the JVM Management and Monitoring information from within JConsole Script Shell tab.

Starting JConsole with a JRuby Script Shell tab

This is an easy step when you know how to do it and what pitfall to avoid. As it happens, the error message that you get back if things go wrong is a bit laconic, so it's better to know in advance what is suposed to work and what isn't.

First you will need to download several things (unless you already have them):

  • A version of Java SE 6 JDK, or higher,
  • The binary zip containing the various ScriptEngines offered by the java.net scripting project. Note that this zip does not contain the script language implementations, it only contains the ScriptEngine wrappers for each script.
  • The implementation for JRuby, that you can get at http://jruby.codehaus.org/

Create a scripts folder somewhere and unzip the jsr223-engines.zip you got from http://scripting.dev.java.net/ inside. You will find the JRuby script engine in a jruby subfolder: jruby/build/jruby-engine.jar.

Then expand the jruby-bin-0.9.8 archive you got from http://jruby.codehaus.org/ in that same scripts folder. You will now find the jruby implementation jars in a jruby-0.9.8/lib subfolder.

To start jconsole from the scripts folder use the following command:

# jconsole  -J-Djava.class.path=<classpath> \\
#              => classpath must contain tools.jar, jconsole.jar, 
#                 jruby-engine.jar, jruby jars...
#           -J-D<lang-property=language> \\
#              => tells the demo which script language to instantiate
#           -pluginpath <pluginpath>
#              => pluginpath must contain the demo plugin
#
$JDK_HOME/bin/jconsole -J-Djava.class.path=$JDK_HOME/lib/jconsole.jar:$JDK_HOME/lib/tools.jar:jruby/build/jruby-engine.jar:jruby-0.9.8/lib/asm-2.2.3.jar:jruby-0.9.8/lib/jruby.jar \\
     -J-Dcom.sun.demo.jconsole.console.language=jruby \\
     -pluginpath $JDK_HOME/demo/scripting/jconsole-plugin/jconsole-plugin.jar
                        

Be carefull not to include any empty element when specifying the classpath - something like e.g. jconsole.jar:tools.jar::jruby.jar:jruby-engine.jar. If you do, the ScriptEngine instantiation is likely to fail. In that case, JConsole will start, but when you try to connect to the target VM, the Script Shell tab will not be created. You will see a cryptic error message on your terminal that says:

Exception in thread "AWT-EventQueue-0" java.lang.RuntimeException: cannot load jruby engine
        at com.sun.demo.scripting.jconsole.ScriptJConsolePlugin.createScriptEngine(ScriptJConsolePlugin.java:135)
                        

When JConsole has started, connect to a target VM. After the connection is established, you should notice a Script Shell tab:

JConsole tabs - including the Script Shell tab

Click on the Script Shell tab and you'll be able to enter Ruby commands.

The Script Shell tab

The MBeanServerConnection to the target VM can be obtained from the $plugin global variable which is set by the Script Shell plugin:

rb>$plugin.context.MBeanServerConnection
javax.management.remote.rmi.RMIConnector$RemoteMBeanServerConnection@18380a4

rb>
                        

For instance, to count the MBeans in the target VM you just need to call:

rb>$plugin.context.MBeanServerConnection.MBeanCount
17

rb>
                        

Or to get the list of MBeans registered in the target VM:

rb>$plugin.context.MBeanServerConnection::queryNames nil, nil
[java.lang:type=Memory, java.lang:type=GarbageCollector,name=Copy, java.lang:type=MemoryPool,name=Code Cache, java.lang:type=Runtime, java.lang:type=ClassLoading, java.lang:type=Threading, java.util.logging:type=Logging, java.lang:type=Compilation, java.lang:type=MemoryPool,name=Eden Space, com.sun.management:type=HotSpotDiagnostic, java.lang:type=MemoryPool,name=Survivor Space, java.lang:type=GarbageCollector,name=MarkSweepCompact, java.lang:type=OperatingSystem, java.lang:type=MemoryPool,name=Tenured Gen, java.lang:type=MemoryPool,name=Perm Gen, JMImplementation:type=MBeanServerDelegate, java.lang:type=MemoryManager,name=CodeCacheManager]

rb>
                        

Assigning variables

One of the pitfalls I immediately fell into was when I tried to save the connection to the target VM in a local variable. Each line of command that you type in is executed in its own local scope, and therefore, any local variable you try to set will be accessible only within that scope, i.e. within that line. So for instance:

rb>conn = $plugin.context.MBeanServerConnection ; conn.MBeanCount
17

rb>
                        

works, but the following doesn't:

rb>conn = $plugin.context.MBeanServerConnection
javax.management.remote.rmi.RMIConnector$RemoteMBeanServerConnection@94229e

rb>conn.MBeanCount
org.jruby.exceptions.RaiseException

rb>
                        

Here again, the error message you get when jruby raises an exception is a bit laconic ("org.jruby.exceptions.RaiseException") so it took me a while to figure out what was going wrong.

The bottom line is: if you want to be able to assign a variable on one line, and use it in the next, you must use a global variable assignation:

rb>$conn = $plugin.context.MBeanServerConnection
javax.management.remote.rmi.RMIConnector$RemoteMBeanServerConnection@94229e

rb>$conn.MBeanCount
17

rb>
                        

Creating Proxies to access Platform MXBeans

So now we are ready to get back to Jeff's example:

rb>include_class 'java.lang.management.ManagementFactory'
java.lang.management.ManagementFactory

rb>include_class 'java.lang.management.MemoryMXBean'
java.lang.management.MemoryMXBean

rb>$conn = $plugin.context.MBeanServerConnection
javax.management.remote.rmi.RMIConnector$RemoteMBeanServerConnection@94229e

rb>$memory_mbean = ManagementFactory::newPlatformMXBeanProxy $conn, "java.lang:type=Memory", MemoryMXBean::java_class
MXBeanProxy(javax.management.remote.rmi.RMIConnector$RemoteMBeanServerConnection@94229e[java.lang:type=Memory])

rb>$memory_mbean.heapMemoryUsage
init = 0(0K) used = 22425448(21899K) committed = 31408128(30672K) max = 66387968(64832K)

rb>$memory_mbean.nonHeapMemoryUsage
init = 12746752(12448K) used = 28892200(28215K) committed = 29065216(28384K) max = 100663296(98304K)

rb>
                        

... and that's it! Nice, isn't it?

cheers,
-- daniel

Update: I have also come across this nice article by Paul King, this time about using JMX with Groovy. Quoting:

Given that Groovy sits directly on top of Java, Groovy can leverage the tremendous amount of work already done for JMX with Java.

Update 2: I also came across this blog entry by Ivan who explains how to manage GlassFish using JRuby.

Comments:

Post a Comment:
Comments are closed for this entry.
About

Daniel Fuchs blogs on Scene Builder, JMX, SNMP, Java, etc...

The views expressed on this blog are those of the author and do not necessarily reflect the views of Oracle.

Search

Categories
Archives
« March 2015
SunMonTueWedThuFriSat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    
       
Today