Не будем долго останавливаться на том, что такое Prometheus, об этом уже и так много сказано. Скажем коротко: Prometheus - система мониторинга самых различных устройств и приложений, самостоятельно сканирующая целевые объекты. В нашем случае, целевой объект - тестовый стенд с устройствами различных архитектур под управлением ОС "Нейтрино".

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

Нам же требовался достаточно простой текстовый экспортёр, который бы стабильно работал в условиях использования встраиваемых систем с различными архитектурами процессора, и учитывал бы особенности и ограничения ОС.

Так и появился данный проект, доступный в нашем публичном репозитории, реализованный на С++11.

Преимущества использования Prometheus для задач мониторинга

Внедрение профессионального инструмента мониторинга позволило нам отказаться от использования БД, ведь он сам управляет хранением данных и устареванием метрик. Нативная интеграция с Grafana позволила заменить комплексные запросы к БД на простые запросы к Prometheus, легко настраиваемые через интерфейс.

Таблица в Grafana - Состояние стендов
Таблица в Grafana - Состояние стендов
Упрощение запросов в таблице после перехода на Prometheus
Упрощение запросов в таблице после перехода на Prometheus

Мощный язык запросов PromQL и комплексные метрики экспортёра также позволили расширить наполнение панели Grafana: теперь мы можем четко отслеживать состояние соединения наших стендов:

Визуализация метрик HTTP-соединения
Визуализация метрик HTTP-соединения
Что происходит на графиках?

На графиках в первом столбце отображается гистограмма интервалов между HTTP-запросами. Интервал запросов сервера - 45с, поэтому большая часть запросов попадает во временной ряд <= 50с.

На графиках второго столбца рассчитывается среднее время интервала по специальным полям гистограммы: _sum / _count .

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

Начало работы

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

В экспортёре уже есть несколько метрик:

Counter<int>   http_requests_total;
Histogram<int> http_req_duration_seconds;

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

Для вышеописанных целей инкапсуляции и создан оберточный класс MetricsHandler. Соответственно, любая метрика, не связанная с самим экспортёром, должна быть членом данного класса.

Посмотрим на metricshandler.cpp:

#include "metricshandler.h"

#include "logger/logger.h"
#include "collector/collector.h"


std::string MetricsHandler::generate_text_data()
{
    std::ostringstream ss;

    return ss.str();
}


void MetricsHandler::set_metrics()
{}


void MetricsHandler::modify_metrics()
{}

Так выглядит класс без каких-либо пользовательских метрик, и нам предстоит его дополнить, объявив в классе новый объект одного из классов - наследников класса Metric, и дополнив функции соответствующими вызовами функций нашей метрики.

Это будет очень просто! Но перед этим пару слов о том, какие метрики существуют в Prometheus и как они реализованы в экспортёре.

Метрики

Если вам не нужен обзор метрик - переходите сразу к их реализации.

Виды метрик

В Prometheus есть 4 вида метрик:

  • Счетчик (Counter)

  • Измеритель (Gauge)

  • Гистограмма (Histogram)

  • Сводка (Summary)

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

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

Гистограмма - комплексная метрика, хранит сумму всех значений и количество всех измерений, а также хранит выборки значений в сегментах (buckets), разделенных по параметру le - less or equal.

Подробнее про гистограммы можно прочитать тут.

Сводка - похожа на гистограмму. Тоже хранит сумму всех значений и кол-во измерений, но выборки производит по квантилям.

Сводку сложно реализовать из-за особенностей реализации квантилей (тут), которые требуют вычислений на стороне клиента (в отличие от гистограммы), а также она очень схожа по функциональности с гистограммой, поэтому она пока не получила реализации. 

Метки

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

http_requests_total{code="200"} 1
http_requests_total{code="400"} 0

Есть особые метки, используемые гистограммой: {le=""} и сводкой: {quantile=""}. Их стоит избегать. 

Реализация

Для счетчиков, измерителей и гистограммы создан соответствующий шаблонный класс, позволяющий объявить метрику любого численного типа. Все классы наследуются от абстрактного класса Metric.

Пример создания объекта метрики: 

Gauge<double> MetricsHandler::cpu_load( "cpu_load", "Total CPU Load" );

Первый аргумент - реальное название метрики, которое будет передано Prometheus, второй - строка # HELP, которая должна передаваться в Prometheus вместе с текстовой формой метрики.

Рассмотрим функции set_metric() и modify_metric(). Первая функция используется для инициализации метрики или одной из ее меток. Вторая - для последующего изменения существующих значений (по умолчанию: +=, но можно использовать  = с передачей флага MetricFlag::RESET).

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

http_requests_total.set_metric( {{"method", "GET"}, {"code", "200"}}, 0 );
http_requests_total.modify_metric( {{"method", "GET"}, {"code", "200"}}, 1 );

За формирование текстовой формы метрик, которая будет передана Prometheus, отвечает функция generate_text_data().

Добавляем первую метрику

Теперь мы готовы дополнить файл metricshandler.cpp метрикой, которая будет хранить и передавать в требуемой текстовой форме информацию о загрузке ЦП в процентах:

#include "metricshandler.h"

#include "logger/logger.h"
#include "collector/collector.h"


Gauge<double>   MetricsHandler::cpu_load( "cpu_load", "Total CPU Load" );


std::string MetricsHandler::generate_text_data()
{
    std::ostringstream ss;

    ss << cpu_load.generate_text_data();

    return ss.str();
}


void MetricsHandler::set_metrics()
{
    cpu_load.set_metric( 0 );
}


void MetricsHandler::modify_metrics()
{
    double cpu_load_value = 0.0;

    // cpu_load_value = Collector::get_cpu_load();

    cpu_load.modify_metric( cpu_load_value,  MetricFlag::RESET );
}

Не забудем объявить метрику в metricshandler.h:

...

class MetricsHandler
{
public:
    ...
private:
    MetricsHandler() = delete;

    static Gauge<double> cpu_load;
};

...

Вот и все, метрика создана! Можно запустить программу и увидеть в данных Prometheus, полученных от экспортёра, наше значение. Ниже инструкция, как это можно сделать:

1) Настраиваем конфигурационный файл сервера Prometheus:

Настраиваем конфигурацию
Настраиваем конфигурацию

2) Запускаем на целевом устройстве экспортёр, повышаем вербозность, указываем порт, название лога и таймаут на получение запроса 60с:

Запуск экспортёра на ВМ
Запуск экспортёра на ВМ

3) Теперь, если все пошло по плану и сервер может получить доступ к указанному адресу, то в разделе Target Health в веб-конфигураторе мы увидим, что состояние целевого устройства - UP

Состояние ВМ - UP
Состояние ВМ - UP

Перейдем по указанному endpoint, чтобы увидеть полученные данные:

# HELP http_requests_total Total HTTP requests
# TYPE http_requests_total counter
http_requests_total{code="200",method="GET"} 1
http_requests_total{code="400",method="GET"} 0

# HELP http_request_duration_seconds A histogram of the request duration
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="12"} 0
http_request_duration_seconds_bucket{le="18"} 0
http_request_duration_seconds_bucket{le="28"} 0
http_request_duration_seconds_bucket{le="39"} 0
http_request_duration_seconds_bucket{le="50"} 0
http_request_duration_seconds_bucket{le="+Inf"} 0
http_request_duration_seconds_sum 0
http_request_duration_seconds_count 0

# HELP cpu_load Total CPU Load
# TYPE cpu_load gauge
cpu_load 0

Заключительная часть

Конечно, в метрике, которая всегда возвращает 0, мало смысла, поэтому осталась еще последняя часть работы, а именно обновление ее значения. Получением данных о системе или о запущенных в ней программах занимается класс Collector. Реализуем функцию get_cpu_load(), чтобы можно было раскомментировать строку 30 metricshandler.cpp (Реализация оставлена за кадром в виду громоздкости функции, но с ней можно ознакомиться в коде проекта).

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

График cpu_load
График cpu_load

А также мы можем легко интегрировать результаты в систему визуализации Grafana, как это сделано у нас для отслеживания состояния тестовых стендов:

Интеграция cpu_load в Grafana
Интеграция cpu_load в Grafana

В данной статье мы научились получать простую метрику с устройства под управлением ОС "Нейтрино". Если вас интересует более глубокое погружение в тему создания собственных метрик, можете ознакомиться с соответствующими правилами и рекомендациями в официальной документации.

Спасибо за внимание!

Подписывайтесь на наш канал, чтобы быть в курсе свежих новостей

Комментарии (0)