«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. Визуализация метрик

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

Downloads per Minute Chart

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

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

Теперь мы начинаем видеть более низкие значения. И они ближе к 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", "[email protected]" + tsDbFile + "," + "[email protected]" + tsTextFile);
}

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

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

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

«Фреймворк предоставляет нам класс RecorderFactory для создания экземпляров записывающего устройства и исходных классов записывающего устройства для различных типов агрегирования: 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, более высокая магнитуда — максимальное значение на логарифмической шкале — для логарифмической базы 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 и поместим в него все вышеуказанные методы:

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

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

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

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

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

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

5. Использование MeasurementRecorder

<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>

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

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

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

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

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);
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

6.1. Запись метрик

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

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

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);
}

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

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

warnThresholdMillis — максимальное время, разрешенное для выполнения метода без предупреждающего сообщения. errorThresholdMillis — максимальное время, разрешенное для выполнения метода без сообщения об ошибке. экземпляр MeasurementRecorderSource

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

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

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

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

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

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]
...

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

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

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

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

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

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

«