First Steps with Jolokia

Posted by & filed under , , .

ExplodingCup6small javaIn a (rather old) previous post I have talked about using Sun (ahem Oracle!) JMX/HTML bridge to manage and monitor your applications. As it happens, that agent has been discontinued and due to various licensing issues (I’m guessing) one can’t even download it normally from a maven repo, and has to rely on all sorts of hacks to include the required jar in the application. Also, one of the downsides of that — while a small price to pay though! — is that you have to write some code to start the agent when your app starts.

In exchange of the (rather small) piece of code that you write to start the agent, you get in exchange a fully-fledged HTML-based app which allows you to inspect and change your managed beans. The trouble with that though — while a very handy tool otherwise! — is that it provides all the data in HTML and all the requests made to the agent are HTTP GET only. This means that if you need to query one attribute of one of your beans, you will have to employ some curl-ing (eeeeassy!) and some HTML parsing (ouch!). Do-able, even more so as the agent uses very simple HTML and the structure of the pages is always the same — but a bit cumbersome at times. Also, unfortunately complex data structures cannot be set or displayed easily using that tool.

As with everything, therefore, there are pros and cons — pro being you get a fully fledged HTML app for next to nothing, the con being that while you don’t have to put effort into providing a HTML app to invoke methods and set attributes, you do need to put some effort into querying values. Because of this, when Jolokia was signalled to me (thank you, Lionel!) I rushed to check it out to see if it can deliver better than the Oracle agent and what are the pros and cons on this.

To start with — since most of the apps I work on are web-based — I used Jolokia’s tutorial and their war implementation inside a Tomcat instance. I haven’t, for the purpose of this article, actually used any of the tools, apart from a simple browser — I like the convenience of that and it’s something that I can use on my mobile, table or laptop, so the occasionaly ninja-style monitoring on the go is always possible with a browser interface! So I like an application that can offer easy integration/parsing/etc with scripting tools/frameworks/languages, but quite often I find myself also in need of a simple HTML interface too which I can access in a browser. Jolokia certainly delivers the former and doesn’t do a bad job at the latter either to be honest.

Let’s have a look at the example I put together for dipping my toe in the water: consider a simple Java servlet which is also a managed bean too.The servlet has a single attribute (Message) — it uses this attribute to display a page like the following when the servlet is invoked (be it HTTP GET or HTTP POST):

Default Message shown by servlet

When the message is being changed (via JMX), it shows it in red just to provide an extra visual confirmation:

Message changed and displayed in red

Also, our managed bean has a single method/operation too: doubleMessage — this is basically the same as a call to:

setMessage( getMessage() + getMessage() );

In the sense that simply concatenates the message to itself twice. Both the attribute and the operation are exposed via JMX so they can be read/written/invoked via JMX. For the JMX side of things I’ve chosen to use a DynamicMBean-based implementation — but there are other ways of exposing this via JMX, so feel free to make changes as necessary.

The servlet registers itself with the MBean server using the name SimpleServlet. (As a side note, as this serlvet gets pooled and multiple instances are created, this will create a problem since the second instance will fail to register due to a name conflict — however, for the purpose of this simple example, we won’t worry about that.)

Also, we’re talking about a servlet, which can take obviously concurrent requests — be them just simple GET requests or requests via JMX — so it’s only appropriate that we provide some thread-safety here; in this instance I simply chose to make the get/set pair synchronized as well as the call to doubleMessage. (Yes, I know, should have used a ReadWriteLock really and use the read lock for get and write lock for the others — too much overload though for a simple “hello world!”, don’t you think?)

With that in mind, our code becomes this:

public class SimpleServlet extends HttpServlet implements DynamicMBean {
 private static final String DEF_MESSAGE      = "Mary had a little lamb. The doctor was very very surprised!";
 private String              message;
 
 public SimpleServlet() {
  this.message = DEF_MESSAGE;
  MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
  ObjectName oName;
  try {
   oName = new ObjectName("SERVLET:name=SimpleServlet");
   mbs.registerMBean(this, oName);
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
 
 private void setHeaders(HttpServletResponse resp) throws IOException {
  resp.addHeader("Content-Type", "text/html");
  resp.addHeader("Expires", "-1");
  resp.addHeader("Pragma", "no-cache");
 }
 
 @Override
 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  setHeaders(resp);
 
  String copy = null;
  synchronized (this) {
   copy = message;
  }
 
  StringBuilder msg = new StringBuilder("<h1>Message of the day is</h1>");
  if (DEF_MESSAGE.equals(copy)) {
   msg.append("<p>");
  } else {
   msg.append("<p style=\"color:red\">");
  }
  msg.append(copy).append("</p>");
 
  resp.getOutputStream().print(msg.toString());
 }
 
 public synchronized final String getMessage() {
  return message;
 }
 
 public synchronized final void setMessage(String message) {
  this.message = message;
 }
 
 public synchronized final void doubleMessage() {
  this.message = message + message;
 }
 
 private static final String ATTR_MESSAGE = "Message";
 private static final String OP_DOUBLE    = "doubleMessage";
 
 @Override
 public Object getAttribute(String attribute) throws AttributeNotFoundException {
  if (ATTR_MESSAGE.equals(attribute)) {
   return getMessage();
  }
  throw new AttributeNotFoundException(attribute);
 }
 
 @Override
 public AttributeList getAttributes(String[] attributes) {
  AttributeList result = new AttributeList();
  Object o;
  for (String a : attributes) {
   try {
    o = getAttribute(a);
    result.add(new Attribute(a, o));
   } catch (AttributeNotFoundException e) {
    e.printStackTrace();
   }
  }
  return result;
 }
 
 @Override
 public MBeanInfo getMBeanInfo() {
  MBeanAttributeInfo[] attr = new MBeanAttributeInfo[] {new MBeanAttributeInfo(ATTR_MESSAGE, String.class.getName(), "The message to be displayed.", true, true, false)};
  MBeanOperationInfo[] op = new MBeanOperationInfo[] {new MBeanOperationInfo(OP_DOUBLE, "Doubles the current message", null, void.class.getName(), MBeanOperationInfo.ACTION)};
 
  return new MBeanInfo(SimpleServlet.class.getName(), "Simple servlet", attr, null, op, null);
 }
 
 @Override
 public Object invoke(String actionName, Object[] params, String[] signature) throws ReflectionException {
  if (OP_DOUBLE.equals(actionName)) {
   doubleMessage();
   return null;
  }
  throw new ReflectionException(new UnsupportedOperationException(actionName));
 }
 
 @Override
 public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException {
  if (ATTR_MESSAGE.equals(attribute.getName())) {
   if (!(attribute.getValue() instanceof String)) {
    throw new InvalidAttributeValueException(ATTR_MESSAGE);
   }
   setMessage((String) attribute.getValue());
  } else {
   throw new AttributeNotFoundException(attribute.getName());
  }
 }
 
 @Override
 public AttributeList setAttributes(AttributeList attributes) {
  if (attributes == null || attributes.size() == 0) {
   return null;
  }
  AttributeList result = new AttributeList();
  for (Object o : attributes) {
   if (o instanceof Attribute) {
    Attribute a = (Attribute) o;
    try {
     setAttribute(a);
     result.add(a);
    } catch (AttributeNotFoundException e) {
     e.printStackTrace();
    } catch (InvalidAttributeValueException e) {
     e.printStackTrace();
    }
   }
  }
  return result;
 }
}

You will recognize the bottom part as being pretty much my standard boilerplate DynamicMBean code, so really the “beef” is in this:

public class SimpleServlet extends HttpServlet implements DynamicMBean {
 private static final String DEF_MESSAGE      = "Mary had a little lamb. The doctor was very very surprised!";
 private String              message;
 
 public SimpleServlet() {
  this.message = DEF_MESSAGE;
  //...
 }
 
 private void setHeaders(HttpServletResponse resp) throws IOException {
  resp.addHeader("Content-Type", "text/html");
  resp.addHeader("Expires", "-1");
  resp.addHeader("Pragma", "no-cache");
 }
 
 @Override
 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  setHeaders(resp);
 
  String copy = null;
  synchronized (this) {
   copy = message;
  }
 
  StringBuilder msg = new StringBuilder("<h1>Message of the day is</h1>");
  if (DEF_MESSAGE.equals(copy)) {
   msg.append("<p>");
  } else {
   msg.append("<p style=\"color:red\">");
  }
  msg.append(copy).append("</p>");
 
  resp.getOutputStream().print(msg.toString());
 }
 
 public synchronized final String getMessage() {
  return message;
 }
 
 public synchronized final void setMessage(String message) {
  this.message = message;
 }
 
 public synchronized final void doubleMessage() {
  this.message = message + message;
 }
 
 //... rest of the JMX boilerplate code
}

With that in mind, compile the code, deploy it to Tomcat (together with the Jolokia jar) and fire up Tomcat… I’m running Tomcat with the standard settings here, so everything will be running on localhost on port 8080.

Based on my code and configuration, the serlvet will be under http://localhost:8080/jktest/index.jsp – and provides by default the first screen shown above. Now, if you followed the Jolokia tutorial, the web context for Jolokia will be /jolokia, so to read any attribute of my bean I simply need to issue this in a browser: http://localhost:8080/jolokia/read/SERVLET:name=SimpleServlet — and I get this JSON response which is a representation of my MBean:

{"timestamp":1361776703,"status":200,"request":{"mbean":"SERVLET:name=SimpleServlet","type":"read"},"value":{"Message":"Mary had a little lamb. The doctor was very very surprised!"}}

Compared to the Oracle/Sun JMX bridge, this is easier to parse — it’s JSON format and there are tons of parsers for tons of languages out there. However, please note that the above URL queries the full MBean — same as with the Oracle/Sun bridge, just that in this case the format is easier to parse.There is a pattern as well to the URL: http://<host>:<port>/<context for Jolokia>/read/<OName of the object> — simple! (This means I can script this easily!)

Jolokia though takes this one step further: I can query one single attribute, with a URL like this: http://<host>:<port>/<context for Jolokia>/read/<OName of the object>/<attribute name> — so in our case, if I want to query the Message attribute, the URL would be: http://localhost:8080/jolokia/read/SERVLET:name=SimpleServlet/Message and the response from the server:

{"timestamp":1361776748,"status":200,"request":{"mbean":"SERVLET:name=SimpleServlet","attribute":"Message","type":"read"},"value":"Mary had a little lamb. The doctor was very very surprised!"}

Now, what about writing/setting an attribute? Well, we just need to change the operation name from read to write and append a value: http://<host>:<port>/<context for Jolokia>/write/<OName of the object>/<attribute name>/Value — for example: http://localhost:8080/jolokia/write/SERVLET:name=SimpleServlet/Message/ABC would set the text to be displayed to ABC.

And finally to execute an operation, you have the URL format: http://<host>:<port>/<context for Jolokia>/exec/<OName of the object>/<operation name> — so in this case, to execute doubleMessage, the URL is http://localhost:8080/jolokia/exec/SERVLET:name=SimpleServlet/doubleMessage.

So far we have been looking at HTTP GET usage only — this is the easiest to script with the likes of curl since you just have to build a URL and curl it; however, as they boast on their website, Jolokia supports a proper REST-ful interface, so you can POST a JSON request and get back exactly the same as the above back. This obviously allows for more complex data structure to be sent over the wire — something that unfortunately the Oracle/Sun bridge doesn’t handle at all 🙁 So in the next post on this subject I’ll be looking at that and also how to embed Jolokia in a non-web app — stay tuned.

As per usual, you can download the full project and source code for this example here: jktest.tar.bz2

 

3 Responses to “First Steps with Jolokia”

  1. Prabodhani

    Thank you very much.. Perfect tutorial.. .

  2. amon

    servlets don’t get pooled, threads do 😉

  3. Garry

    The requests made to Jolokia at http://localhost:8080/jolokia/read/java.lang:type=Memory/HeapMemoryUsage/used always returns this structure:
    {
    “timestamp”: 1427392176,
    “status”: 200,
    “request”: {
    “mbean”: “java.lang:type=Memory”,
    “path”: “used”,
    “attribute”: “HeapMemoryUsage”,
    “type”: “read”
    },
    “value”: 15348352
    }

    but in the section 6.1.4. Paths of http://www.jolokia.org/reference/html/protocol.html, the returned body is just:
    15348352

    Why my requests does not return just 15348352 but the whole json document?

    Very thankful