Friday, January 06, 2012

GroovyMX: A Groovy JMX Client


I bootstrapped a new project on Github called GroovyMX (or just gmx). It is a monitoring oriented API but you may find it useful for various things. In the spirit of Groovy SQL, I am attempting to provide a JMX client API that is rich in functionality, terse in code and that extends the natural abilities of the native Java client.

This is a quick example which demonstrates how to connect to a remote MBeanServer and list the committed memory in bytes of each of the JVM's Memory Pools:


The output of this script is:

java.lang:type=MemoryPool,name=PS Eden Space:  402653184
java.lang:type=MemoryPool,name=PS Survivor Space:   16777216
java.lang:type=MemoryPool,name=Code Cache:  3407872
java.lang:type=MemoryPool,name=PS Perm Gen: 84738048
java.lang:type=MemoryPool,name=PS Old Gen:  268435456
 
Briefly,what this code is doing:
  1. This is usually the only import you will need.
  2. The Gmx class represents an MBeanServer, or more specifically, an MBeanServerConnection. There are a few ways to acquire one, depending on the situation, but in this case, I am connecting to a remote MBeanServer using its JMXServiceURL.
  3. The mbeans method has several overloads. In this case, I am providing an ObjectName pattern that will match to all the JVM's MemoryPool MXBeans, and a closure which executes for each ObjectName returned. 
  4. The closure is passed an instance of a MetaMBean which is notionally a proxy that combines the ObjectName of the MBean that it represents, and a MBeanServerConnection to the MBeanServer where the MBean is registered (the Gmx). In as much as possible, MetaMBeans act just like regular Pojos (or Pogos)  so the MBean attributes are accessed as simple properties and MBean operations are invoked like regular methods.
  5. The MetaMBean also has various local properties which are also accessed as simple properties in a Pogo. An example of this is objectName in line 4.
  6. The MemoryPool MBeans publish an attribute called Usage which is an instance of CompositeData. Fortunately, Groovy allows the simple reference of the composite sub-values by simple dot notation so the expression it.Usage.committed retrieves the nested value from the Usage composite structure that is keyed by the key committed
  7. Keen observers might wonder why Usage is cased that way. This is because the Groovy property specifier Usage is directly mapped to the MemoryPool MBean attribute Usage. Since it is perfectly legal for an MBean to have two separate attributes Foo and foo, the MetaMBean only honors the correctly specified case.
One of the tricky parts of making MetaMBean behave like a regular Pojo is that MBean operation invocations require the exact signature of the operation to be passed as an argument, which is very exacting and JMX can be quite fussy in this regard. Unlike regular Java and Groovy, there is no implicit [un/]boxing or inheritance easing done on your behalf. Consider these signature pairs:

void foo(int)                void foo(Integer)
void log(CharSequence)       void log(String)

If these were MBeanOperations, they would represent two different signatures, so the tricky part is inspecting the MetaMBean invocation name and arguments and executing a multidimensional pattern match against the MBean provided MBeanOperationInfos.

Gmx is also integrated with the Java Attach API so another way of acquiring a Gmx instance is to specify the PID of the JVM you want to attach to. The following example illustrates a script that discovers all (Attach API compatible) JVMs on the local machine and then prints the MBeanServerID attribute for each from the MBeanServerDelegate MBean.


The output of this script is:

helios104_1324051453215
helios104_1325860886519
helios104_1325876067431
helios104_1325782180150

Not super interesting, but I think the brevity is great.
This last example demonstrates some of the powerful optimizations of the Groovy remoting implemented by Gmx. To contrive an example, consider determining the total number of thread blocks across all threads in a JVM. Rather than retrieving every ThreadInfo from the ThreadMXBean and computing the total locally, I install the Gmx remoting on the remote MBeanServer and pass a script to perform the computation on the remote JVM and then return the result.

 The raw script passing is a bit awkward, and I'm working on implementing seamless remote closure invocation (See Issue #15).  and native closures are now supported. See here and here.

There are several more features which are complete, defective, in the roadmap/documentation or just in my imagination, but if you are interested, please check out the Gmx GitHub Site's Source Code, Issues and Wiki. I just started this recently so its not ready for a release, but you can download a snapshot from my Cloudbees snapshot repository.

Snapshots:
The dependencies can be viewed in the Gmx Maven Pom.

There are some additional examples in the project unit tests:
If you have any feedback, please drop a comment on this blog.

Cheers.

No comments: