Это четвертая, финальная часть из цикла статей про метрики. В первой — вводной — я рассказал, почему метрики для сервисов устроены именно так, чем они отличаются от логов, и какую задачу решают. Во второй разобрались с форматом и типами метрик. В третьей — с перцентилями. Теперь, наконец, можно пойти и вывести что-нибудь на графики! На этот раз будет более хардкорно.

Оглавление цикла:

  1. Потерянное введение

  2. Prometheus

  3. Перцентили для чайников

  4. PromQL


Постоянно держим в уме: метрики не про точность, а про усредненные значения. Есть много операций и тонкостей, при которых данные теряют точность, и это нормально — производные, перцентили, разные corner cases. За всем не уследишь, поэтому заранее считаем, что данные не 100% точные. Метрики вообще не подходят для какого-нибудь биллинга, а вот для оценки состояния систем — вполне.

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

Мы будем экспериментировать с его родным веб-интерфейсом. Этого достаточно, чтобы наблюдать за запросами и результатами из БД, и рисовать простые графики. Если у вас есть Grafana, можно все делать в ней: там есть Query Inspector, который тоже показывает запросы/ответы. Но Grafana добавит тормозов и своих странностей в процесс, поэтому на время экспериментов лучше обойтись без нее. Чтобы не превращать статью в devops-гайд по настройке, инструкции по запуску сервера останутся за скобками, проще всего найти любой готовый docker-compose.

Запросы

Вспоминаем из прошлых частей, что Prometheus — это Time Series Database, и в нем хранятся временны́е ряды. Каждая метрика — это временной ряд, можно сказать, отдельная таблица. Имя таблицы — это набор лейблов, а значения — одно число, записанное в разные моменты времени: [(day1, t1), (day2, t2), ...]. Запросы могут:

  • достать значения из конкретного ряда за нужное время

  • то же самое сразу из нескольких рядов

  • сгруппировать или провести вычисления над результатом

Чтобы было, что выводить на график, нам нужен хотя бы один временной ряд: по горизонтали на графике всегда будет время, по вертикали — значение метрики в этот момент. Чтобы нарисовать несколько линий, нужно получить несколько временны́х рядов. Точный формат ответов будем разбирать дальше — чтобы не запутаться.

Примеры простых запросов

Самый простой запрос

http_requests_total

Попробуйте его или что-нибудь подобное в своих условиях. В результате достанутся все метрики, у которых название – http_requests_total, и при этом могут быть любые другие лейблы, например с разными verb или url. В запрос неявно подставится текущее время. То есть мы запросим показания за один timestamp. Ответ выглядит как-то так:

  • один timestamp;

  • лейблы какого-то ряда, который попал под запрос;

  • значение из этого ряда в этот момент времени;

  • лейблы второго ряда, который тоже попал под запрос;

  • значение из второго ряда в этот же момент времени;

  • …и так далее для всех рядов.

Это называется instant vector, про него подробнее будет чуть ниже.

Кстати, название метрики — не какая-то особенная штука, а синтаксический сахар. Это просто лейбл с названием __name__. Вот запрос, который делает то же самое:

{__name__="http_requests_total"}

Достаем метрику с подходящими лейблами

Получаем такой же набор значений разных рядов в один момент времени, тот же тип ответа — instant vector.

http_requests_total{job="prometheus",group="canary"}

Есть еще странный формат запросов с квадратными скобками

Пока что просто пример: достаем метрику с подходящими лейблами, и для каждой точки собираем массив предыдущих точек за 1 минуту. Ответ на такой запрос приходит в другом формате — range vector. Что это за зверь и зачем это вообще нужно – см. ниже.

http_requests_total{job="prometheus",group="canary"}[1m]

Простые типы

Их всего два:

  • Строки — чтобы запрашивать лейблы и их значения.

  • Числа — только double, они могут участвовать в функциях.

Помним, что значение метрики — всегда одно число в один момент времени, больше никаких данных нет.

Типы выражений и API

Дальше разберем два сложных типа, которыми тоже оперируют функции: instant vector и range vector. Описание намеренно упрощенное, чтобы было легче разобраться. Еще будет уточнение про типы, которые используются в API между Prometheus и Grafana: vector и matrix, и как все это друг с другом соотносится.

Тип instant vector

Массив из key-value, где key – метрика с лейблами, а value — значение в запрошенный момент времени. Это единственное, что рисуется на графиках. Все остальное надо привести к этому типу какой-нибудь функцией (агрегировать).

Почему такая матрешка? Дело в том, что один запрос может вернуть сразу несколько временны́х рядов. Вспоминаем, что одна метрика с разными значениями лейблов — это разные ряды. Поэтому instant vector-ы всегда возвращаются набором key-value, где ключ — это полное описание ряда (имя метрики и все лейблы), чтобы отличать один ряд от другого.

Один instant vector привязан к одному timestamp. Чтобы из этого получилось что-то полезное (для отображения на графике) — к запросам обычно добавляются диапазон времени и шаг, а в ответе возвращаются наборы instant vector-ов, попавшие под диапазон. И в интерфейсе Prometheus, и в Grafana, это делается автоматически, поэтому в самих запросах можно сконцентрироваться именно на том, какие данные мы хотим достать. Единственное, что может показаться неожиданным — если в запрошенный timestamp нет значения в БД, то Prometheus будет искать значения с timestamp меньше, то есть отматывать назад, по умолчанию в пределах 5 минут.

Пример

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

Метрика (временной ряд)

Время (unix timestamp)

Значение (double)

{__name__="http_requests_total",instance="app1"}

1615973700

0

{__name__="http_requests_total",instance="app2"}

1615973700

0

{__name__="http_requests_total",instance="app1"}

1615973730

0

{__name__="http_requests_total",instance="app2"}

1615973730

1

{__name__="http_requests_total",instance="app1"}

1615973760

1

{__name__="http_requests_total",instance="app2"}

1615973760

4

{__name__="http_requests_total",instance="app1"}

1615973790

3

{__name__="http_requests_total",instance="app2"}

1615973790

7

{__name__="http_requests_total",instance="app1"}

1615973820

4

{__name__="http_requests_total",instance="app2"}

1615973820

10

  • Если запросить http_requests_total{instance="app1"} в момент времени 1615973790 то получим instant vector с одной метрикой, у которого value = (1615973790, 3)

  • Если запросить http_requests_total в момент времени 1615973760 то получим instant vector с двумя метриками, у которых разные value: (1615973760, 1) и (1615973760, 4) и разные теги, app1 и app2

  • Если запросить http_requests_total{instance="app1"} в момент времени 1615973795 — такого timestamp в базе нет, поэтому подойдет ближайшее значение из прошлого, то есть с timestamp 1615973790, результат будет как в первом случае

Про рисование instant vector на графике будет ниже.

Селекторы instant vetcor-ов

Метрики и лейблы можно матчить по равно/неравно и регулярками, подошло/не подошло. Операторы: = != =~ !~

http_requests_total{environment=~"staging|testing|(dev.*)", method!="GET"}

Тип range vector

Уже сложнее: массив из key-value, где key – метрика, а value – это значение в запрошенный момент времени + массив из [предыдущих значений за какой-то интервал]. Без паники, ниже разберемся зачем это нужно, и все станет понятно. Отличие от instant vector только в более сложном value. Причина, почему есть key, все та же: один запрос может вернуть много временных рядов с разными лейблами.

Так вот, про то, почему value здесь такой хитрый...

Помните, что такое производная?

Если вы еще не убежали, попробуем разобраться по-инженерному, без идеально точного матана. Производная какой-то функции — это вот такая формула:

( f(x+0.1) - f(x) ) / 0.1

Число 0.1 здесь — какой-то малый шаг. Формула нам показывает, на сколько изменится значение нашей функции f(), если мы на чуть-чуть сдвинем x. Делить на 0.1 нужно, потому что так удобнее работать с этой штукой: не важно, какой шаг мы возьмем — большой или маленький, если значение f() меняется одинаково, то и значение производной окажется одинаковым.

Чтобы посчитать производную, нужно иметь хотя бы два значения функции

Можно немного считерить и считать производную не для значений (x, x+0.1), а для (x-0.1, x), то есть для текущего и предыдущего. Если у нас есть несколько предыдущих точек — еще лучше. Можно посчитать хитрее и еще делать интерполяцию/экстраполяцию. То есть мы хотим иметь набор значений [f(x), f(x-0.1), f(x-0.2), f(x-0.3), ...].

Вот для этого и нужен range vector: для каждого момента времени x мы имеем точку со значением метрики в этот момент: f(x), и еще заглядываем в прошлое и берем несколько предыдущих точек: [f(x-30s), f(x-60s), f(x-90s), ...]. 30 секунд здесь берутся из-за того, что Prometheus собирает значения метрик с таким интервалом, между ними просто ничего нет.

Range vector используется не только для производной: все функции, которые считают какое-то «изменение за небольшой интервал», например, delta, принимают на вход этот тип — просто для унификации.

Селекторы range vector-ов

Как написать запрос, возвращающий ranve vector? Так же, как и обычный запрос, только добавляем интервал в квадратных скобках в конце.

http_requests_total{job="prometheus"}[5m]

На что влияет число в квадратных скобках: в каком-то смысле, благодаря ему можно тюнить степень «сглаживания» или «разрешение» (точность).

  • больше интервал — более гладкий результат, видно тренд (общую картину);

  • меньше интервал — более шумный результат, видно пики и резкие скачки;

  • слишком малый интервал и не захватили минимум две точки — нет результата.

Range vector нельзя вывести на график (см. пример ниже — слишком много данных, непонятно, что выводить). Его нужно сначала обработать какой-то функцией и получить instant vector, который уже нормально рисуется. Собственно, для этого он и нужен — чтобы можно было применять всякие навороченные функции, которым нужны не только точки для графика, но и «взгляд в прошлое» по каждой точке, чтобы брать производную, считать дельту и прочее. Конкретные функции и их юзкейсы будут ниже, а пока что посмотрим, как достать range vector из базы.

Пример

Отталкиваемся от примера выше (про instant vector), только теперь запрос такой: http_requests_total{instance="app1"}[1m]. Чтобы не потеряться, он вернет только одну метрику, поэтому она в таблице не упоминается.

Время (unix timestamp)

Значение (double)

Предыдущие значения за 1 минуту

1615973700

0

[]

1615973730

0

[0]

1615973760

1

[0, 0]

1615973790

3

[0, 1]

1615973820

4

[1, 3]

Увеличим интервал:

http_requests_total{instance="app1"}[2m].

Время (unix timestamp)

Значение (double)

Предыдущие значения за 2 минуты

1615973700

0

[]

1615973730

0

[0]

1615973760

1

[0, 0]

1615973790

3

[0, 0, 1]

1615973820

4

[0, 0, 1, 3]

Разбирать пример, в котором один запрос возвращает две метрики, нет смысла: логика та же, что и раньше, только для каждой метрики — свои данные.

Выбор интервала

Зависит от сценариев использования.

  • Для запросов, которые пишутся руками во время экспериментов: хотите сглаженный график, чтобы было видно, что с ним происходит в целом — берите интервал побольше. Если нужно видеть, как значение «скачет» — берите поменьше, но не менее 1 минуты (2*scrape_interval, который у нас 30 секунд).

  • Для запросов, которые вы пишете в Grafana, важно учитывать текущий масштаб графика, поэтому используйте переменную $__rate_interval (и ни в коем случае не $__interval).

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

Чтобы подробнее объяснить эти моменты, понадобится отдельная статья, уже с прицелом на Grafana и с примерами из нее, поэтому здесь оставим без деталей.

Типы в API

Если открыть веб-интерфейс Prometheus, попробовать разные запросы и посмотреть на трафик (в Devtools по F12, например), то можно офигеть увидеть странную вещь: на самом деле типы данных, которые мы можем получить, называются vector и matrix. А еще запросы на вкладках Table и Graph с одним и тем же текстом ходят на разные URLы и возвращают разные типы!

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

  • Table, запрос одной метрики, например http_requests_total{instance="app1"}.

  • Table, запрос со временем в квадратных скобках, по той же метрике, например http_requests_total{instance="app1"}[1m].

  • Graph с тем же запросом метрики.

  • Graph с тем же запросом со временем в квадратных скобках (да, в интерфейсе будет ошибка, так и нужно).

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

А теперь объяснения:

Метрика

Запрос http_requests_total{instance="app1"} из вкладки Table — это запрос instant vector-а. В json нам возвращается vector. В запросе видно, что добавляется time — мы просим instant vector на один момент времени.

Матрица

Запрос http_requests_total{instance="app1"}[1m] из вкладки Table — это запрос range vector-а. В json нам возвращается matrix. В запрос тем же образом добавилось time. Единственное, на что нужно обратить внимание — выше мы разбирали, что value состоят из точки и [массива прошлых точек]. Но на самом деле можно упростить и хранить все в одном массиве, просто добавив «текущую» точку к началу массива. Например, вместо (1,[2,3,4]) Prometheus делает [1,2,3,4].

График

Запрос http_requests_total{instance="app1"}, такой же как и первый, но уже переключенный на вкладку Graph. Теперь мы получаем ответ типа matrix! Почему?

Дело в том, что одиночный instant vector не нарисуешь на графике, это просто точка в один момент времени (или несколько точек, если вернулись ряды с разными лейблами). А нам нужно много точек за диапазон. Поэтому в запросе теперь не time, а start, end, step. В результате структура key-value остается такой же, мы можем одним запросом вернуть несколько метрик, но в value уже лежит массив вроде [(timestamp1, 1), (timestamp2, 10), ...] — значения за разные моменты времени. По структуре оно получилось таким же, как и результат запроса range vector-а, который с квадратными скобками. Поэтому тип ответа используется один и тот же, matrix.

Ошибка

А что будет, если попросить нарисовать график из запроса range vector-а? То есть взять http_requests_total{instance="app1"}[1m] и переключить на вкладку Graph? Матрица матриц? Трехмерный график? Было бы прикольно, но получим просто ошибку: range vector нельзя запросить за диапазон и нарисовать, потому что получилось бы больше данных, чем влезает в матрицу, которая по сути и есть 2D-график.


Теперь про разные функции и запросы. В реальности, когда для запросов используется Grafana, добавляются еще сложности с ее переменными — но это тема отдельной статьи.

Offset

Если хотим запросить данные относительно текущего времени назад, например, чтобы сравнить с прошлым днем.

http_requests_total offset 1d

Вложенные запросы

Так делать можно, но пропустим. Это уже сложности, которые поначалу не нужны.

OR

Логическое «ИЛИ» над одним тегом костылится с помощью регулярных выражений.

http_requests_total{app=~"apache|nginx|iis.*"}

AND

Логическое «И» над одним тегом костылится, если в запросе написать тег несколько раз с разными условиями.

# найдет все tag, начинающиеся на aaa И заканчивающиеся на bbb
metric{tag=~"aaa.*", tag=~".*bbb"}

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

JOIN

В PromQL есть примерное подобие join-ов: 1-to-1, 1-to-N, N-to-1, но с ходу непонятно, как ими пользоваться: называется vector matching и имеет довольно страшный синтаксис, хотя статья неплохо объясняет, как это работает. Полностью тащить сюда документацию не хочется, и даже один реалистичный пример будет громоздким, поэтому придется пропустить.

Операторы и группировки

Только для instant vectors

Обычная арифметика и логика. Оперировать можно числами и векторами, т.е. пересечь два вектора, сравнить с числом и т.д.

Например, вот запрос, который умножит значения на 10 и вернет только те, которые больше или равны 50:

metric{tag="value"} * 10 >= 50

Запрос, который вернет пересечение: значения, у которых полностью совпадающий набор тегов в обоих запросах:

metric1 and metric2{tag="something"}

Запрос, который вернет разницу. Например, посчитаем сколько RAM занято:

total_ram{instance="host"} - free_ram{instance="host"}

Аналогично or (объединение) и unless (дополнение).

Агрегация

Только для instant vectors

Есть функции, которые делают что-то вроде GROUP BY. Предположим, есть такие метрики:

http_requests_total{app="nginx"}
http_requests_total{app="apache"}

Это разные временны́е ряды, и если рисовать их на графике «в лоб» запросом http_requests_total, получим две разные линии. Если мы хотим объединить их в один график, нужна агрегация. Например, сумма:

sum(http_requests_total)

Результат — один вектор, значит и график будет один. Значения, очевидно, сумма значений всех временных рядов, попавших под запрос.

Если у нас метрика с кучей тегов, но отобразить нужно только определенные, то можно сгруппировать по тегам. В результате получим несколько векторов, в зависимости от того, какие получились группы. Например, мы пишем метрику запросов с тегами app, instance, datacenter, region. Выведем по графику на каждый app+instance, просуммировав по датацентрам и регионам. В результате получим ряды с разными app и instance, но вообще не будет datacenter и region, так как их мы схлопнули в сумму:

sum by (app, instance) (http_requests_total)

Можно переставить местами части запроса, то есть вот это — тот же самый запрос. Пишите, как вам удобно:

sum (http_requests_total) by (app, instance)

Можно группировать по «всему кроме тега»:

sum without (instance) (http_requests_total)
# или поменять местами:
sum (http_requests_total) without (instance)

Список всех агрегаторов — в документации Prometheus.

Функции

Бывают для instant и range векторов

rate(range vector)

Наш главный друг и товарищ! Это что-то вроде навороченной производной, которая пытается рисовать гладкий график и учитывает всякие corner cases (см. ниже). Применяется к возрастающим счетчикам, чтобы показать прирост за какое-то время. Например, количество обработанных запросов, переданных байт и т.д.

rate(http_requests_total{app="nginx"}[5m])
  • считает скорость прироста в секунду (запросов/сек);

  • подходит только для counter т.к. полагается на возрастание;

  • учитывает сбросы метрики на 0, например при рестартах приложений;

  • экстраполируется;

  • есть irate для резко прыгающих счетчиков.

increase(range vector)

Не надо использовать increase, лучше берите rate, так в документации советуют!

increase(http_requests_total{app="nginx"}[5m])
  • считает прирост за интервал (сколько запросов пришло);

  • подходит только для counter т.к. полагается на возрастание;

  • учитывает сбросы счетчика на 0, например при рестарте;

  • экстраполируется (поэтому бывают дроби даже когда прирост целый).

По сути это сахар для rate() * интервал. Попробуйте нарисовать какую-нибудь метрику с ним и с rate. Увидите закономерность. Например, для интервала в 1 час, эти штуки дадут одинаковый результат:

rate(metric[1h])

increase(metric[1h])/3600

delta(range vector)

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

delta(ram_free{host="postgresql"}[5m])
  • считает разницу за интервал (сколько запросов пришло);

  • подходит только для gauge;

  • экстраполируется (поэтому бывают дроби даже когда изменение целое).

deriv(range vector)

Производная. Аналог rate(), но для метрик, которые могут убывать или скакать туда-сюда. Не учитывает ситуации, когда метрика обнулилась, и может сойти с ума в этот момент.

deriv(ram_free{host="postgresql"}[5m])
  • считает производную, т.е. прирост в секунду (запросов/сек);

  • подходит только для gauge;

  • не учитывает сбросы в 0.

histogram_quantile(instant vector)

Специальная функция для работы с гистограммами (которые пишут бакеты типа le="1s"). Позволяет посчитать нужный перцентиль из гистограммы. При этом сами гистограммы можно агрегировать, например sum by (tag).

Возможность агрегировать гитсограммы и только после этого считать перцентили — их киллер-фича, и основное отличие от Summary.

Например, у вас две реплики приложения. Они обе пишут гистограммы с распределением запросов, каждая реплика — свою. Чтобы вывести один график, их надо объединить в одну. Это делается уже в Prometheus, после этого можно обсчитать гистограммы и получить перцентили, которые мы уже хотим видеть на графике. Если приложения вместо Histogram пишут Summary — их уже нельзя объединить или сложить! Подробнее об этом было в предыдущей статье.

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])
    )
)
  • считает квантили;

  • только для histogram с бакетами (теги le).

aggr_over_time(range vector)

Агрегаторы типа sum и max работают только для instant vectors, но если очень нужно — есть похожие функции-агрегаторы для range vectors, которые делают агрегацию на каждом интервале. То есть это как delta(), только max/min/avg и т.д. В простых случаях все это не нужно, но возможность есть.

avg_over_time
min_over_time
max_over_time
sum_over_time
count_over_time
quantile_over_time
stddev_over_time (отклонение)
stdvar_over_time (дисперсия)

Бонус

Для тех, кто героически остался с нами до конца — пара заметок о граблях, на которые легко налететь при первых попытках реализовать сбор метрик у себя.

(не)Персистентность

Ваши сервисы, которые выставляют метрики, не живут вечно — они рано или поздно обновятся или перезапустятся. Их метрики при этом сбросятся и начнут отсчитываться с нуля. Это нормально и учтено в модели данных и запросов Prometheus. Важно об этом помнить, чтобы использовать подходящие типы и функции, например:

  • rate() учитывает такую ситуацию, но ожидает, что значение метрики всегда возрастает — без этого математика не сойдется. Поэтому совершенно нормально сделать счетчик «сколько приложение обработало запросов», и начинать его с нуля после рестарта.

  • С другой стороны, deriv() работает с gauge, но не обрабатывает ситуации, когда метрика сбросилась в ноль. Обычно это и не нужно: gauge используется для показателей, которые не отсчитываются с нуля, например, свободная RAM. Когда приложение перезапустилось, метрика сразу будет показывать актуальное значение и периодически его обновлять.

Таким образом, не нужно писать метрики в БД на своей стороне или как-то обеспечивать их персистентность. Кроме того, вам поначалу может быть вообще все равно, потому что это такой частный случай: ну и что, подумаешь — иногда метрику штормит при деплоях или когда приложение упало, какая-то секунднаямелочь. Но это станет важно в дальнейшем, когда вы захотите сделать алерты. С алертами эта особенность станет огромной головной болью, причиной ложных срабатываний, костылей и переписываний. Лучше сразу разобраться, как ведут себя метрики при рестартах приложений, и пользоваться подходящими функциями в запросах. Даже если на первый взгляд кажется, что некоторые функции похожи и делают одно и то же.

Реплики приложения

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

# реплика 1:
api_http_requests_total{method="POST", url="/login"} 2
# реплика 2:
api_http_requests_total{method="POST", url="/login"} 3

Раз лейблы одинаковые, то эти два значения, собранные в примерно один и тот же timestamp, попадут в один и тот же временной ряд! То есть, у нас будет мешанина из противоречивых данных. При построении графика это будут две очень близкие друг к другу точки. При следующем scrape ситуация повторится.

Время (unix timestamp)

Значение (double)

1615973700

2

1615973701

3

1615973730

2

1615973731

5

Сколько запросов было обработано на самом деле всеми репликами? Сначала 5, потом 7, но эту информацию мы уже никак не достанем — она вся сложена в один последовательный ряд. Поэтому каждая реплика должна добавлять какой-то уникальный для себя лейбл — имя реплики, hostname, id pod-а в kubernetes, что угодно, позволяющее отличить метрику этой реплики от другой.

# реплика 1:
api_http_requests_total{method="POST", url="/login", replica="app_1"} 2
# реплика 2:
api_http_requests_total{method="POST", url="/login", replica="app_2"} 3

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


Это конец цикла. Теперь вы знаете кунг-фу основы и принципы Prometheus, немного математической магии, и какие инструменты для обработки метрик есть под рукой.

Что дальше? Можно отправляться в Grafana — писать запросы и рисовать графики. Потом возвращаться в приложение и добавлять метрики, если чего-то не хватает. Следующая статья, если захотите продолжения, уже будет сборником рецептов для Grafana. Там не будет гайда, какие метрики собирать и как их правильно отображать — это все зависит только от ваших потребностей и специфики. И, наконец, на основе графиков можно делать алерты, например средствами той же Grafana. На самом деле, алерты – это самая мощная штука, ради которой вообще нужны были метрики. Но это уже тема для других статей.