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


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

Что происходит на графиках?
На графиках в первом столбце отображается гистограмма интервалов между 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

Перейдем по указанному 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:

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

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