Що спільного в словах AOP та Performance tool ? -нічого , окрім того що задопомогою АОР ми будемо пиляти власного “валосипеда” і спробуємо ним поміря швидкодію Java аплікацій.
Отож, визначимо цілі нашого проекту :
- Отримаувати заміри виконання методі задопомогою анотацій
- Керувати історією викликів мотода
- Отримувати результат в наступних форматах : String, JSON, HTML
Отже, створюємо наш проект командою : “mvn archetype:create -DgroupId=org.ar.stat4j -DartifactId=Stat4J” з цього бидно архітектуру пакетів а також горду назву “Stat4J” (типу заявка на світове визнання 8-D ).
Додаємо залежності в pom.xml :
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
Створюємо пакет “org.ar.stat4j.annotations” і додаємо в в нього клас “Stat4JPoint.java”, це і буде наша анотація котру ставитимемо над методом котрий будемо заміряти. Відкриваємо клас і пишемо в ньому настйпний код :
@Target(ElementType.METHOD)
}
@Target – місце над яким можна буде декларувати дану анотацію , у нашому випадку це методи.
@Retention – як довго дана анотація буде зберігатися над анотованим типом, у нашому випадку напротязі усього виконання аплікації.
@interface – вказує що даний клас є анотацією
Отже тепер ми маємо анотацію яка буде вказувати на методи котрі нам треба заміряти, залишилось тільки відслідковувати коли під час виконання аплікацію будуть викликатися анотовані цією анотацією методи. Для цього нам і потрібне АОР.
Створюємо пакет “org.ar.stat4j.aspects” і додаємо туди наш аспект “Stat4JAsp.aj”. Даний аспект повинен спрацьовувати коли буде викликано будь який метод анотований нашою анотацією. Для цього в середену аспекту пишемо :
@Aspectpublic class Stat4JAsp {
@Around(“execution(* *(..)) &&@annotation(org.ar.stat4j.annotations.Stat4JPoint)”) public Object around(ProceedingJoinPoint point) throws Throwable {
}
}
@Aspect – вказує AspectJ плагіну (який ми пізніше додато до конфігурації мавена) , що даний клас є аспектом.
@Around – вказує що даний аспект має виконатись навколо методу , тобто не перед чи після а саме навко методу (іншими словами його обгортаємо) а як аргумент ми передаємо умову при якій даний аспект повинен виконатись : * – будь який ретурн тип , * – будь яка назва методу , (..) – будь яка кількість аргументів, && – а також , @annotation(<type>) – анотація типу <type>.
Тепер у нас є анотація і аспект який її відслідковуватиме.
Залишилось лише додати плагін до bild секції в pom.xml який під час компіляції проекту зробить всю ту “магію” яка називається АОР (Обгорне анотовані методи своєю іпмлементацією). Плагін має виглядати так :
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>${aj.mvn.pg.version}</version>
<executions>
<execution>
<goals>
<goal>compile</goal><!– to weave all your main classes –>
<goal>test-compile</goal><!– to weave all your test classes –>
</execution>
</executions>
<configuration>
<sources>
<source>
<basedir>src/main/java/org/ar/stat4j/aspects</basedir>
<includes>
<include>**/*.aj</include>
</includes>
</source>
</sources>
<complianceLevel>1.8</complianceLevel>
<outxml>false</outxml>
<verbose>true</verbose>
<showWeaveInfo>true</showWeaveInfo>
<source>1.8</source>
<target>1.8</target>
</configuration></plugin>
В даній конфігурації важливо вірно вказати пакет в якому знаходяться ваші аспекти а також версію джави (у нашому випадку 1.8).
Пробуємо білдати наш проект : mvn clean install , і якщо бачимо :
[INFO] ————————————————————————
[INFO] BUILD SUCCESS
[INFO] ————————————————————————
public class Stat4J {
private Stat4J() {}
}
}
}
Statistic.java
public class Statistic {
…
}
Point.java
public class Point implements Comparable<Point>{
private long startTrack;
private long finishTrack;
…
}
(приклад наводжу на одному з методів, повна версія буде доступна за посилання в кінці поста)
public long getMaxExecutionTimeInNano(){
return stats.get(stats.size()-1).executionTimeInNanoseconds();
}
}
Stat4J.java
…
…
public Point startTrack(String componentName, String pointName) {
if (!records.containsKey(componentName)) {
statistic.getPoints().add(point);
Map<String, Statistic> pointToStat = new HashMap<>();
pointToStat.put(pointName, statistic);
records.put(componentName, pointToStat);
} else if (!records.get(componentName).containsKey(pointName)) {
Statistic statistic = new Statistic();
statistic.getPoints().add(point);
records.get(componentName).put(pointName, statistic);
} else {
records.get(componentName).get(pointName).getPoints().add(point);
}
return point;
}Перший if ініціалізує цілу групу замірів відштовхуєчись від імені класу. Другий if інішіалізує групу замірів на базі методів у випадку якщо група класу вже ініціалізована. А третій if спрацює якщо група класу і методі уже є , тоді тільки додасть новий замір.
public void stopTrack(Point point) {
}
Маючи все вище написане залишаться оновити наш аспект і в його тілі вказати що перш ніж виконати метод ми повинні розпочати замір а далі виконати метод і після цього закінчити замір:
Point statisticPoint = Stat4J.instance().startTrack(point.getTarget().getClass().getCanonicalName(), MethodSignature.class.cast(point.getSignature()).getMethod().getName());
return result;
У випадку якщо наш метод нічого не повертає , ми всеодно повинні повертати Object як результат виконання методу так як на даному етапі аспект нічого про сам метод не знає.
public static final String COMPONENT_OUTPUT = “%1s.%2s:tCall times: %3s,tMax: %4s (ns)t|t%5s(ms),tMin: %6s(ns)t|t%7s(ms),tAvr: %8s(ns)t|t%9s(ms);n”;
public static final String POINT_OUTPUT = “tt%1s:tExecution date:
public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(“MM.dd.yy HH:mm:ss.SSS”);
public String print(Map<String, Map<String, Statistic>> statistic, boolean history){
final StringBuilder strBld = new StringBuilder();
statistic.forEach((componentName, points) -> points.forEach((pointName, stats)
history))));
return strBld.toString();
}
StringBuilder strBld = new StringBuilder();
statistic.getPointSize(), statistic.getMaxExecutionTimeInNano(),
statistic.getMaxExecutionTimeInMili(),
statistic.getMinExecutionTimeInNano(),
statistic.getMinExecutionTimeInMili(),
statistic.getAverageExecutionTimeInNano(),
statistic.getAverageExecutionTimeInMili()));
if (history && statistic.getPoints().size() > 1) {
strBld.append(String.format(POINT_OUTPUT,pointName,
DATE_FORMAT.format(statPoint.getExecutionDate()),
statPoint.executionTimeInNanoseconds(),
statPoint.executionTimeInMiliseconds())));
}
Тепер потрібно додати метод в головний клас утиліти для виводу статистики який буде приймати аргументом тип прінтера (String, JSON, HTML) і вмикання / вимикання історії викликів :
private final StringPrinter stringPrinter = new StringPrinter();
…
…
Після цього напишемо тестовий склас методи якого нічого не робитимуть але зупинятимуть потік на якийсь час, і заанотуємо ці методи щоб виміряти їх час виконання :
public class PerformanceTestObject {
@Stat4JPoint
public void method2MS() throws InterruptedException {
}
public void method25MS() throws InterruptedException {
}
public int method155MS() throws InterruptedException {
return 155;
}
public void method1Sec() throws InterruptedException {
}
}
і відповідно сам тест :
public class Point4JAspTest {
public void testPerformanceMeassuringStringOutput() throws InterruptedException { PerformanceTestObject performanceTestObject = new PerformanceTestObject();
performanceTestObject.method25MS();
performanceTestObject.method25MS();
performanceTestObject.method25MS();
performanceTestObject.method1Sec();
performanceTestObject.method155MS();
}
}
org.ar.sta4j.PerformanceTestObject.method25MS: Call times: 4, Max: 30011694(ns) | 30(ms), Min: 27759291(ns) | 27(ms), Avr: 28949879(ns) | 28(ms);
method25MS: Execution date: 03.03.76 14:58:04.321, Execution time: 27759291 (ns)| 27(ms);
method25MS: Execution date: 03.03.76 07:00:38.850, Execution time: 28609810 (ns)| 28(ms);
method25MS: Execution date: 03.02.76 14:27:54.968, Execution time: 29418723 (ns)| 29(ms);
method25MS: Execution date: 03.02.76 22:38:51.322, Execution time: 30011694 (ns)| 30(ms);
org.ar.sta4j.PerformanceTestObject.method2MS: Call times: 1, Max: 3253797(ns) | 3(ms), Min: 3253797(ns) | 3(ms), Avr: 3253797(ns) | 3(ms);
org.ar.sta4j.PerformanceTestObject.method1Sec: Call times: 1, Max: 1002833335(ns) | 1002(ms), Min: 1002833335(ns) | 1002(ms), Avr: 1002833335(ns) | 1002(ms);
org.ar.sta4j.PerformanceTestObject.method155MS: Call times: 1, Max: 162245276(ns) | 162(ms), Min: 162245276(ns) | 162(ms), Avr: 162245276(ns) | 162(ms);
Ось і все. Прінтери для JSON та HTML формату не став тут викладати заради економії часу проте все чого тут не вистачає ви зможене знайти тут.
Очікую ваші відгуки та пропозиції щодо даної утиліти.
П.С. Надіюсь було цікаво.