Мониторинг C++ приложений: от сбора метрик до настройки алертов
Современные C++ приложения работают в высоконагруженных средах: от серверов баз данных до игровых движков и финансовых систем. Без мониторинга вы рискуете пропустить утечку памяти, падение производительности или критический сбой. В этой статье мы разберем, как организовать полноценный мониторинг C++ приложений: какие метрики собирать, как настроить алерты и какие инструменты использовать. Вы получите готовые примеры кода и практические советы.
1. Зачем нужен мониторинг C++ приложений?
Мониторинг — это не просто «посмотреть логи». Это система, которая позволяет:
- Обнаруживать проблемы на ранней стадии — до того, как они повлияют на пользователей.
- Анализировать производительность — CPU, память, диски, сеть.
- Отслеживать бизнес-метрики — количество обработанных запросов, время ответа.
- Автоматизировать оповещения — алерты при превышении порогов.
Без мониторинга вы работаете вслепую. Особенно это критично для C++ приложений, где ошибка может привести к segfault или неопределенному поведению.
2. Ключевые метрики для C++ приложений
Метрики делятся на три группы: системные, прикладные и бизнес-метрики. Рассмотрим каждую.
2.1 Системные метрики
Эти метрики показывают, как приложение использует ресурсы ОС:
- CPU usage — загрузка процессора (user, system, idle).
- Memory usage — RSS, VMS, утечки памяти.
- Disk I/O — чтение/запись, задержки.
- Network I/O — входящий/исходящий трафик, ошибки.
- File descriptors — количество открытых файлов.
Пример сбора метрик через /proc/self/status (Linux):
#include <fstream>
#include <string>
#include <unordered_map>
std::unordered_map<std::string, std::string> parseProcStatus() {
std::ifstream file("/proc/self/status");
std::unordered_map<std::string, std::string> metrics;
std::string line;
while (std::getline(file, line)) {
auto pos = line.find(':');
if (pos != std::string::npos) {
std::string key = line.substr(0, pos);
std::string value = line.substr(pos + 2);
metrics[key] = value;
}
}
return metrics;
}
// Пример использования:
auto status = parseProcStatus();
std::cout << "VM size: " << status["VmSize"] << std::endl;
2.2 Прикладные метрики
Они специфичны для вашего приложения:
- Время выполнения функций — замеры с помощью
std::chrono. - Количество ошибок — исключения, возвращаемые коды.
- Состояние потоков — количество активных/ожидающих потоков.
- Размер очередей — например, очередь задач.
Пример замера времени:
#include <chrono>
#include <iostream>
class Timer {
std::chrono::high_resolution_clock::time_point start;
public:
Timer() : start(std::chrono::high_resolution_clock::now()) {}
~Timer() {
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "Execution time: " << duration.count() << " us\n";
}
};
void heavyFunction() {
Timer t;
// ... ваш код
}
2.3 Бизнес-метрики
Они отражают ценность для пользователя:
- Количество обработанных запросов в секунду (RPS).
- Время ответа (p50, p95, p99).
- Количество активных сессий.
- Процент успешных операций.
3. Инструменты для сбора и визуализации метрик
Для C++ приложений популярны следующие связки:
- Prometheus + Grafana — стандарт де-факто. Prometheus собирает метрики, Grafana отображает.
- InfluxDB + Telegraf — для временных рядов.
- ELK Stack (Elasticsearch, Logstash, Kibana) — если нужен анализ логов.
- OpenTelemetry — современный стандарт для трассировки и метрик.
Пример интеграции с Prometheus через HTTP-эндпоинт (используем библиотеку prometheus-cpp):
#include <prometheus/exposer.h>
#include <prometheus/registry.h>
#include <prometheus/counter.h>
#include <prometheus/gauge.h>
int main() {
// Создаем HTTP-сервер на порту 8080
prometheus::Exposer exposer("0.0.0.0:8080");
// Создаем реестр метрик
auto registry = std::make_shared<prometheus::Registry>();
// Создаем счетчик запросов
auto& counter_family = prometheus::BuildCounter()
.Name("http_requests_total")
.Help("Total number of HTTP requests")
.Register(*registry);
auto& counter = counter_family.Add({{"method", "GET"}, {"endpoint", "/api"}});
// Создаем датчик памяти
auto& gauge_family = prometheus::BuildGauge()
.Name("memory_usage_bytes")
.Help("Current memory usage")
.Register(*registry);
auto& gauge = gauge_family.Add({});
// Регистрируем метрики
exposer.RegisterCollectable(registry);
// Имитация работы
while (true) {
counter.Increment();
gauge.Set(1024 * 1024 * 256); // 256 MB
std::this_thread::sleep_for(std::chrono::