Pomiar i monitoring czasu wykonania metod w aplikacjach Springframework
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ć.