JMX Scripts using JRuby -- Part II
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.