
Не будем долго останавливаться на том, что такое 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, как это сделано у нас для отслеживания состояния тестовых стендов:

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

