С чего начать Первые шаги трек · 15 ступеней
Linux Labs

PromQL: язык запросов Prometheus

12 мин Ubuntu 24.04 · Prometheus 2.45

Язык запросов к метрикам Prometheus: выбрать ряды по меткам, превратить счётчик в скорость, сложить и сравнить.

Шпаргалка

9 команд Версия для печати
  • q — короткое имя для promtool query instant
  • q() { promtool query instant http://localhost:9090 "$1"; }
  • q 'up' 1 — цель жива, 0 — нет
  • q 'up == 0' только упавшие цели
  • q 'node_memory_MemAvailable_bytes' ряды метрики по имени
  • q 'up{job="node"}' ряды с нужной меткой
  • q 'rate(node_cpu_seconds_total[1m])' счётчик → скорость
  • q 'increase(prometheus_http_requests_total[5m])' прирост
  • q 'sum by (mode) (rate(node_cpu_seconds_total[1m]))' сумма
  • q 'count by (job) (up)' целей в каждом задании

Обзор

PromQL — язык запросов Prometheus. Запрос выбирает временные ряды из базы Prometheus и считает по ним: скорость роста, сумму, долю, сравнение с порогом. На PromQL пишут выражения правил алертов (prometheus) и панели grafana.

Веб-интерфейс Prometheus на :9090 показывает результат графиком. Из терминала запрос выполняет promtool из пакета prometheus:

text
$ promtool query instant http://localhost:9090 'up'
up{instance="localhost:9090", job="prometheus"} => 1 @[1792643816.192]
up{instance="localhost:9100", job="node"} => 1 @[1792643816.192]

Строка — один ряд: имя метрики, метки в фигурных скобках, после => — значение, после @ — метка времени в секундах unix-времени (date -d @1792643816 переводит её в дату, см. date). Пустой вывод с кодом 0 значит «таких рядов нет», это не ошибка.

Метрика, метки и ряд

Метрика — величина с именем: node_memory_MemAvailable_bytes — доступная память в байтах. Метки — пары имя="значение", которые уточняют, о чём число: mountpoint="/", cpu="0", mode="idle". Имя вместе с полным набором меток задаёт временной ряд — последовательность значений с метками времени. Сколько разных наборов меток, столько рядов: node_filesystem_avail_bytes на учебной машине — три ряда, для /, /boot и /boot/efi.

Метки job и instance добавляет сам Prometheus, когда собирает метрики: job — имя задания из scrape_configs, instance — адрес цели (prometheus).

Селектор

Селектор выбирает ряды по имени и меткам:

promql
node_filesystem_avail_bytes                  # все ряды метрики
node_filesystem_avail_bytes{mountpoint="/"}  # метка равна
up{job!="prometheus"}                        # метка не равна
node_cpu_seconds_total{mode=~"user|system"}  # по регулярному
node_cpu_seconds_total{mode!~"idle|iowait"}  # не подходит под него
{__name__=~"node_memory_.*"}                 # по части имени

Значение метки пишется в двойных кавычках. Регулярное выражение после =~ должно совпасть со значением целиком: job=~"node" не выберет node2, нужно job=~"node.*" (regex).

Мгновенный вектор и вектор за интервал

up — мгновенный вектор: по одному последнему значению каждого ряда. Окно в квадратных скобках — up{job="node"}[1m] — даёт вектор за интервал: все точки каждого ряда за последнюю минуту. При сборе раз в 15 секунд это четыре точки. Единицы окна — s, m, h, d.

Вектор за интервал сам по себе график не рисует: его передают функции, которая сводит точки окна в одно число, — rate, increase, avg_over_time, max_over_time.

Счётчик и gauge

Тип метрики виден в выводе экспортёра, строка # TYPE (node-exporter):

text
# TYPE node_cpu_seconds_total counter
node_cpu_seconds_total{cpu="0",mode="idle"} 23.82
# TYPE node_memory_MemAvailable_bytes gauge
node_memory_MemAvailable_bytes 2.95616512e+08
Тип Как ведёт себя Как читать
counter только растёт с запуска процесса, при перезапуске — снова с нуля; имя обычно на _total через rate или increase
gauge текущее значение, ходит вверх и вниз как есть, сравнивать с порогом
histogram ряды _bucket{le="…"}, _sum, _count: сколько наблюдений попало в каждую корзину _sum и _count — через rate

Голое значение счётчика о нагрузке не говорит: 23.82 секунды простоя процессора с загрузки машины — неизвестно, много это или мало. Смысл есть у скорости его роста.

rate и increase

rate(счётчик[окно]) — средняя скорость роста за окно, в единицах в секунду. increase(счётчик[окно]) — прирост за окно, то есть rate, умноженный на длину окна.

promql
rate(node_cpu_seconds_total{mode="idle"}[1m])  # доля простоя, 0…1
increase(prometheus_http_requests_total[1m])   # запросов за минуту

Секунды процессора в секунду — доля времени: 0.9 у mode="idle" значит, что ядро 90 % времени простаивало. Обе функции понимают сброс счётчика: значение упало — процесс перезапустился, и рост считается от нуля, без отрицательной скорости.

Окно должно вмещать хотя бы две точки, обычно берут четыре интервала сбора: при scrape_interval: 15s — [1m]. [10s] при сборе раз в 15 секунд даёт пустой ответ. Пуст rate и в первые десятки секунд после запуска Prometheus: второй точки ещё нет.

rate и increase — только для счётчиков. У gauge скорость роста считают deriv, разницу за окно — delta, среднее и максимум — avg_over_time и max_over_time.

Агрегация: sum by и without

sum, avg, min, max, count сводят ряды в один или в группы по меткам:

promql
sum(rate(node_cpu_seconds_total[1m]))             # всё в один ряд
sum by (mode) (rate(node_cpu_seconds_total[1m]))  # ряд на mode
sum without (cpu) (rate(node_cpu_seconds_total[1m]))  # без cpu
count by (job) (up)                               # целей в job
topk(3, node_filesystem_size_bytes)               # три наибольших

После by (…) у результата остаются только перечисленные метки, после without (…) — все, кроме перечисленных:

text
$ promtool query instant http://localhost:9090 'count by (job) (up)'
{job="prometheus"} => 1 @[1792644275.563]
{job="node"} => 1 @[1792644275.563]

Сначала rate, потом sum: сумма счётчиков разных процессов сбрасывается, когда перезапускается любой из них.

Арифметика и сравнение

+ - * / между двумя наборами рядов считаются попарно — для рядов с одинаковыми метками (имя метрики не в счёт). Число и ряд — для каждого ряда:

promql
node_filesystem_avail_bytes{mountpoint="/"}
  / node_filesystem_size_bytes{mountpoint="/"}  # доля свободного
node_memory_MemAvailable_bytes / 1024 / 1024    # в МиБ

После арифметики имени метрики у результата нет — только метки:

text
$ promtool query instant http://localhost:9090 \
    'node_memory_MemAvailable_bytes / 1024 / 1024'
{instance="localhost:9100", job="node"} => 292.01953125
  @[1792644275.651]

Сравнение >, <, >=, <=, ==, != работает как фильтр: оставляет ряды, для которых условие верно, со своим значением, а остальные убирает. Пустой ответ — условие не выполнено ни для одного ряда. Со словом bool сравнение возвращает каждый ряд со значением 1 или 0:

promql
up == 0                       # только упавшие цели
up == bool 1                  # все цели: 1 — жива, 0 — нет
node_load1 > 4                # ряды, где нагрузка выше 4

% в PromQL — остаток от деления (7 % 3 — 1), процентов в языке нет. «Меньше 10 %» пишут долей, < 0.1, или умножают долю на 100 и сравнивают с 10. … * 100 < 10% не разбирается: parse error: unexpected end of input.

Выражение правила алерта — такое же сравнение: алерт загорается для каждого ряда, который прошёл фильтр (prometheus).

Если наборы меток двух сторон различаются, пары не находятся и ответ пуст: у node_filesystem_avail_bytes есть mountpoint, device, fstype, у node_memory_MemTotal_bytes их нет. on (instance) сравнивает только перечисленные метки, ignoring (…) — все, кроме них. Если с одной стороны под пару подходят несколько рядов, нужен group_left:

promql
node_filesystem_avail_bytes
  / on (instance) group_left node_memory_MemTotal_bytes

and, or, unless работают с наборами рядов: a unless b — ряды a, для которых в b нет ряда с теми же метками. absent(…) возвращает 1, когда рядов нет совсем: absent(up{job="app"}) — задания app нет.

Мгновенный запрос и запрос за период

promtool query instant вычисляет выражение в один момент — сейчас. promtool query range — в каждой точке периода с шагом --step, как строит график Grafana:

text
$ promtool query range --start=$(date -d '-1 min' +%s) \
    --end=$(date +%s) --step=20s http://localhost:9090 'up{job="node"}'
up{instance="localhost:9100", job="node"} =>
1 @[1792644042]
1 @[1792644062]

То же через API — /api/v1/query и /api/v1/query_range. Выражение передают через --data-urlencode: в нём фигурные скобки, кавычки и пробелы. Значение в ответе — строкой, рядом с меткой времени:

text
$ curl -s localhost:9090/api/v1/query \
    --data-urlencode 'query=up{job="node"}' | jq -c '.data.result[]'
{"metric":{"__name__":"up","instance":"localhost:9100","job":"node"},
  "value":[1792643816.429,"1"]}

Длинные строки вывода здесь перенесены. Значение числом в jq — .value[1] | tonumber.

Рецепты

6

Какие цели упали

promql
up == 0

Пусто — все цели отвечают. Почему цель не отвечает, пишет поле lastError в API целей (prometheus).

Сколько процентов диска занято

promql
100 * (1 - node_filesystem_avail_bytes{mountpoint="/"}
         / node_filesystem_size_bytes{mountpoint="/"})

На учебной машине ответ — около 42: корень занят на 42 %. Для правила «диск почти полон» добавь сравнение: … > 90.

Загрузка процессора в процентах

promql
100 * (1 - avg by (instance)
         (rate(node_cpu_seconds_total{mode="idle"}[1m])))

avg by (instance) усредняет ядра машины: у машины с четырьмя ядрами четыре ряда mode="idle", а нужно одно число на машину.

Доля ошибок 5xx

promql
sum(rate(http_requests_total{code=~"5.."}[5m]))
  / sum(rate(http_requests_total[5m]))

Имя метрики и метки зависят от приложения: найди их в его /metrics. sum с обеих сторон убирает метки, и деление идёт число на число.

Метрика пропала

promql
absent(up{job="node"})

Возвращает 1, если рядов нет: задание удалили из конфига или в имени опечатка. up == 0 такой случай не ловит — ряда up просто нет.

Сколько рядов у Prometheus

promql
count({__name__=~"node_.+"})

На учебной машине один node_exporter даёт около 1300 рядов. Каждое новое значение метки — новый ряд: метка с идентификатором запроса или с адресом клиента раздувает базу.

Неполадки

5

Пустой ответ

promtool ничего не напечатал и вышел с кодом 0 — под выражение не попал ни один ряд:

  • опечатка в имени метрики или значении метки. Какие задания есть — curl -s localhost:9090/api/v1/label/job/values, какие ряды у метрики — /api/v1/series с --data-urlencode 'match[]=up';
  • сравнение отфильтровало всё — проверь выражение без > …;
  • метки двух сторон арифметики не совпали — см. promql;
  • окно rate короче двух интервалов сбора, или Prometheus запущен меньше минуты назад;
  • на машине с одной ВМ нет node_network_*: у неё только lo, а экспортёр его пропускает (node-exporter).

expected type range vector

text
query error: bad_data: invalid parameter "query": 1:6: parse error:
  expected type range vector in call to function "rate",
  got instant vector

У rate нет окна: rate(x) вместо rate(x[1m]).

unexpected identifier … expected string

text
1:8: parse error: unexpected identifier "node" in label matching,
  expected string
1:8: parse error: unexpected identifier "job" in grouping opts,
  expected "("

Первое — значение метки без кавычек: up{job=node} вместо up{job="node"}. Второе — метки группировки без скобок: sum by job вместо sum by (job). 1:8 — строка и позиция ошибки в выражении.

many-to-one matching must be explicit

text
query error: execution: multiple matches for labels:
  many-to-one matching must be explicit (group_left/group_right)

После on (…) или ignoring (…) одному ряду с одной стороны подходят несколько с другой. Если так и задумано — добавь group_left (много рядов слева) или group_right (справа).

Скорость скачет после перезапуска

Сырое значение счётчика упало до нуля — процесс перезапустился. rate и increase это учитывают. delta и вычитание x - x offset 5m — нет: на сбросе они дают отрицательное число.

Опасно

1

Тяжёлый запрос на маленькой машине

Prometheus держит выбранные точки в памяти, пока считает запрос. {__name__=~".+"} за [1d] или rate по всем рядам за неделю на машине с 512 МиБ могут занять всю память, и ядро завершит Prometheus (oom-killer). Начинай с узкого селектора и короткого окна.

Термины

5
временной ряд time series
Последовательность значений одной метрики с одним набором меток, у каждого значения — метка времени. Prometheus хранит ряды и считает по ним запросы PromQL. Ещё встречается в: Prometheus: сбор метрик и правила алертов
метка (label)
Пара имя="значение" у метрики: mountpoint="/", job="node". Уточняет, о чём число; по меткам выбирают ряды в запросе, группируют алерты и выбирают их маршрут. Ещё встречается в: Prometheus: сбор метрик и правила алертов, Alertmanager: кому и сколько раз уходят алерты, blkid, journalctl и ещё 15
счётчик (counter)
Метрика, которая только растёт с запуска процесса и обнуляется при перезапуске: байты, запросы, секунды процессора. Имя обычно на _total, читают через rate или increase. Ещё встречается в: PAM: проверка входа, политика паролей и блокировка, Классический файрвол iptables, node_exporter: метрики машины для Prometheus, PostgreSQL и ещё 11
gauge
Метрика с текущим значением, которое ходит вверх и вниз: свободная память, нагрузка, длина очереди. Читается как есть и сравнивается с порогом. Ещё встречается в: node_exporter: метрики машины для Prometheus
гистограмма (histogram)
Метрика-распределение: ряды _bucket{le="…"} считают наблюдения не больше порога le, _sum и _count — их сумму и число. Так меряют время ответа. Ещё встречается в: node_exporter: метрики машины для Prometheus

См. также

  • prometheus — сбор метрик, конфиг, правила и API.
  • node-exporter — метрики машины: процессор, память, диски.
  • alertmanager — что происходит с алертом после правила.
  • grafana — графики по тем же запросам.
  • date — перевести метку времени @[…] в дату.
  • jq — разобрать JSON-ответ API.
  • regex — синтаксис для =~.
Загрузка…