Jeff Mesnil’s Weblog


JMX Scripts using JRuby

[update: fixed Ruby script by using ManagementFactory::newPlatformMXBeanProxy instead of MBeanServerInvocationHandler::newProxyInstance based on Daniel comment]

I needed to write a script to automate the management of a Java application using JMX which I could put in a crontab.

I found some explanation on how to script JMX in jconsole with BeanShell or with Groovy. It’s a good approach but:

  • I want the scripts to be executable in a cron (and not from jconsole GUI)
  • I’d prefer to write them in Ruby

It turns out that it is straightforward to write such scripts using JRuby and Java 5 or later. To keep things simple, let say I want to write a script which will be called periodically to run the garbage collector on a long-running Java application.

First let use jconsole as our long-running application. We will start jconsole with all the System properties required to manage its JVM through JMX/RMI connector:

jconsole -J-Dcom.sun.management.jmxremote \
         -J-Dcom.sun.management.jmxremote.port=3000 \
         -J-Dcom.sun.management.jmxremote.ssl=false \
         -J-Dcom.sun.management.jmxremote.authenticate=false

gc.rb

The script to run the garbage collector is simple:

module JMX
    require 'java'

    include_class 'java.util.HashMap'
    include_package 'java.lang.management'
    include_package 'javax.management'
    include_package 'javax.management.remote'

    url = JMXServiceURL.new "service:jmx:rmi:///jndi/rmi://localhost:3000/jmxrmi"
    connector = JMXConnectorFactory::connect url, HashMap.new
    mbsc = connector.mbean_server_connection

    memory_mbean = ManagementFactory::newPlatformMXBeanProxy mbsc, "java.lang:type=Memory",
        MemoryMXBean::java_class

    memory_mbean.verbose = !memory_mbean.verbose
    puts "verbose = #{memory_mbean.verbose}"

    memory_mbean.gc
end

This gc.rb script is doing several things:

  1. it require 'java' so our scripts will run only on JRuby.
  2. it includes all the required classes and packages .
  3. it connects to jconsole’s MBeanServer using the “standard” JMX URL service:jmx:rmi:///jndi/rmi://localhost:3000/jmxrmi
  4. it creates a proxy of the MemoryMXBean based on the MBeanServerConnection mbsc and the ObjectName java.lang:type=Memory
  5. for “fun”, it toggles the verbose boolean of the memory_mbean
  6. and finally, it runs the garbage collector by calling memory_mbean.gc

When we run the script:

$ jruby gc.rb
verbose = true

we see that a gc has been performed by jconsole’s JVM:

[Full GC 3754K->3172K(6076K), 0.0895140 secs]
[GC 3680K->3189K(6076K), 0.0008180 secs]
[GC 3701K->3281K(6076K), 0.0016410 secs]
[GC 3793K->3320K(6076K), 0.0014080 secs]
...

I just need to put the call to this script in my crontab and I’m done.

MBeans in JRuby classpath

Writing such scripts works fine for most cases but there is a caveat: it requires to have the MBean Java class in JRuby classpath to be able to create a proxy using either MBeanServerInvocationHandler::newProxyInstance or ManagementFactory::newPlatformMXBeanProxy (if the MBean is a MXBean as it is the case in this script).

If you have access to the jar containing the MBeans, you just need to put it $JRUBY_HOME/lib/ directory to be able to access it from Ruby scripts.

Still it should possible to map JMX API with Ruby metamodel to get rid of this requirement and develop “JMX-generic” scripts.

Next Step: Generic JMX Scripts

The next step would be to create Ruby objects representing JMX MBeans where their attributes & operations will be added dynamically to the corresponding Ruby instances by examining the MBeanInfo.
The previous example would look like:

memory_mbean = MBeanObject.new mbsc, "java.lang:type=Memory"
memory_mbean.verbose = !memory_mbean.verbose
memory_mbean.gc

The big change will be that we will no longer require to have the MBeans in JRuby classpath making it simpler to manage many Java applications (e.g. Tomcat, JBoss, etc.)
To be continued…

4 Responses to “JMX Scripts using JRuby”

  1. Daniel Fuchs Says:

    Hi Jeff,

    Nice blog entry, but I’d like to bring something to your attention:

    The MBeans used in the JVM Management & Monitoring API are not
    regular MBeans, they’re MXBeans.
    Although generic support for custom MXBeans was only added in JDK 6,
    JDK 5 had a built-in support for M&M MXBeans.

    In the general case, using MBeanServerInvocationHandler.newProxyInstance
    does not work for MXBean interfaces. Instead you should be
    using ManagementFactory.newPlatformMXBeanProxy as I have explained
    in one of my posts:
    http://blogs.sun.com/jmxetc/entry/howtoretrieveremotejvm#comment-1174563727000

    MBeanServerInvocationHandler.newProxyInstance works only for regular
    MBeans.

    In other words:

    • JDK 6 JMX.newMBeanProxy is equivalent to JDK 5
      MBeanServerInvocationHandler.newProxyInstance
    • JDK 6 JMX.newMXBeanProxy is a more general method for JDK 5
      ManagementFactory.newPlatformMXBeanProxy (newMXBeanProxy works
      for any MXBeans, newPlatformMXBeanProxy will/might only work for
      platform MXBeans).

    So you should correct your code and use
    ManagementFactory.newPlatformMXBeanProxy instead of
    MBeanServerInvocationHandler.newProxyInstance.

    Hope this helps,

    – daniel

  2. Jeff Mesnil’s Weblog » Blog Archive » JMX Scripts using JRuby — Part II Says:

    […] e_method instead of eval based on a poignant explanation of eval-less metaprogramming. In Part I, I created a JRuby script to manage a Java application using JMX. In this e […]

  3. Jeff Mesnil’s Weblog » Blog Archive » JMX Scripts using JRuby Says:

    […] :newPlatformMXBeanProxy instead of MBeanServerInvocationHandler::newProxyInstance based on Daniel comment] I needed to write a script to automate the managemen […]

  4. GSIY … Ruby-Rails Portal Says:

    […] of JMX on JRuby came up recently and I decided to play around. I found a great starter on Jeff Mesnil’s blog, but I decided I hated the syntax. Ruby has spoiled me. Ac […]