Application monitoring with JMX and Jolokia

(or: It’s the inner values, that count)

Remember last time, when your application was all green in your monitoring suite, but you got complaints, because it did not do, what it was expected to? Or have you ever been in the situation, where you wanted to measure what your application does, without going through megabytes of logfiles? Do you need some KPI based monitoring? Don’t want to reinvent the wheel?

For any of these cases, the following monitoring approach, using the standard Java JMX approach together with Jolokia as HTTP bridge, will be perfect!

At first, let’s take a look at JMX, the Java Management Extensions:

JMX is a Java API for ressource management. It is a standard from the early days (JSR 3: JMX API, JSR 160: JMX Remote API), got some overhaul recently (Java 6: Merge of the both APIs into JSR 255 – the JMX API version 1.3) and since Java 7, we have the JMX API version 2.0. Basically, JMX consists of three layers, the Instrumentation Layer (the MBeans), the Agent Layer (the MBean Server) and the Distributed Layer (connectors and management client).

Although you can use JMX for managing virtually everything (even services), we just contentrate here on using JMX for monitoring purposes.The same we do for MBeans.

What are MBeans?

Generally spoken, MBeans are resources (e.g. a configuration, a data container, a module, or even a service) with attributes and operations on them. Everything else, like notifications or dynamic structures are out of scope for us now.

Technically, a MBean is a class, which implements an Interface and uses a naming convention, where the Interface name is the same as the class name plus “MBean” at the end:

class MyClass implements MyClassMBean

Now, let’s create a sample counting MBean:

public interface MyEventCounterMBean {
  public long getEventCount();
  public void addEventCount();
  public void setEventCount(long count);
}
package my.monitoring;
public class MyEventCounter implements MyEventCounterMBean {
  public static final String OBJECT_NAME="my.monitoring:type=MyEventCounter";
  private long eventCount=0;

  @Override
  public long getEventCount() {
    return eventCount;
  }

  @Override
  public void addEventCount() {
    eventCount++;
  }

  @Override
  public void setEventCount(long count) {
    this.count = count;
  }
}

Before we can use the bean, we have to make it available. For that, we need to wire it with the MBean server. The MBean server acts as a registry for MBeans, where each MBean is registered by its unique object name. Those object names consists of two parts, a Domain and a number of KeyProperties. The Domain can be seen as the package name of the bean, and one of these KeyProperties, the type, is its class name. if you use the “name” property, it denotes one of its attributes.

So, for our example above, the ObjectName would be:

my.monitoring:type=MyEventCounter

In every JVM, there’s at least one standard MBean server, the PlatformMBeanServer, which can be reached via

MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

In theory, you could use more than one MBean server per JVM, but normally, using only the PlatformMBeanServer is sufficient.

Next step: Accessing MBeans

To access our MBean, we can either use Spring and its magic, or we do it manually.

The manual way looks the following:

We once have to register our bean, e.g. in an init method:

MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName myEventCounterName = new ObjectName(MyEventCounter.OBJECT_NAME);
MyEventCounter myEventCounter = new MyEventCounter();
mbs.registerMBean(myEventCounter, myEventCounterName);

And for every access, we have to retrieve it from the MBeanServer so that we can invoke the methods:

MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName myEventCounterName = new ObjectName(MyEventCounter.OBJECT_NAME);
mbs.invoke(myEventCounterName, "addEventCount", null, null);

Have you seen the second argument of the invoke method? It’s the name of the operation, you want to invoke. If you want to pass arguments, you pass their values as an object array as third, and their signature as fourth string array, e.g.

mbs.invoke(myEventCounterName, "setEventCount", new Object[] {number}, new String[] {int.class.getName()});

If we’re lucky, and our whole application is managed by Spring, it’s sufficcient to work with configuration and annotation only.

The MBean needs to be annotated as @Component and @ManagedResource with the object name as parameter:

@Component
@ManagedResource(objectName="my.monitoring:type=MyEventCounter")

and the attributes need a @ManagedAttribute:

@Override
@ManagedAttribute
public void addEventCount() {
  eventCount++;
}

In your spring configuration, besides the <context:component-scan> tag, you need one additional line for exporting the MBeans:

<context:mbean-export>

And those classes, which want to use the bean, just have to import it with the @Autowired annotation:

@Autowired
private MyEventCounterMBean myEventCounterMBean

Accessing MBeans from outside, using Jolokia

Of course, with the jconsole, you can access your MBeans, but a more elegant and more firewall-friendly way is use an HTTP bridge, which allows you to access the MBeans over HTTP. That’s, where Jolokia joins the game.

Jolokia is JMX-JSON-HTTP bridge, which allows access to your MBeans over HTTP and returns their attributes in JSON. Nice, isn’t it? Besides that, it allows bulk requests for improved performance, has got a security layer to restrict access and is really easy to install.

If you want to monitor your webapp, which runs inside tomcat, all you need is, to deploy the Jolokia agent webapp (available as a .war file) into your tomcat.

For a standalone Java application, just apply the Jolokia JVM agent (which in fact acts as an internal HTTP server) as javaagent in your start script:

java -javaagent:$BASE_DIR/libs/jolokia-jvm-1.2.1-agent.jar=port=9999,host=*

And if you build with gradle, apply the following line to your build.gradle:

runtime (group:"org.jolokia", name:"jolokia-jvm", classifier:"agent", version:"1.2.1")

Helpers – jmx4perl

Now, that we can access our MBeans from outside, it would be nice to have a tool available to just read the values on the comand line. The best tool for that is jmx4perl, which is available on github at https://github.com/rhuss/jmx4perl

The installation reminds of the good old Perl days with CPAN. If you’ve never worked with CPAN, just install jmx4perl according to the documentation and ACK all questions.

Now, let’s get an overview of all available MBeans:

jmx4perl http://your.application.host:9999/jolokia list

And if you want a decicated bean, run:

jmx4perl http://your.application.host:9999/jolokia read my.monitoring:type=MyEventCounter

Your output is in JSON and will be like:

{
 EventCount => 234,
 Name => 'MyEventCounter'
}

And finally, if you just need one attribute, run:

jmx4perl http://your.application.host:9999/jolokia read my.monitoring:type=MyEventCounter EventCount

In that case, you’ll get nothing but the value as a result.

Let’s go!

With these tools and figures, you can monitor virtually everything inside your application. All you have to do now is to provide the data (and you, as the developer of your application know, what exactly shall be monitored) and to monitor it with Nagios, OpenTSDB, whatever you want. All these tools are able, either directly, or with helpers like jmx4perl, to access, process and monitor the data.

One thought on “Application monitoring with JMX and Jolokia

  1. Hi. I know your post is about using your own client but I can’t make it woekrd with JConsole so I was hoping you could help me.I need to monitor a weblogic server running on a remote machine using JConsole. Just for now, I’m testing using two machines with Ubuntu OS, but the real server uses Solaris. So, I’m using my pc as the server and a partner’s pc to connect remotely using JConsole.Environment: the server runningis WebLogic 10.3; the server is running with Java Sun JDK 1.6.0_26 and the client is running with Java Sun JDK 1.6.0_20I’m able to connect Locally, running JConsole and clicking on the weblogic process.The application deployment is pretty big, it runs a few scripts. When the domain of weblogic is created I set the following properties:java -classpath ${CLASSPATH} ${MY_OPTS} -Dweblogic.management.username=${ADMIN_USER} \-Dweblogic.management.password=${ADMIN_PASSWD} -Dweblogic.Domain=${MY_DOMAIN} \-Dweblogic.Name=${MY_SERVER} -Dweblogic.ListenPort=${MY_PORT} \-Dweblogic.RootDirectory=${MY_DOMAIN_HOME}/${MY_DOMAIN} \-Djava.rmi.server.hostname=${ADMIN_HOST} \-Dcom.sun.management.jmxremote.port=22222 \-Dcom.sun.management.jmxremote.authenticate=true \-Dcom.sun.management.jmxremote.ssl=false \-Dcom.sun.management.jmxremote.password.file=/home/MY_USER/jmxremote.password \-Dcom.sun.management.jmxremote.access.file=/home/MY_USER/jmxremote.access \-Dweblogic.management.GenerateDefaultConfig=true weblogic.Server &Then, I run jconsole -debug from client’s console and try to connect with the remote option using:service:jmx:rmi:///jndi/rmi://:22222/jmxrmiADMIN_HOST = the server_ip, that is 172.17.209.66And I get the following error:22/11/2011 05:22:54 RMIConnector connectMc1S FINA: [javax.management.remote.rmi.RMIConnector: jmxServiceURL=service:jmx:rmi:///jndi/rmi://172.17.209.66:22222/jmxrmi] connecting…22/11/2011 05:22:54 RMIConnector connectMc1S FINA: [javax.management.remote.rmi.RMIConnector: jmxServiceURL=service:jmx:rmi:///jndi/rmi://172.17.209.66:22222/jmxrmi] finding stub…22/11/2011 05:22:54 RMIConnector connectMc1S FINA: [javax.management.remote.rmi.RMIConnector: jmxServiceURL=service:jmx:rmi:///jndi/rmi://172.17.209.66:22222/jmxrmi] Failed to retrieve RMIServer stub: javax.naming.ServiceUnavailableException [Root exception is java.rmi.ConnectException: Connection refused to host: 172.17.209.66; nested exception is: java.net.ConnectException: Conexif3n rehusada]java.io.IOException: Failed to retrieve RMIServer stub: javax.naming.ServiceUnavailableException [Root exception is java.rmi.ConnectException: Connection refused to host: 172.17.209.65; nested exception is: java.net.ConnectException: Conexif3n rehusada] at javax.management.remote.rmi.RMIConnector.connect(RMIConnector.java:340) at javax.management.remote.JMXConnectorFactory.connect(JMXConnectorFactory.java:248) at javax.management.remote.JMXConnectorFactory.connect(JMXConnectorFactory.java:207)And in the logs of my application I see the following:Conector JMX ready in: service:jmx:rmi:///jndi/rmi://usuario-System-Product-Name:22222/jmxrmiThe /etc/hosts shows: 172.17.209.65 usuario-System-Product-Name # Added by NetworkManager 127.0.0.1 localhost.localdomain localhost ::1 usuario-System-Product-Name localhost6.localdomain6 localhost6 127.0.1.1 usuario-System-Product-Nameis that why I see the usuario-System-Product-Name and not the ip? Even if I set as hostname the ip of my computer?I disabled both firewals and try the command line jmx tool Alex suggested buy with no luck :-(Any other suggestions / tips / help ????

Leave a Reply