Główna » Cloud, Java, Polecane, Spring Framework

Pomiar i monitoring czasu wykonania metod w aplikacjach Springframework

27 January 2012 Brak komentarzy

W pewnym momencie rozwoju aplikacji najpoważniejszym wyzwaniem staje się zapewnienie wydajności. Przy wzroście ilości użytkowników wzrasta obciążenie systemu. Wzrasta również ilość przechowywanych danych i operacje na tych samych tabelach zajmują znacznie więcej czasu niż dotychczas.
Często trudno jest ocenić w którym miejscu aplikacji tracimy najwięcej, dodatkowo takich miejsc jest wiele i trudno wybrać, które fragmenty kodu optymalizować najpierw, aby uzyskać jak najwięcej.

Z pomocą programowania aspektowego (Aspektów), anotacji oraz prostego obiektu generowania statystyk można łatwo stworzyć idealne narzędzie do analizy wydajności i wyszukiwanie wąskich gardeł w systemie.

Wystarczy stworzyć anotację:

package pl.benhauer.salesmanago.tools.monitoring;
 
import java.lang.annotation.*;
 
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Monitored {
}

Anotacja ta pozwala na dekorowanie metod oraz typów – a więc będzie możliwość użycia jej na całej klasie.

Następnie tworzymy aspekt, który będzie wykonanie metod monitorował i zbierał statystyki

package pl.benhauer.salesmanago.tools.monitoring;
 
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
 
@Aspect
public class MonitoredAspect {
 
    private static final boolean GENERATE_STATISTICS = true;
 
    @Pointcut(value = "execution(public * *(..))")
    public void anyPublicMethod() {
    }
 
    @Pointcut(value = "execution(* (@pl.benhauer.salesmanago.tools.monitoring.Monitored *).*(..))")
    public void anyAnnotatedClass() {
    }
 
    @Around("anyPublicMethod() && @annotation(monitored)")
    public Object monitorMethod(ProceedingJoinPoint pjp, Monitored monitored) throws Throwable {
        return proceed(pjp);
    }
 
    @Around("anyAnnotatedClass()")
    public Object monitorClass(ProceedingJoinPoint pjp) throws Throwable {
        return proceed(pjp);
    }
 
    private Object proceed(ProceedingJoinPoint pjp) throws Throwable {
        if (GENERATE_STATISTICS)
            return proceedWithStatistics(pjp);
        else
            return pjp.proceed();
    }
 
    private Object proceedWithStatistics(ProceedingJoinPoint pjp) throws Throwable {
        String methodId = resolveMethodId(pjp);
 
        long st = System.nanoTime();
        Object result = pjp.proceed();
        long et = System.nanoTime() - st;
        MonitoringStatistics.updateStats(methodId, et);
 
        return result;
    }
 
    private String resolveMethodId(ProceedingJoinPoint pjp) {
        String name = pjp.getSignature().getName();
        String declaringTypeName = pjp.getSignature().getDeclaringTypeName();
        return declaringTypeName + "." + name;
    }
}

Aspekt korzysta z prostej klasy pseudo licznika

package pl.benhauer.salesmanago.tools.monitoring;
 
import java.util.*;
 
public class MonitoringStatistics {
 
    private static Map<String, MethodExecutionStatistics> METHOD_STATISTICS = Collections.synchronizedMap(new HashMap<String, MethodExecutionStatistics>());
 
    public static void updateStats(String methodId, long executionTime) {
        MethodExecutionStatistics methodExecutionStatistics = METHOD_STATISTICS.get(methodId);
        if (methodExecutionStatistics == null) {
            methodExecutionStatistics = new MethodExecutionStatistics(methodId);
            METHOD_STATISTICS.put(methodId, methodExecutionStatistics);
        }
 
        methodExecutionStatistics.update(executionTime);
    }
 
    public List<MethodExecutionStatistics> getMethodStatistics() {
        List<MethodExecutionStatistics> values = new ArrayList<MethodExecutionStatistics>(METHOD_STATISTICS.values());
        Collections.sort(values);
        return values;
    }
}

której elementem jest klasa zliczająca statystyki dla konkretnych metod:

package pl.benhauer.salesmanago.tools.monitoring;
 
import java.util.concurrent.TimeUnit;
 
public class MethodExecutionStatistics implements Comparable<MethodExecutionStatistics> {
 
    private String methodId;
 
    private long totalExecutionTime;
    private long maxExecutionTime = Long.MIN_VALUE;
    private long minExecutionTime = Long.MAX_VALUE;
    private long executedTimes;
 
    public MethodExecutionStatistics(String methodId) {
        this.methodId = methodId;
    }
 
    public synchronized void update(long executionTime) {
        this.executedTimes++;
        totalExecutionTime += executionTime;
        if (maxExecutionTime < executionTime)
            maxExecutionTime = executionTime;
        if (minExecutionTime > executionTime)
            minExecutionTime = executionTime;
    }
 
    public void reset() {
        totalExecutionTime = 0L;
        maxExecutionTime = Long.MIN_VALUE;
        minExecutionTime = Long.MAX_VALUE;
        executedTimes = 0L;
    }
 
    public String getMethodId() {
        return methodId;
    }
 
    public long getTotalExecutionTime() {
        return totalExecutionTime;
    }
 
    public long getAverageExecutionTime() {
        return executedTimes > 0L ? (long) ((double) totalExecutionTime / ((double) executedTimes)) : 0;
    }
 
    public long getAverageExecutionTimeMs() {
        return toMs(getAverageExecutionTime());
    }
 
    public long getMaxExecutionTime() {
        return maxExecutionTime;
    }
 
    public long getMaxExecutionTimeMs() {
        return toMs(maxExecutionTime);
    }
 
    public long getTotalExecutionTimeMs() {
        return toMs(totalExecutionTime);
    }
 
    public long getMinExecutionTime() {
        return minExecutionTime;
    }
 
    public long getMinExecutionTimeMs() {
        return toMs(minExecutionTime);
    }
 
    public long getExecutedTimes() {
        return executedTimes;
    }
 
    private long toMs(long ns) {
        return TimeUnit.NANOSECONDS.toMillis(ns);
    }
 
    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append(methodId).append('[');
        sb.append("AverageExecutionTime = ").append(getAverageExecutionTime());
        sb.append(", TotalExecutionTime = ").append(totalExecutionTime);
        sb.append(", MaxExecutionTime = ").append(maxExecutionTime);
        sb.append(", MinExecutionTime = ").append(minExecutionTime);
        sb.append(", ExecutedTimes = ").append(executedTimes);
        sb.append(']');
        return sb.toString();
    }
 
    @Override
    public int compareTo(MethodExecutionStatistics methodExecutionStatistics) {
        return Long.valueOf(methodExecutionStatistics.getAverageExecutionTime()).compareTo(getAverageExecutionTime());
    }
 
    public String getShortMethodId() {
        return methodId.replace("pl.benhauer.salesmanago.", "");
    }
}

Kolejnym krokiem jest aktywacja aspektów w Springframework, wystarczy dodać

<aop:aspectj-autoproxy/>

do pliku konfiguracji.

Teraz wystarczy dodać anotacje do wybranych metod i/lub klas by na gadżeciarskim panelu admina otrzymać taki oto rezultat:

Jak na dłoni widząc miejsca, nad którymi należy popracować…

Zostaw odpowiedź!

Musisz się zalogować aby móc komentować.