package org.dcache.commons.stats;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.Uninterruptibles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.ExecutionException;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* MonitoringProxy can be used to decorate a any class implementing an interface
* T for measuring the number of invocations
* or the execution times or both of each of the method of T, using RequestCounters
* and RequestExecutionTimeGauges classes.
* This class provides a utility method for decorating a class that implements
* interface T with a MonitoringProxy proxy.
* <p/>
* The article
* <a href=http://www.ibm.com/developerworks/java/library/j-jtp08305.html>
* Java theory and practice: Decorating with dynamic proxies</a>
* from IBM Developer Network claims that performance impact from dynamic proxies
* should be minimal.
* @author timur
*/
public class MonitoringProxy <T> implements InvocationHandler {
private static final Logger logger = LoggerFactory.getLogger(MonitoringProxy.class);
private final T monitoredObject;
private final RequestCounters<Method> counter;
private final RequestExecutionTimeGauges<Method> gauge;
private MonitoringProxy(T monitoredObject,
RequestCounters<Method> counter,
RequestExecutionTimeGauges<Method> gauge) {
this.monitoredObject = checkNotNull(monitoredObject);
this.counter = checkNotNull(counter);
this.gauge = checkNotNull(gauge);
}
@Override
public Object invoke(Object proxy, final Method method, Object[] args)
throws Throwable {
counter.incrementRequests(method);
final long startTimeStamp = System.currentTimeMillis();
Object result = null;
try {
result = method.invoke(monitoredObject, args);
} catch (InvocationTargetException e) {
counter.incrementFailed(method);
throw e.getTargetException();
} catch (Error | RuntimeException e) {
counter.incrementFailed(method);
throw e;
} finally {
if (result instanceof ListenableFuture) {
final ListenableFuture<?> future = (ListenableFuture<?>) result;
future.addListener(
new Runnable()
{
@Override
public void run()
{
try {
Uninterruptibles.getUninterruptibly(future);
} catch (ExecutionException | Error | RuntimeException e) {
counter.incrementFailed(method);
}
updateTime(method, startTimeStamp);
}
},
MoreExecutors.directExecutor());
} else {
updateTime(method, startTimeStamp);
}
}
return result;
}
private void updateTime(Method method, long startTimeStamp)
{
long execTime = System.currentTimeMillis() - startTimeStamp;
logger.debug("invocation of {} took {} ms", method, execTime);
gauge.update(method, execTime);
}
public static <T> T decorateWithMonitoringProxy(Class<?>[] interfaces,
T monitoringObject,
RequestCounters<Method> counter,
RequestExecutionTimeGauges<Method> gauge) {
MonitoringProxy <T> monitoringHandler = new MonitoringProxy<>(monitoringObject,counter,gauge);
return (T) Proxy.newProxyInstance(monitoringObject.getClass().getClassLoader(),
interfaces, monitoringHandler);
}
}