Jeff Mesnil
Weblog · Dev · Photos · Books

JMX Scripts using JRuby -- Part II

May 31, 2007

update: a more Ruby-esque version using Rails idioms by Aaron Batalion

update: updated Ruby script to use instance_eval and define_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 entry, I'll explain how to remove the dependency on the MBean proxies by taking advantage of Ruby to dynamically add the MBean attributes to a Ruby object representing the MBean.

MBean proxies

Using MBean proxies (using either ManagementFactory.newPlatformMXBeanProxy() or MBeanServerInvocationHandler.newProxyInstance()) is always the simplest way to interact with MBeans. However it also means that the MBean interfaces must be available to the JMX client. This can be sometimes problematic.
For example, to use a proxy on your MBean (as opposed to the JVM MBeans that I'm using in my examples for convenience), you must add the jar containing your MBeans in JRuby classpath.
If it is not a problem, good news, go for it.
However if it makes the setup too complex, let's try to break this dependency.

MBean attributes

To make it simple, I'll focus on the MBean attributes. We will create a Ruby object which makes it simple and elegant to read the value of a MBean attribute without creating a proxy of the MBean (by simple & elegant, I mean something else than calling directly MBeanServerConnection.getAttribute(ObjectName, String)).

We will again use jconsole as our managed java application (see Part I to start jconsole with all System properties required to manage it remotely).

Going directly to the conclusion, the code to display a list of all loggers defined by java.util.logging is

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

logging = MBean.new mbsc, "java.util.logging:type=Logging"
# list all Loggers
logging.LoggerNames.each do | logger_name |
    puts logger_name
end

Running this code will display all loggers:

sun.rmi.client.call
java.awt.ContainerOrderFocusTraversalPolicy
javax.management.remote.rmi
javax.swing.SortingFocusTraversalPolicy
sun.rmi.server.call
sun.rmi.transport.tcp
...

Where is the magic? It is in

logging = MBean.new mbsc, "java.util.logging:type=Logging"

JRuby's MBean class

MBean is a Ruby class which creates objects with specific methods to access all the attributes associated to the ObjectName passed in parameter using the MBeanServerConnection to connect to a remote MBeanServer.

What does its code look like?

class MBean
    include_class 'javax.management.ObjectName'

    def initialize(mbsc, object_name)
        @mbsc = mbsc
        @object_name = ObjectName.new object_name
        info = mbsc.getMBeanInfo @object_name
        info.attributes.each do | mbean_attr |
            self.class.instance_eval do 
                define_method mbean_attr.name do
                    @mbsc.getAttribute @object_name, "#{mbean_attr.name}"
                end
            end
        end
    end
end

This class only defines a constructor initialize which accepts a MBeanServerConnection and a String representing an ObjectName which is used to create a @object_name field.

It retrieves the MBeanInfo associated to the @object_name thanks to @mbsc. It then iterates on the MBeanAttributeInfo using info.attributes.

That's in this iterator that the magic happens:

self.class.instance_eval do 
    define_method mbean_attr.name do
        @mbsc.getAttribute @object_name, "#{mbean_attr.name}"
    end
end

It calls instance_eval to add a method to the instance class.
This method is created using define_method with mbean_attr.name as the method name. It returns the value of the mbean attribute by calling @mbsc.getAttribute for the given mbean_attr.name of the @object_name of the MBean.

What does this mean? Executing logging = MBean.new mbsc, "java.util.logging:type=Logging" will create the logging object and add a method to this object to access the LoggerNames attribute:

def LoggerNames
    @mbsc.getAttribute @object_name, "LoggerNames"
end

Conclusion

Complete code is in jmx-attr.rb.

With few lines of code, it is possible to access MBean attributes in a simple way.

However, to have the same level of functionalities than using MBean proxies, we still need to be able to write attributes and invoke operations. Let's keep that for other entries.