Thursday, May 26, 2011

Jolokia, Java, JMX, and Groovy

The Bhut Jolokia is not just a chili pepper; it was deemed for several years as the hottest chili pepper in the world. In this blog post, I talk about something presumably named after this chili pepper (the logo and subtitle "JMX on Capsaicin" are strong hints): the "remote JMX with JSON over HTTP" project called Jolokia.


The main Jolokia page describes this project:
Jolokia is remote JMX with JSON over HTTP. It is fast, simple, polyglot and has unique features. It's JMX on Capsaicin. Jolokia is a JMX-HTTP bridge giving an alternative to JSR-160 connectors. It is an agent based approach with support for many platforms. In addition to basic JMX operations it enhances JMX remoting with unique features like bulk requests or fine grained security policies.

Java Management Extensions can be extremely useful for managing and monitoring Java-based applications. For the most part, JMX remote clients are typically written in Java or a JVM-based language like Groovy or JRuby because JMX is most friendly to JVM-based clients.

Although there are non-JVM approaches to JMX web clients such as the experimental JMX Web Services Connector, these are the exception rather than the rule. Perhaps the most significant advantage of Jolokia is that it is based on HTTP (rather than traditional RMI used for JMX remote access) and its format is language-independent JSON (which I recently discussed in Groovy 1.8 Introduces Groovy to JSON). Having HTTP as the transport mechanism and JSON as the data format allows Jolokia-based clients to be written in nearly any conceivable language. At the time of this writing, out-of-the-box Jolkia agents are WAR (Java EE web application) Agent, OSGi Agent, JVM JDK6 Agent, and the Mule Agent. I focus on the JVM JDK6 Agent in this post.

The next code listing contains a simple nonsensical class that is intended solely to provide a long-running Java application whose automatically-provided MXBeans can be accessed via Jolokia.

Main.java
package dustin.examples;

import static java.lang.System.out;

/**
 * Main class for demonstrating Jolokia JVM Agent. This class doesn't really do
 * anything special except "live" in the JVM so that its built-in JMX MXBeans
 * can be accessed by JMX clients.
 */
public class Main
{
   /**
    * Main executable function.
    *
    * @param arguments Command-line arguments: one numeric argument expected.
    */
   public static void main(final String[] arguments)
   {
      final long numberLoops = arguments.length > 0 ? Long.parseLong(arguments[0]) : 1000000L;
      for (long count = 0; count < numberLoops; ++count)
      {
         if (count % 100 == 0)
         {
            try
            {
               Thread.sleep(10000);
            }
            catch (InterruptedException interruptedEx)
            {
               out.append("Thread's beauty sleep was interrupted.");
            }
         }
      }
   }
}

The above class, once compiled, can be run with the Java launcher. However, to employ Jolokia's JVM JDK6 Agent, the JVM Tool Interface (JVMTI) must be used. The JVMTI documentation warns that "JVM TI may not be available in all implementations of the JavaTM virtual machine." The Jolokia documentation is more specific in saying that Oracle's HotSpot JVM is the one for which their particular Agent is supported (because it leverages the HttpServer embedded in HotSpot).

Assuming that the Main.java class shown above is compiled and then packaged into a JAR file called JolokiaDemo.jar, the Java launcher can be run against this JAR file and associate the Jolokia JVM JDK6 Agent with the following command:

java -javaagent:C:\jolokia-0.90\jolokia-jvm-jdk6-0.90-agent.jar=port=8778,host=localhost -cp dist\JolokiaDemo.jar dustin.examples.Main 1000000000

The command just shown specifies the Jolokia agent JAR (jolokia-jvm-jdk6-0.90-agent.jar) and specifies two properties with values (host and port). Note that the specified host and port happen to be the defaults, but I included them here explicitly to indicate how they are supplied to the agent. The portion "-javaagent:C:\jolokia-0.90\jolokia-jvm-jdk6-0.90-agent.jar=port=8778,host=localhost" is the JVMTI portion and everything else is "traditional" Java launcher syntax.

Running the java launcher as indicated above leads to output like that shown in the next screen snapshot.


I like that Jolokia reports the URL by which its HTTP-exposed JMX information is available. This makes it easier to ensure that the client connects properly. In "normal" mode, this is about all that is seen as the client interacts with the application. However, I find it useful to enable Jolokia's debugging when getting the client to connect to this exposed application and this is easily done by adding debug=true onto the end of the JVMTI agent specification. An example of this is shown next.

java -javaagent:C:\jolokia-0.90\jolokia-jvm-jdk6-0.90-agent.jar=port=8778,host=localhost,debug=true -cp dist\JolokiaDemo.jar dustin.examples.Main 1000000000 

Running with debug=true leads to output like the following:


I haven't shown the client yet, but this debug output gives pretty good hints as to what the client is asking for. I found this invaluable as I experimented with the Jolokia requests. Speaking of the client, the next code listing contains a Groovy script for accessing some built-in MXBeans information about this simple thread sleeping class.

runJolokiaJmxClient.groovy
#!/usr/bin/env groovy
// runJolokiaJmxClient.groovy

import org.jolokia.client.J4pClient
import org.jolokia.client.exception.J4pConnectException
import org.jolokia.client.exception.J4pRemoteException
import org.jolokia.client.request.J4pReadRequest
import org.jolokia.client.request.J4pReadResponse

def localHost = InetAddress.getLoopbackAddress()
def urlStr = "http://${localHost.hostAddress}:8778/jolokia/"
def j4pClient = new J4pClient(urlStr)
println "Attempting to connect to ${urlStr}..."
def heapMemoryRequest = new J4pReadRequest("java.lang:type=Memory",
                                           "HeapMemoryUsage")
def threadingRequest = new J4pReadRequest("java.lang:type=Threading",
                                          "ThreadCount")
def operatingSystemRequest = new J4pReadRequest("java.lang:type=OperatingSystem",
                                                "Arch")

J4pReadResponse heapMemoryResponse = null
J4pReadResponse threadingResponse = null
J4pReadResponse osResponse = null
try
{
   heapMemoryResponse = j4pClient.execute(heapMemoryRequest)
   threadingResponse = j4pClient.execute(threadingRequest)
   osResponse = j4pClient.execute(operatingSystemRequest)
}
catch (J4pConnectException connEx)
{
   println "ERROR: Cannot connect to ${urlStr}\n${connEx.message}"
}
catch (J4pRemoteException remoteEx)
{
   println "ERROR encountered while trying to access Jolokia-exposed JVM\n${remoteEx.message}"
   println "Status: ${remoteEx.status}"
   println "Error Code: ${remoteEx.errorType}"
   println "Remote Stack Trace: ${remoteEx.remoteStackTrace}"
}
println "Heap Memory: ${heapMemoryResponse?.value}"
println "Thread Count: ${threadingResponse?.value}"
println "Operating System Architecture: ${osResponse?.value}"

The simple script above uses basic Jolokia classes and exceptions to communicate with the Jolokia instrumented Java application. The output from running the above script is shown in the next screen snapshot.


The output proves that the client was able to talk to the simple Java application and acquire information about its memory usage, its thread count, and its operating system architecture. For this exercise, I threw all the required dependencies on the classpath explicitly provided to the Groovy launcher, but there are other ways to handle classpath dependencies in Groovy.

Accessing JMX information via Groovy clients is pretty straightforward thanks to Groovy being a JVM-based language and thanks to its JMX syntactic goodness. In the case of Groovy clients, I probably would not be likely to employ Jolokia. However, the fact that the remote access is via HTTP and that the data is passed back in JSON format means there are numerous possibilities for non-JVM-based clients to access JVM information. Tomasz Nurkiewicz's post Jolokia + Highcharts = JMX for human beings provides a thorough example of doing just this. Nurkiewicz not only covers using JavaScript on the client side, but also provides examples via curl of the JSON format returned by Jolokia.

Jolokia's current version is 0.90 and the main page suggests that 1.0 will be released in late summer of 2011. Jolokia can be downloaded as binary or source code (it has an Apache 2 license). Individual JARs such as the JVM-Agent JAR and the Java Client JAR used in my example can be downloaded as well. The main Jolokia site provides simple examples of using the Jolokia Java Client Library, the JavaScript Client Library, and the Perl Client Library. There are also simple examples of code using the various agents such as the JVM Agent. The Jolokia Reference Documentation is still a work in progress and is also available in PDF format (though the HTML format seems more complete to me).


Conclusion

Jolokia holds significant promise as a mechanism for exposing valuable information held within Java applications and their hosting JVMs to non-Java/non-JVM clients. In this post, I've looked at one very simple example of accessing a Java application's JVM information from a Groovy client using Jolokia.

No comments: