====== Метрики приложения (Prometheus) ====== ===== Работа с Prometheus ===== ==== Установка ==== ==== Использование ==== === Варианты тегов в легенде === {{:linux:label.png?direct&400|}} === Дефотное значение при отсутствии данных === sum() OR vector(0) или, в случае с VictoriaMetrics sum() OR default 0 === === ===== DeepInfo ===== [[https://habr.com/ru/companies/tochka/articles/683608/|Хороший цикл статей]]\\ ==== Общее ==== Метрики это по сути попытка инженерно-математическими методами сжать большой массив данных до чего-то наглядного, в отличии от логов, которые пишутся как есть\\ Парсить логи для графиков тоже может быть приемлемо, но до определенного момента\\
:!: Метрики vs Логи **Логи** - это про точность мы собираем информацию на каждое событие. Можно агрегировать и детализировать до каждого события, но логировать каждый чих плохо масштабируется\\ **Метрики** это сразу общая картина о состоянии приложения. Мы собираем не все детали, а только общую выжимку. Картину мы так же получаем но в детали провалится нельзя, зато хорошо масштабируется и быстро работает, если нужны детали то можно решать точечно\\
:!: Push vs Pull **Push** - принцип такой же как с логами или БД: пришло событие, пишем в хранилище, можно пачками, способ простой\\ **Pull** - наоборот, приложения хранят в памяти данные, кто то периодически приходит и собирает их, например по HTTP.\\ Сложнее реализовать но проще отлаживать, у каждого приложения есть свой endpoint и видно что там происходит\\
:!: Time Series Database (TSDB) Особенности такой БД это обработка временных рядов, т.е. однотипных измерений во времени.\\ БД такого типа оптимизируют хранение какого то числа, которое записано через равные интервалы времени\\ Например чтобы хранить ежедневную температуру нам нужно хранить только одно число и больше ничего. т.е TSDB нужны чтобы сохранять время и одно число, привязанное к этому времени\\ Специфика:\\ * реляционку умеют по минимуму, если SQL то ограниченный или вовсе нет * типы хранимых данных урезаны * оптимизация на постоянную, непрерывную запись Раз на sql это все не похоже то и поддерживать сложный язык запросов нет необходимости, и часто делают свой, метрико-ориентированный простой язык\\
==== OpenMetrics ==== "Превращение формата Prometheus в стандарт" (с)\\ Проект по стандартизации формата данных и запросов Prometheus\\ по сути тот же прометеус\\
:!: Info Представляет формат представления Prometheus с некоторыми улучшениями\\ Все базовые спецификации такие же как у Prometheus, включая использование PromQL, OpenMetrics обеспечивает некоторые улучшение (поддержка Push и Pull, у прометеуса только Pull; по умолчанию секунды вместо миллисекунд и еще несколько)\\ Ориентирован только на метрики. Просто стандартизирует формат представления, посредствам которого данные метрики должны передаваться по сети\\ "Клиент Prometheus Python является эталонной реализацией и внутренне использует модель данных OpenMetrics. Prometheus при парсинге будет отдавать предпочтение OpenMetrics"\\ похоже что добавляет метку "_created" к метрикам, но это не точно, что то везде упоминается, пока точно неясно что именно значит\\ И про синтаксический анализатор Python тоже гругом говорится, похоже что OpenMetrics как таковой это же стандарт, а обеспечивает его некая python надстройка над клиентом Prometheus, но это наверно на принимающей строне, отправляющая это наше приложение, ему надо в нужном формате отправлять данные, в формате OpenMetrics\\
==== OpenTelemetry ==== OpenTelemetry насколько понял это более комплексное решение, набор стандартов, API, SDK и библиотек, для создания, сбора и управления данными телеметрии (журналы, метрики и трассировки)\\
:!: Info Ну да, в первом только метрики, тут кроме метрик еще журналы, трассировки, кстати в качестве метрик поддерживает OpenMetrics. Либо прямо использует формат OpenMetrics для передачи метрик\\ Имеет клиентские библиотеки, встраивается в код приложения\\ Основа для комплексного стека наблюдения со спецификациями для обработки журналов, трассировок и метрик\\
==== Prometheus ==== Формат представления метрик текстом\\ Большой шаг на пути к стандартизации формата представления метрик, особенно для систем мониторинга\\ Включает в себя:\\ * сервер - хранился и сборщик метрик * формат данных * язык запросов (PromQL)
:!: Scrape метрик Приложение выставляет HTTP страницу с метриками, сервер периодически делает GET запросы по настроенным ендпоинтам, ответы сохраняет с текущим timestamp\\ Идея в том что прометеус собирает срез во времени, а потом его средствами мы уже вычисляем изменения\\ **Безопасность**\\ По умолчанию ходит без аутентификации, варианты обезопашевания:\\ * настройка аутентификации средствами прометея * хостить ендпоинт на отдельном порту * файерволл * whitelist на уровне приложения **Альтернативные способы скрапинга**\\ Если например приложение не запущено постоянно и/или не имеет HTTP ендпоинта, есть промежуточные средства для сбора и передачи метрик:\\ * Pushgateway - компонент прометеус, который может собирать метрики затем передавать их серверу * Telegraf - в стоке есть output-плагин для публикации в HTTP endpoint * StatsD Exporter - еще какая то тема, аналогично
:!: Prometheus - Формат данных # HELP http_requests_total Requests made to public API # TYPE http_requests_total counter http_requests_total{method="POST", url="/messages"} 1 http_requests_total{method="GET", url="/messages"} 3 http_requests_total{method="POST", url="/login"} 2 Каждая метрика содержит поля:\\ * HELP описание * TYPE тип метрики * название * набор key-value лейблов (теги) * значение метрики (double) * после сборка добавляется еще timestamp Хранение: имя метрики на самом деле такой же тег, со стандартным именем "__name__"\\ Все теги вместе описывают собой **time series** (временной ряд), т.е. это как бы имя таблицы, составленное из всех **key-value**. В этом ряду лежат только **время и одна цифра** значение метрики\\ Из примера выше у нас одна метрика но три таблицы (GET /message; POST /message; POST /login), в каждую таблицу, каждые 30 сек (итерация скрабинга) пишется одно число (никаких int-ов, никаких строк, ничего, только число)\\ **Кардинальность**\\ Каждое новое значение тега это уже новый временной ряд, т.е. новая таблица\\ Например метрика о HTTP запросах, перемножаем все возможные значения тегов: 2 HTTP глагола, 7 урлов, 5 реплик сервиса, 3 вида ответов, 4 браузера, итого 840 временных рядов, т.е. это как 840 таблиц в sql\\ В целом TSDB справляется и с десятками миллионов рядов, но комбинаторный взрыв устроить очень легко\\
:!: Типы метрик В данном случае, понятие "тип метрики" достаточно условно, под капотом разницы нет и все хранится в double\\ Тип скорее нужен для программистов и библиотек, с помощью которых метрики пишутся и анализируются, т.е. это некое "соглашение" о том как ведет себя метрика\\ **Counter**\\ Счетчик, монотонно возрастающее число, никогда не убывает, может быть сброшен в ноль в случае перезагрузки приложения\\ Как узнать сколько было запросов если есть всего одно число? смотреть дельту, т.к. прометеус сохраняет это число каждые 30 секунд\\ **Gauge**\\ "Стрелка" - число которое может гулять вверх вниз\\ **Histogram**\\ Гистограмма - агрегация чего то самим приложением, когда нам интересно знать **распределение величин** по заранее **определенным группам** (buckets). **Качественное распределение**\\ Например определили 4 бакета с длительностью запроса, (условно по секунде, меньше или равно), пришла очередная метрика с длительностью запроса, он увеличил на единицу соответствующий бакет и суммарную метрику времени\\ В метриках добавляется тег "**le**" (less than or equal)\\ :!: **Гистограмма считает кол-во попаданий** в какую то группу т.е. запоминает счетчики а не сами значения. Каждый бакет как бы отдельная метрика\\ Агрегируется в т.н. **квантили**. Если вы не знаете какие именно бакеты нужны, есть другой тип **Summary** # Пример метрики: распределение времени обработки HTTP-запросов по 4 бакетам http_duration_bucket{url="/", le="0.1"} 100 http_duration_bucket{url="/", le="1"} 130 http_duration_bucket{url="/", le="5"} 140 http_duration_bucket{url="/", le="+Inf"} 141 http_duration_sum{url="/"} 152.7625769 # это бонусом идет сумма всех значений, которые мы записали http_duration_count{url="/"} 141 # это количество значений, т.е. counter который всегда делает +1 на каждое обновление гистограммы **Summary**\\ Сводка. Это результат **агрегации гистрограммы**, она выдает сразу **квантили**, можно сказать **количественное распределение**\\ По сути получается что считаются квантили, суть в том что группировка "с другой стороны" как бы, если в гистограмме мы создали бакеты по заданному времени, и считаем столько запросов распределяется по каким группам, то тут мы смотрим в какое время попадает 99%, 95%, 50% и 10% (уровни предопределены) запросов\\ :!: интерпретация "меньше или равно" указанного времени\\ :!: Саммари не рекомендуется агрегировать или делать это аккуратно, осмысливая реальные значения, легко получить мусор на выходе\\ # Пример метрики: распределение времени обработки HTTP-запросов по 5 квантилям http_duration_summary{quantile="1"} 100 http_duration_summary{quantile="0.99"} 4.300226799 (<= этого времени) http_duration_summary{quantile="0.95"} 2.204090024 http_duration_summary{quantile="0.5"} 0.073790038 http_duration_summary{quantile="0.1"} 0.018127115 http_duration_summary_sum 152.7625769 # как у гистограммы, сумма всех значений http_duration_summary_count 141 # и количество значений Сводки хранятся в памяти на стороне приложения, а т.к. для них нужно хранить какую то историю показателей, память растет и необходимо периодически ее чистить, поэтому подсчеты квантилей происходят с некоторыми потерями\\ Есть разные алгоритмы для очистки старых данных, последнее/случайное и тд, с разной эффективностью\\
=== Перцентили === Перцентили нужны для того чтобы описать массив данных одним числом\\ Например чтобы понять как ведет себя приложение в целом, отбрасывая единичные выбросы\\
:!: Данные Метрика это временной ряд т.е. набор значений во времени, отбросил timestamp'ы остается просто массив чисел\\ условно [1,3,0,186,14,12,8,17,7]\\ Как обобщить эти данные ? самое популярное это вывести среднее, самое простое среднее арифметическое, это когда сумма элементов делится на их кол-во, **в данном примере 27.5**\\ В какой то теоретической сфере вполне правильно и удобно, есть ряд чисел, правильным средним будет именно это число\\ Но в какой то практическое сфере, прикладной, это не совсем подходит, т.к. есть выбросы и реально основная масса чисел тут не превышает 20, а только один элемент сильно отрывается от всех остальных, поэтому портит статистику. В таких случаях применяют "медиану", она устойчива к любым выбросам\\ **Медиана**\\ Средний элемент т.е. буквально в середине (упорядоченного) массива, **в данном случае 8**\\ Ну и даже на вскидку, кажется более справедливая цифра\\ [0,1,3,7, 8, 12,14,17,186]\\ Восьмерка взята прямо из массива, он упорядочен, половина элементов меньше (или равна) этому числу, вторая половина больше (или равна) ему, т.о. "50% <= 8"\\ Так же можно выделить мин и макс в этом числовом ряду т.е. 0% и 100%\\ 0% < 0 50% <= 8 100% <= 186 Будет полезно посчитать например 90%, 95%, 99% от подобного ряда, чтобы охватить как можно больше данных, исключая при этом не типичные скачки\\ Это и будет называться 90ым, 95ым, 99ым перцентилем, т.е. "медиана" это ничто иное как 50ый перцентиль, просто имеет более распространенное название\\ :!: Перцентили позволяют нарезать слоями наш массив и понять на какие группы делятся элементы, типа "в среднем", "в основном", "в подавляющем большинстве" и тд.\\ **Пример с мониторингом приложения**\\ Предположим что этот массив чисел это длительность выполнения запросов в секундах к нашему приложению, как оценить скорость его работы ?\\ Среднее арифметическое будет 27.5с\\ Медиана (он же 50% перцентиль) 8с\\ p90,95 и 99 в данном случае будут 17с т.к. выборка очень небольшая они совпадают\\
:!: Семплирование Для подсчета такой статистики нужна история данных, со временем она ес-но разрастается, есть несколько вариантов семплирования данных, один из них применение бакетов, о которых уже говорилось выше. т.е. хранить не сами данные а просто счетчики того, сколько раз элемент попал в какой то заранее определенный диапазон\\ Например в случае с временем запросов к приложению можем выделить несколько бакетов типа "<= 3,5,7 секунд" и наблюдать в какие промежутки укладываются запросы к приложению\\
Возвращаясь к Prometheus, перцентили в нем встречаются в двух случаях, это метрики типа "Histogram" (когда бакеты считаются в приложении, а прометеус потом может посчитать по ним перцентили) и "Summary" (перцентили которые уже посчитаны в приложении а прометеус просто хранит их как числа)\\ **Процентиль** просто вариант перевода на русский язык т.е. полный синоним слова перцентиль\\ **Квартиль** - это четверти т.е 25%, 50%, 75% и 100% (первый, второй, третий и четвертый квартили)\\ **Квантиль** - тоже самое что и перцентиль только в случае если вероятность выражается не в процентах\\ | {{:linux:etteka5wyaq_lkb.png?direct&400|}} | {{:linux:etteka5xeaqqal3.png?direct&400|}} | | {{:linux:ettekatxiaad76a.png?direct&400|}} | {{:linux:ettekayxaaa0kpq.png?direct&400|}} | === PromQL === Каждая метрика это временной ряд, отдельная таблица, имя таблицы это уникальный набор всех тегов а значение одно число записанное в разные моменты времени\\
:!: Извлечение данных В самом простом случае, в запросе достаточно указать только имя метрики, извлекутся данные со всеми тегами\\ http_requests_total # Имя метрики тоже является тегом {__name__="http_requests_total"} # Теги можно фильтровать http_requests_total{job="prometheus",group="canary"} **Range vector**\\ Так же можно задать временной диапазон, с помощью которого график будет сглаживается\\ В таком случае мы получаем т.н. **"range vector"**\\ http_requests_total{job="prometheus",group="canary"}[1m] Простой запрос возвращает **"instant vector"**, перечень одиночных значений, привязанных ко времени, график спокойно рисуется, ничего сложного\\ В случае с **range вектором**, каждая точка содержит в себе значение в запрошенный момент времени + массив из предыдущих значений за этот указанный интервал\\ [ 5, 6, 7 ] [2], [3], [4] [3], [4], [5] [4], [5], [6] Но такие данные на двумерном графике ес-но рисоваться не могут, поэтому их нужно сначала привести к простому виду, к "instant" вектору, делается это с помощью функций агрегации\\ т.е. если мы применяем диапазон времени для сглаживания графика, необходимо так же применить ф-цию агрегирования, получить instant вектор\\ :!: На счет интервала, по умолчанию интервал сборка метрик у прометеуса 30 секунд, для в данном случае нужно не меньше двух точек с-но выбирать следует не меньше минуты, иначе данных просто не будет\\ В случае с графаной, для группировки интервалов следует использовать переменную **$__rate_interval**, вместо "$__interval", она поддерживается только в для прометеуса\\ Переменная интервала призвана динамически подбирать интервал, для прометея rate_interval оптимизирована гораздо лучше
:!: Запросы # OR http_requests_total{app=~"apache|nginx|iis.*"} # AND (# найдет все tag, начинающиеся на aaa И заканчивающиеся на bbb) metric{tag=~"aaa.*", tag=~".*bbb"} # запрос, который умножит значения на 10 и вернет только те, которые больше или равны 50: metric{tag="value"} * 10 >= 50 # вернет пересечение: значения, у которых полностью совпадающий набор тегов в обоих запросах metric1 and metric2{tag="something"} # Запрос, который вернет разницу. Например, посчитаем сколько RAM занято total_ram{instance="host"} - free_ram{instance="host"} **Агрегация**\\ # Сумма. Например есть метрика с двумя разными тегами http_requests_total{app="nginx"} http_requests_total{app="apache"} # Просуммируем значения со всеми вариантами (! попавшими под запрос) sum(http_requests_total) # Для того чтобы отображать не все теги, их можно группировать, группировка по указанным sum (http_requests_total) by (app, instance) # Группировка по всем кроме указанных sum (http_requests_total) without (instance) **Функции**\\ Популярна ф-я **rate**. Применяется к постоянно возрастающим счетчикам, считает скорость прироста в секунду\\ Учитывает сбросы метрик приложения (рестарт например), есть ф-я "irate" для резко прыгающих графиков\\ **deriv** аналог rate() но не для счетчиков а для метрик которые меняются в обе стороны, "gauge"\\ Обе ф-ии принимают **range vector**\\ **histogram_quantile**\\ Принимает **instant vector**, позволяет посчитать нужный перцентиль из гистограммы, при этом сами гистограммы можно агрегировать\\ rate(http_requests_total{app="nginx"}[5m]) deriv(ram_free{host="postgresql"}[5m]) histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) histogram_quantile( 0.95, sum by (url, le) ( rate(http_request_duration_seconds_bucket[5m]) ) )
:!:
:!:
:!:
:!: