Если вы работаете техническим инженером в отделе эксплуатации, то с вероятностью 99,9% вы знакомы с Prometheus и прекрасно разбираетесь в языке запросов promQL. Но даже в «родной и знакомой» сфере есть области, которые остаются вопросительными пятнами. Например, «Сравнение векторов»/«Сопоставление векторов». Это механизмы promQL, которые применяются не так часто, плохо документированы и неочевидны для понимания.
Привет, Хабр! На связи Александр, руководитель кластера надёжности в компании ecom.tech (наша команда занимается SRE, проводит тестирование нагрузкой и обеспечивает стек Observability). Этой статьей я постараюсь сделать вашу жизнь чуточку проще, на примерах объяснив нюансы непростой механики сопоставления.

В тексте будет применяться термин «Сравнение векторов», так как мне он кажется более естественным. Для упрощения чтения я не особо упоминаю термин «вектор», но подразумеваю, что все операции с метриками выполняются с их векторами. Мы посмотрим, какие конструкции promQL применяются в операциях сравнения, после подробно разберём их на пяти примерах. В конце будут ссылки на git репозиторий с docker-compose стендом, на котором можно всё попробовать самостоятельно.
ВНИМАНИЕ: Все примеры в статье синтетические и нужны лишь для демонстрации принципа работы. Я старался не просто показать пример, но донести до читателя ход мыслей как решается задача, возможно объяснения будут казаться избыточными.
База для старта
В promQL для сравнения векторов метрик используются арифметические и логические операторы. Сравнение происходит с помощью оператора между правой и левой группой метрик. Каждая группа это результат запроса promQL.

Метрики в группах сравниваются по полному набору своих меток. Это означает, что метрика с набором меток {node=”server-1”,os=”linux”} из левой группы будет совпадать с метрикой из правой группы, только если будет обладать таким же набором меток. При совпадении будет применён оператор, который использовался при сравнении.

Сравнивать метрики по полному набору меток, мягко говоря, не особо удобно и применяется такой подход очень редко. Для корректировки набора меток, которые будут участвовать в сравнении, применяются ключевые слова on() и ignoring(). Используя ключевое слово on(), в скобках можно указать, какой набор меток будет использоваться для сравнения. А с помощью ignoring() можно указать, какие метки будут исключены.
При применении ключевых слов on() и ignoring() метрики из правой и левой группы будут сравниваться в режиме «один к одному» (one-to-one). И если в процессе сравнения появится больше, чем одно совпадение к одному уникальному набору меток, то возникнет ошибка. Для таких случаев стоит использовать модификаторы группировки.
Модификаторов группировки всего два: group_left() и group_right(). Модификатор group_left() включает режим сравнения “многие к одному” (many-to-one) и применяется когда в левой группе больше метрик которые удовлетворяют условию сравнения. Модификатор group_right() включает режим “один ко многим” (one-to-many), его принцип работы такой же как и у прошлого модификатора, только по отношению к правой группе. В скобках для модификаторов можно передавать имена меток, которые должны попасть в финальную выборку.
Отдельно стоит разобрать логические операторы сравнения: or, and и unless.
Оператор and сравнивает правую группу с левой. Если есть совпадения, то в финальную выборку добавляет метрику только из левой части. Оператор unless работает похоже на оператор and, но при совпадении он выкидывает из финальной выборки совпадающие метрики. Unless удобно использовать, как динамический фильтр в запросах. Оператор or сравнивает каждую метрику из правой группы с метриками в левой. Если не находит совпадений, то в финальную выборку определяет метрику из правой группы. Но если совпадение находится, то в финальную выборку добавляется метрика из левой группы.
В практике я чаще всего применяю именно логические операторы, примеры их применения будут разобраны ниже. У логических операторов есть ограничение, они работают только в режиме "one-to-one".
Вычисляем процент заполнения диска с привязкой к точке монтирования на сервере worker-001.corp.tech
В нашем хранилище метрик для этой задачи находятся две метрики:
node_filesystem_size_bytes. Метрика показывает объём места на диске;
node_filesystem_used_bytes. Метрика показывает, какой объём на диске используется.
Для решения этой задачи необходимо просто поделить значение одной метрики на значение другой. Но сначала их нужно сравнить, а для этого нам понадобится исследовать обе метрики и понять их набор меток.
Сделаем 2 простых запроса:
node_filesystem_size_bytes{node="worker-001.corp.tech"}. Запрос покажет все метрики node_filesystem_size_bytes с уникальными наборами меток для сервера worker-001.corp.tech;
node_filesystem_used_bytes{node="worker-001.corp.tech"}. Запрос сделает тоже, что и первый, только для метрики node_filesystem_used_bytes.
Результаты запросов:

Запросы вернули две группы метрик. Можно сразу заметить, что у них есть общий набор меток, по которым мы можем их сравнить. Для решения задачи мы будем использовать метки device и mountpoint. Для вычисления процента использования диска необходимо разделить метрику node_filesystem_used_bytes на node_filesystem_size_bytes. То есть группа метрик node_filesystem_used_bytes должна быть в левой части выражения, а группа метрики node_filesystem_size_bytes в правой части. Видно, что все метки из первой группы относятся один к одной (сравнение one-to-one) к метрикам во второй группе. Это значит, что мы можем использовать оператор on() без модификаторов группировки.
Результат запроса метрик:

Выполнение этого запроса даст две метрики. Первая покажет результат вычисления для набора с метками {device="/dev/sda1", mountpoint="/"}, а вторая с метками {device="tmpfs", mountpoint="/run/user/1001"}.

Развёрнутое сравнение по цветам:

Ключевое слово on() берёт одну метрику из группы и сравнивает её по набору указанных меток device и mountpoint с другой группой метрик. При совпадении производит операцию деления. На изображении выше цветом указаны совпадающие наборы меток в разных группах.
Вычисляем процент используемой оперативной памяти для каждого контейнера на сервере
Метрики:
container_memory_usage_bytes. Метрика показывает, сколько контейнер использует оперативной памяти;
server_memory_total_bytes. Метрика показывает, сколько на сервере установлено оперативной памяти.
Для решения задачи нам нужно опять сравнить и поделить две метрики (container_memory_usage_bytes поделить на server_memory_total_bytes). Пойдём по сценарию из прошлого примера, исследуем метрики, найдём подходящие метки и напишем финальный запрос.
Результат запроса для исследования метрик:

Вернулось две группы метрик, но количество в каждой группе разное. В левой группе метрик больше (соотношение many-to-one), и нам придётся использовать модификатор группировки, так как больше метрик в левой части, то используется модификатор group_left. Метка, которую мы можем применить для связи только одна, это метка с названием node, но нам её хватит.
Финальный запрос:

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

Развёрнутое сравнение по цветам:

В этом примере принцип такой же, как и в прошлом. Только теперь, каждая запись из правой группы сопоставляется с несколькими записями из левой.
Вычисляем общее количество CPU доступных для размещения контейнеров
Для этой цели используются физические сервера и виртуальные. Известно особое условие: если физический сервер используется для размещения виртуальных серверов, то он не может использоваться для размещения контейнеров. Чтобы выполнить задачу, нужно суммировать все CPU виртуальных серверов и физических серверов на которых нет виртуальных машин.
Метрики:
vm_cpu_core_total. Метрика показывает количество ядер на виртуальном сервере;
server_cpu_core_total. Метрика показывает количество ядер на физическом сервере.
Результат запроса метрик:

Из обеих групп метрик видно, что метрики можно сопоставить по метке node. Для выполнения задачи финальный запрос должен вывести все метрики из группы для server_cpu_core_total, которые не совпадают с метрикой в группе для vm_cpu_core_total по метки node. И далее вывести все метрики из группы vm_cpu_core_total (тут немного запутанно, попробуйте вчитаться). Решением будет использовать логический оператор or. Есть проблема, так как оператор сравнивает весь набор меток для каждой метрики, нам нужно убрать из условий сравнения метку name, иначе ни один набор меток для метрик не найдёт совпадения, так как name у всех разный. Убрать метку из операции сравнения позволяет оператор ignoring().
Финальный запрос:
Результатом запроса будет пять метрик. В этом списке метрик будут отсутствовать метрики серверов, на которых стоят виртуальные машины.

Развёрнутое сравнение по цветам:

Конструкция or ignoring(name) последовательно сравнивает левую группу с правой всего по одной метке node, и если в правой группе есть совпадение, то совпадающая метрика игнорируется.
Выясняем, какие ручки одновременно реализованы в версии v1 и v2
Задача: существует две версии v1 и v2 одной системы. Версия v1 уже работает в полном объёме, а версия v2 ещё не реализовала некоторые HTTP ручки. Нам нужно узнать, какие ручки одновременно реализованы в версии v1 и v2.
Метрика всего одна http_requests_total, которая показывает количество запросов с разбивкой по версии, пути запроса и методу запроса.
Результат запроса метрик:

Эти метрики можно разбить на две группы обычным фильтром по метке app_version. Каждая группа будет соответствовать своей версии системы. Для нашей задачи нужно найти все совпадения в правой и левой группе по меткам path и method. Для такого сравнения необходимо использовать оператор and. Для указания меток используемых в сравнении мы опять применим ключевое слово on(), можно использовать ignoring(), но on() более надёжный, так как даёт полный контроль над набором меток.
Финальный запрос:

Результатом запроса будут четыре метрики принадлежащие к версии v1, HTTP ручки которых реализованы в версии v2 .

Как это выглядит по векторам:

Конструкция and on (path,method) делает сравнение метрик по меткам path и method, и если у метрики в правой группе есть полное совпадение в левой, то она попадает в финальную выборку.
Пример 5...
Я обещал показать пять примеров, но немного слукавил (извините). Для этого примера я хочу, чтобы читатель сам написал запрос. Задача похожа на прошлую, но теперь нужно найти HTTP ручки нереализованные в системе с версией v2. Набор метрик тот же, финальный запрос очень похож на прошлый (подсказка: нужно использовать unless). Для выполнения можно воспользоваться демонстрационным стендом, ссылка будет ниже.
Результат запроса должен быть таким:

Стал ли синтаксис более понятным?
Итак, мы разобрали довольно непонятную тему запросов promQL. Надеюсь, прочитав примеры и разобрав запросы, стало намного понятнее. Воспользуйтесь стендом для тестов, чтобы пройти всё самостоятельно – git репозиторий.
Не ленитесь набивать руку, это даст вам понимание принципов работы, что и будет являться огромной ценностью в будущем. Всем удачи.
