Instrumenting Java Code

Posted by n3integration on September 23, 2015

In an effort to improve performance, developers need ways to instrument their code to track the amount of time taken per operation. There are a few ways to accomplish this while maintaining an elegant solution. Rather than roll your own solution, it is better to leverage an existing metrics library. One popular library for the JVM is the Dropwizard Metrics project, which includes counters, gauges, and timers. Although it’s outside of the scope of this post to cover all of the available functionality, for this use case, we’re interested in the timers.

In the case of maintaining an elegant codebase, how can this library be easily integrated into your project? Object composition is preferable over inheritance, as it leads to code that is easier to understand. With this in mind, let’s consider a decorator. Enter Java’s dynamic Proxy class. A dynamic Proxy allows developers to decorate any object so long as it implements an interface. This is especially useful for tracking performance or application health without bloating the codebase. Google’s guava library offers a more method for defining dynamic proxies:

public static <T> Service<T> getProxiedInstance(final MetricsRegistry registry, final Service<T> delegate) {
  final String metric = delegate.getClass().getSimpleName();
  return Reflection.newProxy(Service.class, (proxy, method, args) -> {
    long start = System.currentTimeMillis();
    try {
      return method.invoke(delegate, args);
    }
    finally {
      Timer t = registry.timer(metric);
      t.update(System.currentTimeMillis() - start, TimeUnit.MILLISECONDS);
    }
  });
}

For clarity, let’s break down the above example.

  1. The MetricsRegistry is a component of the Dropwizard Metrics implementation. This class contains a collection of the metrics being tracked within the application.
  2. Service is the common interface being proxied. The Proxy delegates method calls to this instance.
  3. Each metric is tracked through a unique key. In this case, we’ll use the simple class name for the implementation.
  4. The call to Reflection.newProxy is the guava-way to instantiate a new Proxy instance.
  5. Next, we grab the current time in milliseconds.
  6. Then, we invoke the method on the underlying delegate instance.
  7. We call into the registry for a Timer instance using our metric declared outside of the anonymous block. If a Timer has not already been instantiated, a new instance is returned to the caller.
  8. Then, we update the Timer with the current elapsed time.

If you don’t have an interface or would like a more generic approach, a ProxyFactory could be defined using the javassist library. This solution is not much more complex than the previous example, but it does add additional dependencies to your project.

public static <T> T getInstance(MetricsRegistry registry, Class<T> clazz) {
  try {
    ProxyFactory factory = new ProxyFactory();
    factory.setSuperclass(clazz);

    Class proxyClass = factory.createClass();
    T instance = clazz.cast(proxyClass.newInstance());

    final String metric = String.format("%s.%s", clazz.getSimpleName(), method);

    ((ProxyObject)instance).setHandler((self, method, proceed, args) -> {
      long start = System.currentTimeMillis();
      try {
        return proceed.invoke(instance, args);
      }
      finally {
        Timer t = registry.timer(metric);
        t.update(System.currentTimeMillis() - start, TimeUnit.MILLISECONDS);        
      }
    });

    return instance;
  }
  catch(InstantiationException | IllegalAccessException e) {
    throw new RuntimeException(
      "Failed to instrument class. Does the class being instrumented have a no arg constructor?", e
    );
  }
}

In addition to a third-party dependency, one difference between this implementation is the try...catch block. This is required due to the object instantiation through Java’s reflection API. These exceptions can be thrown in the case where a no args constructor is not defined for the clazz or it is not visible (i.e. public).