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
MetricsRegistry
is a component of the Dropwizard Metrics implementation. This class contains a collection of the metrics being tracked within the application. Service
is the common interface being proxied. TheProxy
delegates 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.newProxy
is the guava-way to instantiate a newProxy
instance. - 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
Timer
instance using ourmetric
declared outside of the anonymous block. If aTimer
has not already been instantiated, a new instance is returned to the caller. - 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
).