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.
- The
MetricsRegistryis a component of the Dropwizard Metrics implementation. This class contains a collection of the metrics being tracked within the application. Serviceis the common interface being proxied. TheProxydelegates method calls to this instance.- Each metric is tracked through a unique key. In this case, we’ll use the simple class name for the implementation.
- The call to
Reflection.newProxyis the guava-way to instantiate a newProxyinstance. - Next, we grab the current time in milliseconds.
- Then, we invoke the method on the underlying delegate instance.
- We call into the registry for a
Timerinstance using ourmetricdeclared outside of the anonymous block. If aTimerhas not already been instantiated, a new instance is returned to the caller. - Then, we update the
Timerwith 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).