Модуль 2 · Как работает Prometheus · Макаров М. Н.

Модуль 2 · как работает prometheus · сбор метрик

Глава 2.3

2.3

Endpoint /metrics
и текстовый формат

Target указывает адрес. В этой главе разобрано, что Prometheus получает по этому адресу. Сначала показан настоящий ответ приложения стенда. Затем разобраны части одной строки, правила записи, виды метрик и работа библиотеки prometheus-client. В конце — заголовок Content-Type и проверка своих строк.

Категория
Формат данных
Уровень
Средний
Опирается на
Глава 2.1: временной ряд; глава 2.2: target
Результат
Чтение и проверка любого ответа /metrics

§1Ответ приложения стенда

Target из главы 2.2 — это адрес. По нему находится страница, похожая на табло на вокзале. На табло написаны текущие сведения о поездах, и оно никому ничего не отправляет: каждый, кто подошёл, читает одни и те же строки. Страница /metrics устроена так же. На ней написаны текущие значения метрик, и приложение их никому не отправляет. Prometheus, браузер и команда curl получают один и тот же текст.

Теперь точнее. /metrics — это адрес внутри приложения, по которому оно на запрос GET отвечает текстом. Такой адрес называют словом endpoint. Имя /metrics общепринятое: Prometheus обращается по этому пути, если в задании не указан другой metrics_path.

Ниже настоящий ответ стенда. Перед запросом на стенд отправлено 125 запросов к товарам, три запроса к несуществующим товарам и один запрос POST. Ключ -i у команды curl показывает вместе с текстом заголовки ответа.

curl -i http://localhost:8020/metricsстенд модуля
HTTP/1.1 200 OK
server: uvicorn
content-length: 236
content-type: text/plain; version=1.0.0; charset=utf-8

# HELP http_requests_total Total HTTP requests
# TYPE http_requests_total counter
http_requests_total{method="GET",status="200"} 125.0
http_requests_total{method="GET",status="404"} 3.0
http_requests_total{method="POST",status="405"} 1.0

В ответе три части. Первые четыре строки — заголовки HTTP: код 200 OK и строка content-type, которая сообщает, в каком формате записан текст. Две строки, начинающиеся с #, описывают метрику: HELP содержит пояснение для человека, TYPE — вид метрики. Три последние строки содержат значения, и каждая из них — один временной ряд из главы 2.1.

Не меньше сообщает то, чего в ответе нет. В нём нет меток job и instance и нет времени. В приложении нет сведений о том, под каким адресом и в какой момент его опрашивают, поэтому метки target и время добавляет Prometheus, когда получает ответ.

Страница http://localhost:8020/metrics в браузере: строки HELP, TYPE и три строки http_requests_total
Тот же ответ в браузере: страница — обычный текст

Ответ состоит из строк одного вида. Чтобы читать любой ответ /metrics, достаточно разобрать одну такую строку.

§2Строка по частям

Строка метрики построена как строка складской ведомости: «Коробки · цвет: синий · склад: № 2 · 125 штук». Сначала сказано, что считают, затем идут уточняющие признаки, в конце стоит количество. В строке /metrics тот же порядок: имя метрики, метки в фигурных скобках, значение.

Три части строки значенияответ стенда
http_requests_totalИмя метрики: что считают
{method="GET",status="200"}Метки: о каких запросах число
125Значение: сколько

У каждой части свои правила записи: какие символы допустимы в имени, как пишется значение метки, в каком виде записывается число. Они собраны в блоке ниже. В нём та же строка стоит вместе с двумя строками описания. Нажмите на любую часть, чтобы увидеть её назначение и правила.

http_requests_total{method="GET",status="200"} 125разбор строки

Части строки выделены цветом: имя — оранжевым, метки — голубым, значение — зелёным.

# HELP http_requests_total Total HTTP requests # TYPE http_requests_total counter http_requests_total{method="GET",status="200"} 125
Строка HELP · описание

Пояснение для человека: слово HELP, имя метрики и произвольный текст. Prometheus показывает этот текст в подсказках интерфейса.

Строка необязательна. Для одного имени допускается одна строка HELP.

Строка TYPE · тип

Слово TYPE, имя метрики и её тип: counter, gauge, histogram, summary или untyped. Строка стоит до первого значения этой метрики. Если её нет, тип считается untyped — «не указан».

На хранение тип не влияет: в базе это такое же число. Тип подсказывает человеку и запросам, как с числом работать. Например, скорость роста считают только для counter.

Имя метрики

Состоит из латинских букв, цифр и знака _. Первый символ — буква или _.

Принятые правила: в конце имени пишут единицу измерения (_seconds, _bytes), у счётчика — окончание _total. В начале обычно стоит название части системы: http_, process_.

Имя метки

Состоит из латинских букв, цифр и знака _, первый символ — не цифра. Имена с двумя подчёркиваниями в начале заняты служебными метками Prometheus, например __address__ из главы 2.2.

Метки разделяются запятыми. Порядок меток значения не имеет: {status="200",method="GET"} — тот же ряд.

Значение метки

Всегда текст в двойных кавычках, даже когда он похож на число: status="200".

Если внутри значения нужна кавычка или обратная косая черта, перед ней ставят обратную косую черту: \", \\. Перевод строки записывают как \n.

Набор меток

Имя вместе со всеми метками определяет ряд. {method="GET",status="200"} и {method="GET",status="404"} — два разных ряда одной метрики.

В одном ответе каждое сочетание имени и меток записывают один раз.

Значение

Число. Допустимы записи 125, 125.0 и 1.25e2. Дробная часть отделяется точкой. Библиотека Python пишет 125.0; для Prometheus это то же значение 125.

Есть три особых значения: NaN (не число), +Inf и -Inf (бесконечность).

Правила отдельных частей складываются в формат всего ответа. Следующий раздел показывает, что происходит, когда формат нарушен.

§3Правила текстового формата

Формат ответа описан в документации Prometheus, в разделе Exposition formats. Основных правил шесть, и большинство из них уже встречалось в разборе строки.

  1. Одно значение занимает одну строку. Строки разделены переводом строки, последняя строка тоже им заканчивается.
  2. В строке значения сначала стоит имя, затем метки в фигурных скобках, затем через пробел число. Меток может не быть.
  3. Строка, которая начинается с #, — комментарий. Prometheus читает из комментариев только HELP и TYPE.
  4. Для одного имени допускается одна строка HELP и одна строка TYPE, и TYPE стоит до первого значения.
  5. Все строки одной метрики стоят вместе: нельзя записать часть её рядов, затем другую метрику, затем остальные ряды первой.
  6. Каждое сочетание имени и меток встречается один раз.

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

Чтобы выяснить, какие нарушения считаются ошибкой записи, на стенде был запущен сервер, который отдаёт ответы с одним нарушением каждый. Prometheus 3.5.0 опрашивал их все, и результаты разделились на две группы.

Результат опроса при разных нарушенияхPrometheus 3.5.0 · стенд
DOWN ответ отклонён целиком
{method=GET}Значение метки без кавычек
c 61,5Запятая в числе вместо точки
# TYPE h countrНеизвестный тип
g{a="1",a="2"}Одна метка записана дважды
UP ответ принят
TYPE стоит после значений
Строки одной метрики стоят не вместе
Два TYPE или два HELP для одного имени
Нет перевода строки в конце ответа
Один ряд записан дважды с разными значениями

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

Рассчитывать на правую колонку не следует по двум причинам. Другие программы, которые читают /metrics, проверяют эти правила строго. А при повторе ряда часть данных теряется, и в интерфейсе Prometheus об этом нет сообщения.

Подробнее: тексты ошибок и редкие правила
НарушениеТекст ошибки в Prometheus
{method=GET}expected label value, got "G" ("INVALID")
c 61,5strconv.ParseFloat: parsing "61,5": invalid syntax
# TYPE h countrinvalid metric type "countr"
g{a="1",a="2"}label name "a" is not unique: invalid sample
Ряд записан дваждыОшибки в интерфейсе нет. В журнале сервера: Error on ingesting samples with different value but same timestamp

Формат появился в апреле 2014 года. Текст записывается в кодировке UTF-8. Между частями строки допустимо любое число пробелов.

После значения через пробел может стоять отметка времени в миллисекундах. Библиотеки её не пишут, и Prometheus ставит время начала опроса.

Prometheus 3 принимает имена с другими символами, например с точками. Такие имена записывают в кавычках внутри скобок: {"my.dotted.metric", "error.message"="Not Found"}.

Одно из правил касается строки TYPE. В ней записан тип метрики, и от типа зависит, сколько строк метрика занимает в ответе.

§4Типы метрик в тексте

Два типа уже знакомы по главе 2.1. Counter считает события с запуска программы и только растёт, как одометр. Gauge показывает текущую величину и меняется в обе стороны, как указатель топлива. В ответе оба занимают одну строку на ряд, а имя счётчика по правилам заканчивается на _total.

Третий тип, histogram, устроен сложнее: он описывает не одно число, а распределение. Нужен он для вопросов вида «сколько запросов выполнялось дольше полсекунды». Одним числом на такой вопрос не ответить, и гистограмма хранит несколько счётчиков сразу.

Сравнение из жизни
Учёт посылок на почте

Почта ведёт три счёта: посылки «до 1 кг», «до 5 кг» и «любого веса». Посылку весом 800 граммов засчитывают во все три счёта: она легче 1 кг, легче 5 кг и входит в общее число.

Гистограмма длительности запросов

Приложение ведёт счёт запросов «не дольше 0,05 с», «не дольше 0,1 с» и так далее. Быстрый запрос засчитывается во все корзины, границы которых он не превысил.

Каждый такой счёт называется корзиной. В ответе корзина — это строка с окончанием _bucket и меткой le, сокращение от less or equal, «меньше или равно». В метке записана верхняя граница корзины в секундах. Пример взят из документации Prometheus.

Histogram в текстовом форматепример из документации
# HELP http_request_duration_seconds A histogram of the request duration.
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.05"} 24054
http_request_duration_seconds_bucket{le="0.1"} 33444
http_request_duration_seconds_bucket{le="0.2"} 100392
http_request_duration_seconds_bucket{le="0.5"} 129389
http_request_duration_seconds_bucket{le="1"} 133988
http_request_duration_seconds_bucket{le="+Inf"} 144320
http_request_duration_seconds_sum 53423
http_request_duration_seconds_count 144320

Первая строка значений читается так: 24 054 запроса заняли не больше 0,05 секунды. Вторая: 33 444 запроса заняли не больше 0,1 секунды, и в это число входят запросы из первой корзины. На схеме те же шесть корзин показаны в масштабе.

Корзины той же гистограммы в масштабечисло запросов
не дольше 0,05 с24 054 не дольше 0,1 с33 444 не дольше 0,2 с100 392 не дольше 0,5 с129 389 не дольше 1 с133 988 любая длительность, +Inf144 320

Полосы только удлиняются, потому что каждая следующая корзина включает предыдущую. Последняя корзина +Inf обязательна и равна общему числу запросов. Теперь на вопрос из начала раздела есть ответ: дольше полсекунды выполнялось 144 320 − 129 389 = 14 931 запрос.

Кроме корзин, у гистограммы есть две итоговые строки. Строка _sum содержит сумму всех измерений: все запросы вместе заняли 53 423 секунды. Строка _count содержит их число: 144 320. Четвёртый тип, summary, решает похожую задачу иначе: приложение считает у себя, в какое время уложилась заданная доля запросов, и отдаёт готовые числа.

Все эти строки приложение не пишет вручную. Их готовит библиотека, и следующий раздел показывает, как код превращается в ответ.

§5Как ответ готовит prometheus-client

Работу библиотеки можно сравнить с сотрудником у входа, который нажимает кнопку ручного счётчика на каждого вошедшего. Когда его спрашивают, сколько людей пришло, он смотрит на счётчик и называет число. Приложение так же вызывает .inc() на каждый запрос, а когда приходит запрос /metrics, библиотека записывает все числа в виде текста.

В приложении стенда используется библиотека prometheus-client для Python. Объявление метрики и страница /metrics занимают в нём двенадцать строк.

stand/app.pyобъявление метрики и /metrics
registry = CollectorRegistry()

HTTP_REQUESTS = Counter(
    "http_requests_total",
    "Total HTTP requests",
    ["method", "status"],
    registry=registry,
)

@app.get("/metrics")
async def metrics():
    return Response(generate_latest(registry), media_type=CONTENT_TYPE_LATEST)

Каждой части ответа из §1 соответствует своё место в коде. Первый аргумент Counter — имя метрики, оно стоит в начале каждой строки значения. Второй аргумент — текст строки HELP. Само слово Counter даёт строку # TYPE … counter. Список ["method", "status"] задаёт имена меток. Функция generate_latest записывает все метрики реестра в виде текста, а CONTENT_TYPE_LATEST — это значение заголовка content-type. Реестр registry — список метрик приложения. Место, где вызывается .inc(), показывает глава 2.6.

Три особенности библиотеки

По коду не всё видно заранее. У библиотеки есть три особенности, которые заметны только в ответе. Все они проверены запуском prometheus-client 0.26.0.

Ряд появляется после первого события. Пока не было ни одного вызова .inc(), в ответе стоят только строки HELP и TYPE. Причина в метках: их значения заранее в библиотеку не записаны, она получает их при первом событии. На стенде после перезапуска приложения запрос http_requests_total возвращал пустой результат до первого запроса к приложению. Пустой результат и значение 0 — разные состояния. Чтобы ряд с нулём появился сразу, его создают вызовом HTTP_REQUESTS.labels("GET", "500") без .inc().

Ряд _created. Без дополнительной настройки у каждого ряда счётчика появляется второй ряд со временем его создания: http_requests_created{method="GET",status="200"} 1.790686414305862e+09. Число рядов счётчиков от этого удваивается. На стенде этот ряд отключён вызовом disable_created_metrics().

Метрики самого Python. Если не создавать свой реестр CollectorRegistry, метрики попадают в общий реестр библиотеки. Тогда в ответе появляются метрики интерпретатора, например python_gc_objects_collected_total и python_info, а в Linux ещё метрики процесса process_*: время процессора и память. В рабочем сервисе их оставляют. На стенде свой реестр оставляет в ответе одну метрику, чтобы ответ помещался на экране.

Подробнее: имя счётчика, gauge без меток, process_* в macOS

Имя счётчика можно передать библиотеке с окончанием _total или без него. В обоих случаях строка значения в тексте Prometheus получит _total.

Gauge без меток появляется в ответе сразу со значением 0.0: у него один ряд, и он создаётся вместе с метрикой.

В macOS метрик process_* нет. Библиотека читает их из папки /proc, которая есть только в Linux. Ряд _created отключает также переменная окружения PROMETHEUS_DISABLE_CREATED_SERIES=True.

В коде осталась одна неразобранная часть: CONTENT_TYPE_LATEST. Этот заголовок сообщает Prometheus формат ответа, и без него ответ не будет принят.

§6Content-Type и выбор формата

Текст из §3 — один из нескольких форматов, в которых приложение может отдать метрики. Поэтому перед чтением ответа Prometheus должен узнать, в каком формате он записан. Об этом стороны договариваются двумя заголовками HTTP, и происходит это так же, как в кафе. Гость говорит: «Лучше меню на русском, можно на английском». Официант приносит меню и называет, на каком оно языке.

Prometheus в заголовке запроса Accept перечисляет форматы, которые он принимает. Приложение в заголовке ответа Content-Type называет формат, в котором ответило. Если этот формат есть в списке, Prometheus читает ответ.

Обмен заголовками при одном опросестенд модуля
запрос · PrometheusAcceptпринимаю OpenMetrics и текст Prometheus
ответ · приложениеContent-Typetext/plain; version=1.0.0
результатОтвет прочитанформат есть в списке

Форматов три. Текст Prometheus из §3 имеет заголовок text/plain с версией 0.0.4 или 1.0.0, его отдаёт стенд. OpenMetrics — стандарт на основе этого текста с заголовком application/openmetrics-text; в конце такого ответа обязательна строка # EOF. Третий формат, protobuf, двоичный: человек его прочитать не может.

Что будет, если официант принесёт меню на языке, которого гость не называл? Начиная с версии 3.0 Prometheus такой ответ не читает. На стенде это проверено: адрес /metrics/html отдаёт те же правильные строки, но с заголовком text/html. Target перешёл в состояние DOWN с текстом ошибки:

Status → Target health, job htmlPrometheus 3.5.0
Error scraping target: received unsupported Content-Type "text/html; charset=utf-8" and no fallback_scrape_protocol specified for target

Строки метрик записаны без ошибок, но ответ отклонён: Prometheus не начал их читать. Prometheus 2 в этом случае разбирал ответ как текст. Для программ, код которых исправить нельзя, в задании указывают параметр fallback_scrape_protocol: PrometheusText0.0.4. Он называет формат, которым читать ответ с неправильным заголовком.

Подробнее: настоящий запрос Prometheus и та же метрика в OpenMetrics

Запрос Prometheus 3.5.0 записан на стенде сервером, который печатает входящие заголовки. Строка Accept на стенде одна, здесь она перенесена для чтения.

Запрос Prometheus к targetзаписан на стенде
GET /metrics HTTP/1.1
Host: echo:8000
User-Agent: Prometheus/3.5.0
Accept: application/openmetrics-text;version=1.0.0;escaping=allow-utf-8;q=0.6,
        application/openmetrics-text;version=0.0.1;q=0.5,
        text/plain;version=1.0.0;escaping=allow-utf-8;q=0.4,
        text/plain;version=0.0.4;q=0.3,*/*;q=0.2
Accept-Encoding: gzip
X-Prometheus-Scrape-Timeout-Seconds: 10

Числа q показывают предпочтения: чем больше число, тем желательнее формат. OpenMetrics 1.0 (0,6) стоит выше текста Prometheus (0,4 и 0,3). Порядок задаёт параметр scrape_protocols. Заголовок Accept-Encoding: gzip разрешает сжатый ответ. Заголовок X-Prometheus-Scrape-Timeout-Seconds сообщает приложению, сколько секунд Prometheus будет ждать.

Приложение стенда отвечает текстом Prometheus на любой Accept: функция generate_latest заголовок запроса не читает. Формат по заголовку выбирает готовый обработчик библиотеки make_asgi_app().

Та же метрика в формате OpenMetrics, как её выводит prometheus-client 0.26.0:

prometheus_client.openmetrics.exposition.generate_latestprometheus-client 0.26.0
# HELP http_requests Total HTTP requests
# TYPE http_requests counter
http_requests_total{method="GET",status="200"} 125.0
http_requests_created{method="GET",status="200"} 1.79068992362875e+09
# EOF

Отличия от текста Prometheus: в HELP и TYPE имя счётчика записано без _total, есть ряд _created, в конце стоит # EOF. Формат protobuf Prometheus запрашивает, когда включены нативные гистограммы.

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

§7Проверка своих строк

В поле ниже записан ответ стенда. Измените его или выберите готовый пример и нажмите «проверить» (или Ctrl+Enter). Проверка применяет правила из §3 к каждой строке и делит замечания на те же две группы, что и опыт на стенде. Ошибка означает, что Prometheus 3.5 отклонит такой ответ. Нарушение означает, что Prometheus 3.5 такой ответ примет. Имена в кавычках проверка не разбирает.

Проверка текстового форматаправила Prometheus 0.0.4

    Начните с примера «повтор и запятая»: в нём встречаются оба уровня. Ряд queue_length{queue="emails"} записан дважды. Это нарушение: Prometheus сохранит значение 3 и отбросит 4. Значение 61,5 записано с запятой. Это ошибка: дробная часть отделяется только точкой, и из-за одной этой строки будет отклонён весь ответ.

    §8Проверьте себя

    Вопрос 1Почему в ответе /metrics нет меток job и instance?

    • Верно. В приложении нет сведений о задании и адресе, под которыми его опрашивают. Если приложение отдаст свою метку job, она сохранится как exported_job.

    • Ответ одинаков для любого клиента: Prometheus, curl и браузер получают один и тот же текст.

    • В строке TYPE только имя и тип метрики.

    Вопрос 2В ответе 200 строк, одна из них — queue_length{queue=emails} 3. Что сохранит Prometheus?

    • Prometheus строки не пропускает: ошибка в любой строке останавливает разбор всего ответа.

    • Верно. Значение метки без кавычек — ошибка записи. Ряд up станет равен 0, и ни одно значение из ответа не будет сохранено.

    • Значение метки должно стоять в двойных кавычках. Без кавычек Prometheus его не читает.

    Вопрос 3Приложение только что перезапущено, запросов ещё не было. Что вернёт Prometheus на запрос http_requests_total?

    • Счётчик с метками не создаёт рядов до первого события: значения меток появляются вместе с событием.

    • Верно, так было на стенде. В ответе /metrics только HELP и TYPE. Ряды с нулём создают заранее вызовом .labels(...).

    • Новая программа отдаёт только свои значения. Старые ряды после неудачного опроса перестают показываться (глава 2.4).

    Вопрос 4Приложение отдаёт правильный текст с заголовком Content-Type: text/html. Что произойдёт в Prometheus 3.5?

    • Так работал Prometheus 2. Начиная с версии 3.0 для этого нужен параметр fallback_scrape_protocol.

    • Верно, это сообщение получено на стенде для адреса /metrics/html.

    • Повторного запроса нет. Следующая попытка будет через интервал опроса.

    Источники главы
    1. Prometheus. Exposition formats — prometheus.io/docs/instrumenting/exposition_formats
    2. Prometheus. Metric and label naming — prometheus.io/docs/practices/naming
    3. Prometheus. Migration to 3.0: scrape protocols — prometheus.io/docs/prometheus/latest/migration
    4. OpenMetrics specification — github.com/prometheus/OpenMetrics
    5. prometheus-client для Python — prometheus.github.io/client_python
    6. Стенд модуля: запросы и вывод библиотеки получены 29.09.2026
    Метрики и наблюдаемость · Модуль 2 · Как работает Prometheus Автор: Макаров Максим Николаевич