Быстрый старт#

Ниже приведен пример выставления метрик в формате Prometheus из прикладного приложения на Spring Boot.

1. Зависимости#

Добавить в Apache Maven зависимости на actuator и micrometer:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

2. Настройки#

В application.properties указать порт actuator (значение по умолчанию 8080) и фильтр включенных endpoint.

Пример application.properties

management.server.port=8081
management.endpoints.web.exposure.include=*

3. Создание прикладных метрик в коде#

Создать прикладные метрики в проекте с помощью micrometer. Подробнее см.: https://www.baeldung.com/micrometer. Если будут выставлены метрики со значением NaN, то есть "value": "NaN", они будут собраны, но валидацию не пройдут и не будут направлены в хранилище.

Ниже приведены примеры различных типов метрик.

Пример метрики Counter

@RestController
public class CounterController {
    private static final String COUNTER_WITH_TAG_NAME = "simple.counterWithTags";
    private static final String COUNTER_WITH_TAG_DESCRIPTION = "Just a simple counter with tags";
    private static final String COUNTER_WITH_USER_TAG_NAME = "simple.counterWithUserTag";
    private static final String TAG_NAME_1 = "terbank";
    private static final String TAG_NAME_2 = "vsp";
    private static final String TAG_USER = "userText";
    private static final String TAG_RN = "rn";
    private static final String RN = "unimon-sandbox-micrometer";
    private static final String TAG_UNIMON_UD = "unimonId";
    private static final String UNIMON_ID = "unimon-sandbox-micrometer";

    @Autowired
    MeterRegistry meterRegistry;

    private Counter simpleCounter;

    private Counter simpleCounterWithTags;

    private Counter simpleCounterWithTags2;

    private Counter simpleCounterWithUserTag;

    /**
     * Инициальзация метрик типа Counter
     */
    @PostConstruct
    public void init() {
        simpleCounter = Counter.builder("simple.counter")
                .description("Just a simple counter")
                .tag(TAG_RN, RN)
                .tag(TAG_UNIMON_UD, UNIMON_ID)
                .register(meterRegistry);

        simpleCounterWithTags = Counter.builder(COUNTER_WITH_TAG_NAME)
                .description(COUNTER_WITH_TAG_DESCRIPTION)
                .tag(TAG_NAME_1, "sib")
                .tag(TAG_NAME_2, "111")
                .tag(TAG_RN, RN)
                .tag(TAG_UNIMON_UD, UNIMON_ID)
                .register(meterRegistry);

        simpleCounterWithTags2 = Counter.builder(COUNTER_WITH_TAG_NAME)
                .description(COUNTER_WITH_TAG_DESCRIPTION)
                .tag(TAG_NAME_1, "msk")
                .tag(TAG_NAME_2, "111")
                .tag(TAG_RN, RN)
                .tag(TAG_UNIMON_UD, UNIMON_ID)
                .register(meterRegistry);
    }

    /**
     * Увеличение счетчика на 1
     */
    @GetMapping("/counter")
    @ResponseBody
    public String incrementCounter() {
        simpleCounter.increment();
        return String.format("Counter has been increases\nCurrent value: %s", simpleCounter.count());
    }

    /**
     * Увеличение на 1 счетчика с разрезом фиксации "terbank" и значением "sib"
     */
    @GetMapping("/counterWithTags/terbank/sib")
    @ResponseBody
    public String incrementCounterTags1() {
        simpleCounterWithTags.increment(1);
        return String.format("Counter has been increases\nCurrent value: %s", simpleCounterWithTags.count());
    }

    /**
     * Увеличение на 2 счетчика с разрезом фиксации "terbank" и значением "msk"
     */
    @GetMapping("/counterWithTags/terbank/msk")
    @ResponseBody
    public String incrementCounterTags2() {
        simpleCounterWithTags2.increment(2);
        return String.format("Counter has been increases\nCurrent value: %s", simpleCounterWithTags2.count());
    }

    @GetMapping("/counterWithUserTagValue")
    @ResponseBody
    public String incrementCounterWithUserTag(@RequestParam String val) {
        simpleCounterWithUserTag = Counter.builder(COUNTER_WITH_USER_TAG_NAME)
                .tag(TAG_USER, val)
                .tag(TAG_UNIMON_UD, UNIMON_ID)
                .register(meterRegistry);
        simpleCounterWithUserTag.increment(1);
        return String.format("Counter has been increases\nCurrent value: %s", simpleCounterWithUserTag.count());
    }
}

Пример метрики DistributionSummary

@RestController
public class DistributionSummaryController {
    @Autowired
    MeterRegistry meterRegistry;

    private DistributionSummary simpleSummary;
    private static final String TAG_RN = "rn";
    private static final String RN = "unimon-sandbox-micrometer";
    private static final String TAG_UNIMON_UD = "unimonId";
    private static final String UNIMON_ID = "unimon-sandbox-micrometer";

    /**
     * Инициальзация метрик типа DistributionSummary
     */
    @PostConstruct
    public void init() {
        simpleSummary = DistributionSummary.builder("simple.distributionSummary")
                .description("Just a simple distributionSummary")
                .publishPercentiles(0.5, 0.75, 0.9)
                .tag(TAG_RN, RN)
                .tag(TAG_UNIMON_UD, UNIMON_ID)
                .register(meterRegistry);
    }

    /**
     * Добавление случайного значения в множество
     */
    @GetMapping("/distributionSummary")
    @ResponseBody
    public String addRecord() {
        simpleSummary.record(Math.random());
        return String.format("value added to the distributionSummary \n Current count: %s", simpleSummary.count());
    }
}

Пример метрики Gauge

@RestController
public class GaugeController {
    private static final String GAUGE_NAME = "simple.gauge";
    private static final String GAUGE_DESCRIPTION = "Just a simple gauge";
    private static final String GAUGE_WITH_TAG_NAME = "simple.gaugeWithTags";
    private static final String GAUGE_WITH_TAG_DESCRIPTION = "Just a simple gauge with tags";
    private static final String TAG_NAME_1 = "listName";
    private static final String TAG_NAME_2 = "returnValue";
    private static final String TAG_RN = "rn";
    private static final String RN = "unimon-sandbox-micrometer";
    private static final String TAG_UNIMON_UD = "unimonId";
    private static final String UNIMON_ID = "unimon-sandbox-micrometer";

    @Autowired
    MeterRegistry meterRegistry;

    private Gauge simpleGauge;
    private Gauge simpleGaugeWithTags;
    private Gauge simpleGaugeWithTags2;
    private ServiceAvailabilityState availabilityState = new ServiceAvailabilityState();
    private List<Integer> list1 = new ArrayList<>(4);
    private List<Integer> list2 = new ArrayList<>(4);

    /**
     * Инициальзация метрик типа Gauge
     */
    @PostConstruct
    public void init() {
        simpleGauge = Gauge.builder(GAUGE_NAME, availabilityState, ServiceAvailabilityState::getStateFlag)
                .description(GAUGE_DESCRIPTION)
                .tag(TAG_RN, RN)
                .tag(TAG_UNIMON_UD, UNIMON_ID)
                .register(meterRegistry);

        simpleGaugeWithTags = Gauge.builder(GAUGE_WITH_TAG_NAME, list1, List::size)
                .description(GAUGE_WITH_TAG_DESCRIPTION)
                .tag(TAG_NAME_1, "list1")
                .tag(TAG_NAME_2, "size")
                .tag(TAG_RN, RN)
                .tag(TAG_UNIMON_UD, UNIMON_ID)
                .register(meterRegistry);

        simpleGaugeWithTags2 = Gauge.builder(GAUGE_WITH_TAG_NAME, list2, List::size)
                .description(GAUGE_WITH_TAG_DESCRIPTION)
                .tag(TAG_NAME_1, "list2")
                .tag(TAG_NAME_2, "size")
                .tag(TAG_RN, RN)
                .tag(TAG_UNIMON_UD, UNIMON_ID)
                .register(meterRegistry);
    }

    /**
     * Установка значения метрики
     * @param val - значение метрики
     */
    @GetMapping("/gauge")
    @ResponseBody
    public String setGauge(@RequestParam String val) {
        try {
            availabilityState.setStateFlag(Integer.valueOf(val));
        } catch (NumberFormatException e) {
            return "Integer value should be provided to set this Gauge";
        }
        return String.format("Gauge has been set\nCurrent value: %s", simpleGauge.value());
    }

    /**
     * Добавляет к list1 значение, при этом изменяется значение метрики (размер списка) с разрезом ListName = list1
     */
    @GetMapping("/gaugeWithTags/listName/list1")
    @ResponseBody
    public String addElementList1() {
        list1.add(1);
        return String.format("Gauge has been updated\nCurrent value: %s", simpleGaugeWithTags.value());
    }

    /**
     * Добавляет к list2 значение, при этом изменяется значение метрики (размер списка) с разрезом ListName = list2
     */
    @GetMapping("/gaugeWithTags/listName/list2")
    @ResponseBody
    public String addElementList2() {
        list2.add(1);
        return String.format("Gauge has been updated\nCurrent value: %s", simpleGaugeWithTags2.value());
    }

}

class ServiceAvailabilityState {

    Integer stateFlag = 0;

    public Integer getStateFlag() {
        return stateFlag;
    }

    public void setStateFlag(Integer stateFlag) {
        this.stateFlag = stateFlag;
    }
}

Пример метрики Timer

@RestController
public class TimerController {

    @Autowired
    MeterRegistry meterRegistry;

    private Timer simpleTimer;
    private Timer simpleTimerWithPercentile;
    private static final String TAG_RN = "rn";
    private static final String RN = "unimon-sandbox-micrometer";
    private static final String TAG_UNIMON_UD = "unimonId";
    private static final String UNIMON_ID = "unimon-sandbox-micrometer";

    /**
     * Инициальзация метрик типа Timer
     */
    @PostConstruct
    public void init() {

        simpleTimer = Timer.builder("simple.timer")
                .description("Just a simple timer")
                .tag(TAG_RN, RN)
                .tag(TAG_UNIMON_UD, UNIMON_ID)
                .register(meterRegistry);
        simpleTimerWithPercentile = Timer.builder("simple.timerWithPercentile")
                .description("Just a simple timer with percentile")
                .tag(TAG_RN, RN)
                .tag(TAG_UNIMON_UD, UNIMON_ID)
                .publishPercentiles(0.5, 0.9, 0.99)
                .register(meterRegistry);
    }

    /**
     * Добавляет в метрику типа Timer значение длительности вызова sleep()
     */
    @GetMapping("/timer")
    @ResponseBody
    public String runTimer() {
        simpleTimer.record(3000, TimeUnit.MILLISECONDS);
        return String.format("Timer has been run\nCurrent value: %s \nCurrentCount: %s",
                simpleTimer.totalTime(TimeUnit.MILLISECONDS), simpleTimer.count());
    }

    /**
     * Добавляет в метрику типа Timer с включенной публикацией процентилей, значение длительности вызова sleep()
     */
    @GetMapping("/timerWithPercentile")
    @ResponseBody
    public String runTimerWithPercentile() {
        simpleTimerWithPercentile.record(() -> {
            try {
                TimeUnit.SECONDS.sleep((int) (Math.random() * 4));
            } catch (InterruptedException ignored) { }
        });
        return String.format("Timer has been run\nCurrent value: %s  \nCurrentCount: %s",
                simpleTimerWithPercentile.totalTime(TimeUnit.MILLISECONDS), simpleTimerWithPercentile.count());
    }

}

4. Настройки для подключения Agent#

Выполнить настройки из пункта «Подключение и конфигурирование». Просмотреть метрики, выставленные приложением в формате Prometheus можно по ссылке: http://localhost:8081/actuator/prometheus.