package com.kendelong.util.performance;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import com.kendelong.util.monitoring.graphite.GraphiteClient;
import com.kendelong.util.monitoring.webservice.ExternalNameElementComputer;
/**
* This interceptor will log all calls to the proxied bean, and record max, min, and average response times, call rates,
* and number of exceptions at both the class and method level. By default it proxies every bean whose name ends with
* "Controller" or "Service", as well as any bean annotated with the MonitorPerformance annotation.
*
* Like all aspects, you must create one in each ApplicationContext that you wish to use it in. Be sure to give the prototype instance
* an id; if not, the auto-assigned id's in the different ApplicationContexts will conflict.
*
* Export the bean to JMX (no point in monitoring response times if you can't see the results!) using the
* com.kendelong.util.spring.JmxExportingAspectPostProcessor
*
* <pre>
* {@code
<bean id="servicePerformanceMonitor" class="com.kendelong.util.performance.PerformanceMonitoringAspect" scope="prototype">
<property name="graphiteClient" ref="graphiteClient"/>
</bean>
<bean id="aspectJmxExporter" class="com.kendelong.util.spring.JmxExportingAspectPostProcessor" lazy-init="false">
<property name="mbeanExporter" ref="mbeanExporter"/>
<property name="annotationToServiceNames">
<map>
<entry key="com.kendelong.util.performance.PerformanceMonitoringAspect" value="performance" />
</map>
</property>
<property name="jmxDomain" value="app.mystuff"/>
<property name="sendControllerMethodDataToGraphite" value="false"/>
</bean>
* }
* </pre>
*
* @author Ken DeLong
* @see com.kendelong.util.performance.MonitorPerformance
* @see com.kendelong.util.spring.JmxExportingAspectPostProcessor
*
*/
@Aspect
@ManagedResource(description="Monitor basic performance metrics")
@Order(400)
public class PerformanceMonitoringAspect implements Ordered
{
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final Map<String, PerformanceMonitor> monitors = new ConcurrentHashMap<>();
private final ExternalNameElementComputer nameElementComputer = new ExternalNameElementComputer();
private GraphiteClient graphiteClient;
private Boolean sendControllerMethodDataToGraphite = false;
private int order = 0;
@Around("bean(*Controller)")
public Object monitorControllers(ProceedingJoinPoint pjp) throws Throwable
{
return monitorInvocation(pjp, sendControllerMethodDataToGraphite);
}
@Around("@within(ann)")
public Object monitorAnnotatedClasses(ProceedingJoinPoint pjp, MonitorPerformance ann) throws Throwable
{
return monitorInvocation(pjp, ann.sendMethodDataToGraphite());
}
//@Around("bean(*Controller) or @within(com.kendelong.util.performance.MonitorPerformance)")
public Object monitorInvocation(ProceedingJoinPoint pjp, boolean includeMethods) throws Throwable
{
String classKey = StringUtils.substringAfterLast(pjp.getSignature().getDeclaringTypeName(), ".");
String methodName = pjp.getSignature().getName();
String methodKey = classKey + "." + methodName;
PerformanceMonitor classMonitor = getMonitor(classKey);
PerformanceMonitor methodMonitor = getMonitor(methodKey);
Object value;
long startTime = System.currentTimeMillis();
String graphitePrefix = null;
try
{
if(graphiteClient != null)
{
String nameElement = nameElementComputer.computeExternalNameElement(pjp.getTarget().getClass());
graphitePrefix = "performance.";
if(nameElement != null) graphitePrefix = "webservice." + nameElement + ".";
graphiteClient.increment(graphitePrefix + classKey + ".accesses");
if(includeMethods) graphiteClient.increment(graphitePrefix + methodKey + ".accesses");
}
value = pjp.proceed();
long stopTime = System.currentTimeMillis();
long duration = stopTime - startTime;
classMonitor.addTiming(duration);
methodMonitor.addTiming(duration);
if(logger.isTraceEnabled())
{
logger.trace("Performance monitor [" + methodKey + "] finished in [" + duration + "] ms");
}
if(graphiteClient != null)
{
graphiteClient.time(graphitePrefix + classKey, duration);
if(includeMethods) graphiteClient.time(graphitePrefix + methodKey, duration);
}
return value;
}
catch(Throwable t)
{
classMonitor.addException();
methodMonitor.addException();
if(graphiteClient != null)
{
graphiteClient.increment(graphitePrefix + classKey + ".error");
if(includeMethods) graphiteClient.increment(graphitePrefix + methodKey + ".error");
}
throw t;
}
}
private PerformanceMonitor getMonitor(String key)
{
PerformanceMonitor monitor = null;
monitor = monitors.get(key);
if(monitor == null)
{
monitor = new PerformanceMonitor();
monitors.put(key, monitor);
}
return monitor;
}
@ManagedAttribute(description="Show performance report")
public String getPerformanceReport()
{
ReportFormatter formatter = new ReportFormatter();
return formatter.formatReport(monitors);
}
@ManagedAttribute(description="Get map of monitor objects")
public Map<String, PerformanceMonitor> getMonitors()
{
return monitors;
}
public GraphiteClient getGraphiteClient()
{
return graphiteClient;
}
public void setGraphiteClient(GraphiteClient graphiteClient)
{
this.graphiteClient = graphiteClient;
}
@Override
public int getOrder()
{
return order;
}
public void setOrder(int theOrder)
{
order = theOrder;
}
@ManagedOperation(description="Reset all monitors")
public void resetAllMonitors()
{
monitors.clear();
}
public Boolean getSendControllerMethodDataToGraphite()
{
return sendControllerMethodDataToGraphite;
}
@ManagedAttribute(description="Whether to send dedicated method-level data to graphite for Controllers")
public void setSendControllerMethodDataToGraphite(Boolean sendControllerMethodDataToGraphite)
{
this.sendControllerMethodDataToGraphite = sendControllerMethodDataToGraphite;
}
}