Перейти к основному содержимому

Введение в SPF4J

· 9 мин. чтения

1. Обзор

Тестирование производительности — это деятельность, которую часто переносят на конечные этапы цикла разработки программного обеспечения. Обычно мы полагаемся на профилировщики Java для устранения проблем с производительностью.

В этом руководстве мы рассмотрим Simple Performance Framework для Java (SPF4J). Он предоставляет нам API, которые можно добавить в наш код. В результате мы можем сделать мониторинг производительности неотъемлемой частью нашего компонента .

2. Основные концепции сбора и визуализации метрик

Прежде чем мы начнем, давайте попробуем понять концепции сбора и визуализации метрик на простом примере.

Давайте представим, что нас интересует мониторинг загрузок только что запущенного приложения в магазине приложений. Ради обучения давайте подумаем о проведении этого эксперимента вручную.

2.1. Сбор показателей

Во-первых, нам нужно решить, что нужно измерять. Нас интересует показатель загрузок/мин. Поэтому будем измерять количество загрузок .

Во-вторых, как часто нам нужно проводить измерения? Давайте определимся «раз в минуту».

Наконец, как долго мы должны контролировать? Давайте определимся «на один час».

Имея эти правила, мы готовы провести эксперимент. После завершения эксперимента мы можем увидеть результаты:

Time    Cumulative Downloads    Downloads/min
----------------------------------------------
T 497 0
T+1 624 127
T+2 676 52
...
T+14 19347 17390
T+15 19427 80
...
T+22 27195 7350
...
T+41 41321 11885
...
T+60 43395 40

Первые два столбца — время и совокупное количество загрузок — являются прямыми значениями, которые мы наблюдаем. Третий столбец, загрузок/мин , представляет собой производное значение, вычисляемое как разница между текущим и предыдущим кумулятивными значениями загрузки. Это дает нам фактическое количество загрузок за этот период времени.

2.2. Визуализация метрик

Давайте построим простой линейный график зависимости времени от загрузок /мин .

./eb10b70fe92cc43ae7c99536df22f61d.png

Мы видим, что есть некоторые пики, указывающие на большое количество загрузок, которые происходили в нескольких случаях. Из-за линейной шкалы, используемой для оси загрузок , нижние значения отображаются в виде прямой линии.

Давайте изменим ось загрузок , чтобы использовать логарифмическую шкалу (по основанию 10) и построим логарифмический/линейный график.

./8880789b66bacb135453df9a1f819520.png

Теперь мы на самом деле начинаем видеть более низкие значения. И они ближе к 100 (+/-). Обратите внимание, что на линейном графике указано среднее значение 703 , так как он также включает пики.

Если бы мы исключили пики как аберрации, мы могли бы сделать вывод из нашего эксперимента, используя логарифмический/линейный график:

  • средняя загрузка/мин составляет порядка 100 с

3. Мониторинг производительности вызова функции

Поняв, как получить простую метрику и проанализировать ее из предыдущего примера, давайте теперь применим ее к простому методу Java — isPrimeNumber :

private static boolean isPrimeNumber(long number) {
for (long i = 2; i <= number / 2; i++) {
if (number % i == 0)
return false;
}
return true;
}

При использовании SPF4J есть два способа сбора метрик. Давайте рассмотрим их в следующем разделе.

4. Установка и конфигурация

4.1. Настройка Maven

SPF4J предоставляет нам много разных библиотек для разных целей, но для нашего простого примера нам нужно всего несколько.

Основная библиотека — это spf4j-core , которая предоставляет нам большинство необходимых функций.

Давайте добавим это как зависимость Maven:

<dependency>
<groupId>org.spf4j</groupId>
<artifactId>spf4j-core</artifactId>
<version>8.6.10</version>
</dependency>

Есть более подходящая библиотека для мониторинга производительности — spf4j-aspects , которая использует AspectJ .

Мы рассмотрим это в нашем примере, так что давайте добавим и это:

<dependency>
<groupId>org.spf4j</groupId>
<artifactId>spf4j-aspects</artifactId>
<version>8.6.10</version>
</dependency>

И, наконец, SPF4J также поставляется с простым пользовательским интерфейсом, весьма полезным для визуализации данных, поэтому давайте также добавим spf4j-ui :

<dependency>
<groupId>org.spf4j</groupId>
<artifactId>spf4j-ui</artifactId>
<version>8.6.10</version>
</dependency>

4.2. Конфигурация выходных файлов

Инфраструктура SPF4J записывает данные в базу данных временных рядов (TSDB) и при необходимости также может записывать в текстовый файл.

Давайте настроим их обоих и установим системное свойство spf4j.perf.ms.config :

public static void initialize() {
String tsDbFile = System.getProperty("user.dir") + File.separator + "spf4j-performance-monitoring.tsdb2";
String tsTextFile = System.getProperty("user.dir") + File.separator + "spf4j-performance-monitoring.txt";
LOGGER.info("\nTime Series DB (TSDB) : {}\nTime Series text file : {}", tsDbFile, tsTextFile);
System.setProperty("spf4j.perf.ms.config", "TSDB@" + tsDbFile + "," + "TSDB_TXT@" + tsTextFile);
}

4.3. Рекордеры и источники

Основная возможность платформы SPF4J заключается в записи, агрегировании и сохранении метрик, чтобы при их анализе не требовалась постобработка. Для этого используются классы MeasurementRecorder и MeasurementRecorderSource .

Эти два класса предоставляют два разных способа записи метрики. Ключевое отличие заключается в том, что MeasurementRecorder можно вызывать из любого места, тогда как MeasurementRecorderSource используется только с аннотациями.

Фреймворк предоставляет нам класс RecorderFactory для создания экземпляров классов рекордера и исходных классов рекордера для различных типов агрегации:

  • createScalableQuantizedRecorder() и createScalableQuantizedRecorderSource()
  • createScalableCountingRecorder() и createScalableCountingRecorderSource()
  • createScalableMinMaxAvgRecorder() и createScalableMinMaxAvgRecorderSource()
  • createDirectRecorder() и createDirectRecorderSource()

Для нашего примера выберем масштабируемую квантованную агрегацию.

4.4. Создание рекордера

Во-первых, давайте создадим вспомогательный метод для создания экземпляра MeasurementRecorder :

public static MeasurementRecorder getMeasurementRecorder(Object forWhat) {
String unitOfMeasurement = "ms";
int sampleTimeMillis = 1_000;
int factor = 10;
int lowerMagnitude = 0;
int higherMagnitude = 4;
int quantasPerMagnitude = 10;

return RecorderFactory.createScalableQuantizedRecorder(
forWhat, unitOfMeasurement, sampleTimeMillis, factor, lowerMagnitude,
higherMagnitude, quantasPerMagnitude);
}

Давайте посмотрим на различные настройки:

  • unitOfMeasurement — измеряемая единица измерения — для сценария мониторинга производительности это обычно единица времени.
  • sampleTimeMillis — период времени для проведения измерений — или другими словами, как часто проводить измерения
  • фактор – основание логарифмической шкалы, используемой для построения графика измеренного значения.
  • lowerMagnitude — минимальное значение на логарифмической шкале — для логарифмической базы 10, lowerMagnitude = 0 означает 10 в степени 0 = 1
  • highMagnitude — максимальное значение на логарифмической шкале — для логарифмической базы 10, highMagnitude = 4 означает 10 в степени 4 = 10 000
  • quantasPerMagnitude – количество разделов внутри величины – если величина находится в диапазоне от 1000 до 10 000, то quantasPerMagnitude = 10 означает, что диапазон будет разделен на 10 поддиапазонов.

Мы видим, что значения могут быть изменены в соответствии с нашими потребностями. Таким образом, может быть хорошей идеей создать отдельные экземпляры MeasurementRecorder для разных измерений.

4.5. Создание источника

Далее создадим экземпляр MeasurementRecorderSource , используя другой вспомогательный метод:

public static final class RecorderSourceForIsPrimeNumber extends RecorderSourceInstance {
public static final MeasurementRecorderSource INSTANCE;
static {
Object forWhat = App.class + " isPrimeNumber";
String unitOfMeasurement = "ms";
int sampleTimeMillis = 1_000;
int factor = 10;
int lowerMagnitude = 0;
int higherMagnitude = 4;
int quantasPerMagnitude = 10;
INSTANCE = RecorderFactory.createScalableQuantizedRecorderSource(
forWhat, unitOfMeasurement, sampleTimeMillis, factor,
lowerMagnitude, higherMagnitude, quantasPerMagnitude);
}
}

Обратите внимание, что мы использовали те же значения для настроек, что и ранее.

4.6. Создание класса конфигурации

Давайте теперь создадим удобный класс Spf4jConfig и поместим в него все вышеперечисленные методы:

public class Spf4jConfig {
public static void initialize() {
//...
}

public static MeasurementRecorder getMeasurementRecorder(Object forWhat) {
//...
}

public static final class RecorderSourceForIsPrimeNumber extends RecorderSourceInstance {
//...
}
}

4.7. Настройка aop.xml

SPF4J предоставляет нам возможность аннотировать методы измерения и мониторинга производительности. Он использует библиотеку AspectJ , которая позволяет добавлять в существующий код дополнительное поведение, необходимое для мониторинга производительности, без модификации самого кода.

Давайте объединим наш класс и аспект, используя Weaver во время загрузки, и поместим aop.xml в папку META-INF :

<aspectj>
<aspects>
<aspect name="org.spf4j.perf.aspects.PerformanceMonitorAspect" />
</aspects>
<weaver options="-verbose">
<include within="com..*" />
<include within="org.spf4j.perf.aspects.PerformanceMonitorAspect" />
</weaver>
</aspectj>

5. Использование регистратора измерений

Давайте теперь посмотрим, как использовать MeasurementRecorder для записи показателей производительности нашей тестовой функции.

5.1. Запись показателей

Давайте сгенерируем 100 случайных чисел и вызовем метод простой проверки в цикле. Перед этим давайте вызовем наш класс Spf4jConfig , чтобы выполнить инициализацию и создать экземпляр класса MeasureRecorder . Используя этот экземпляр, давайте вызовем метод record() , чтобы сохранить отдельное время, затрачиваемое на 100 вызовов isPrimeNumber() :

Spf4jConfig.initialize();
MeasurementRecorder measurementRecorder = Spf4jConfig
.getMeasurementRecorder(App.class + " isPrimeNumber");
Random random = new Random();
for (int i = 0; i < 100; i++) {
long numberToCheck = random.nextInt(999_999_999 - 100_000_000 + 1) + 100_000_000;
long startTime = System.currentTimeMillis();
boolean isPrime = isPrimeNumber(numberToCheck);
measurementRecorder.record(System.currentTimeMillis() - startTime);
LOGGER.info("{}. {} is prime? {}", i + 1, numberToCheck, isPrime);
}

5.2. Запуск кода

Теперь мы готовы проверить производительность нашей простой функции isPrimeNumber ().

Давайте запустим код и посмотрим на результаты:

Time Series DB (TSDB) : E:\Projects\spf4j-core-app\spf4j-performance-monitoring.tsdb2
Time Series text file : E:\Projects\spf4j-core-app\spf4j-performance-monitoring.txt
1. 406704834 is prime? false
...
9. 507639059 is prime? true
...
20. 557385397 is prime? true
...
26. 152042771 is prime? true
...
100. 841159884 is prime? false

5.3. Просмотр результатов

Запустим пользовательский интерфейс SPF4J, выполнив команду из папки проекта:

java -jar target/dependency-jars/spf4j-ui-8.6.9.jar

Это вызовет настольное приложение с пользовательским интерфейсом. Затем в меню выберите « Файл » > « Открыть » . После этого воспользуемся окном просмотра, чтобы найти файл spf4j-performance-monitoring.tsdb2 и открыть его.

Теперь мы видим, как открывается новое окно с древовидным представлением, содержащим имя нашего файла и дочерний элемент. Давайте нажмем на дочерний элемент, а затем на кнопку Plot над ним.

Это создаст серию графиков.

Первый график, распределение измерений , представляет собой вариант логарифмически-линейного графика, который мы видели ранее. Этот график дополнительно показывает тепловую карту на основе подсчета.

./18420b76fe0b5dc9a964718f8fd9ecf7.png

На втором графике показаны агрегированные данные, такие как минимум, максимум и среднее значение:

./a81f2edd6349ce8c973263121f910cb0.png

И последний график показывает количество измерений в зависимости от времени:

./827c63a13f4e21542afec1364bfbdd28.png

6. Использование MeasurementRecorderSource

В предыдущем разделе нам пришлось написать дополнительный код для нашей функциональности для записи измерений. В этом разделе давайте воспользуемся другим подходом, чтобы избежать этого.

6.1. Запись показателей

Во-первых, мы удалим лишний код, добавленный для сбора и записи метрик:

Spf4jConfig.initialize();
Random random = new Random();
for (int i = 0; i < 50; i++) {
long numberToCheck = random.nextInt(999_999_999 - 100_000_000 + 1) + 100_000_000;
isPrimeNumber(numberToCheck);
}

Вместо всего этого шаблонного кода давайте аннотируем метод isPrimeNumber() с помощью @PerformanceMonitor :

@PerformanceMonitor(
warnThresholdMillis = 1,
errorThresholdMillis = 100,
recorderSource = Spf4jConfig.RecorderSourceForIsPrimeNumber.class)
private static boolean isPrimeNumber(long number) {
//...
}

Давайте посмотрим на различные настройки:

  • warnThresholdMillis — максимально допустимое время работы метода без предупреждения.

  • errorThresholdMillis — максимально допустимое время работы метода без сообщения об ошибке. **

    **

  • recorderSource — экземпляр MeasurementRecorderSource

6.2. Запуск кода

Давайте сначала выполним сборку Maven, а затем выполним код, передав агент Java:

java -javaagent:target/dependency-jars/aspectjweaver-1.8.13.jar -jar target/spf4j-aspects-app.jar

Мы видим результаты:

Time Series DB (TSDB) : E:\Projects\spf4j-aspects-app\spf4j-performance-monitoring.tsdb2
Time Series text file : E:\Projects\spf4j-aspects-app\spf4j-performance-monitoring.txt

[DEBUG] Execution time 0 ms for execution(App.isPrimeNumber(..)), arguments [555031768]
...
[ERROR] Execution time 2826 ms for execution(App.isPrimeNumber(..)) exceeds error threshold of 100 ms, arguments [464032213]
...

Мы видим, что платформа SPF4J регистрирует время, затраченное на каждый вызов метода. И всякий раз, когда он превышает значение errorThresholdMillis , равное 100 мс, он регистрирует это как ошибку. Аргумент, переданный методу, также регистрируется.

6.3. Просмотр результатов

Мы можем просматривать результаты так же, как и ранее, используя пользовательский интерфейс SPF4J, поэтому мы можем обратиться к предыдущему разделу.

7. Заключение

В этой статье мы рассказали об основных концепциях сбора и визуализации метрик.

Затем мы поняли возможности мониторинга производительности фреймворка SPF4J на простом примере. Мы также использовали встроенный инструмент пользовательского интерфейса для визуализации данных.

Как всегда, примеры из этой статьи доступны на GitHub .