Модуль 2 · как работает prometheus · сбор метрик
Глава 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 показывает вместе с текстом заголовки ответа.
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, когда получает ответ.

Ответ состоит из строк одного вида. Чтобы читать любой ответ /metrics, достаточно разобрать одну такую строку.
§2Строка по частям
Строка метрики построена как строка складской ведомости: «Коробки · цвет: синий · склад: № 2 · 125 штук». Сначала сказано, что считают, затем идут уточняющие признаки, в конце стоит количество. В строке /metrics тот же порядок: имя метрики, метки в фигурных скобках, значение.
У каждой части свои правила записи: какие символы допустимы в имени, как пишется значение метки, в каком виде записывается число. Они собраны в блоке ниже. В нём та же строка стоит вместе с двумя строками описания. Нажмите на любую часть, чтобы увидеть её назначение и правила.
Правила отдельных частей складываются в формат всего ответа. Следующий раздел показывает, что происходит, когда формат нарушен.
§3Правила текстового формата
Формат ответа описан в документации Prometheus, в разделе Exposition formats. Основных правил шесть, и большинство из них уже встречалось в разборе строки.
- Одно значение занимает одну строку. Строки разделены переводом строки, последняя строка тоже им заканчивается.
- В строке значения сначала стоит имя, затем метки в фигурных скобках, затем через пробел число. Меток может не быть.
- Строка, которая начинается с
#, — комментарий. Prometheus читает из комментариев толькоHELPиTYPE. - Для одного имени допускается одна строка HELP и одна строка TYPE, и TYPE стоит до первого значения.
- Все строки одной метрики стоят вместе: нельзя записать часть её рядов, затем другую метрику, затем остальные ряды первой.
- Каждое сочетание имени и меток встречается один раз.
Правила проверяются с разной строгостью, и это видно только на опыте. Сначала о том, как ответ проверяется в целом. В бланке заявления десять полей. Если в одном поле дата записана неправильно, заявление возвращают целиком, и девять правильных полей отдельно не принимаются. Prometheus поступает с ответом так же: ошибка записи в одной строке из двухсот означает, что не сохраняется ни одна.
Чтобы выяснить, какие нарушения считаются ошибкой записи, на стенде был запущен сервер, который отдаёт ответы с одним нарушением каждый. Prometheus 3.5.0 опрашивал их все, и результаты разделились на две группы.
DOWN ответ отклонён целиком
UP ответ принят
В левой колонке собраны ошибки записи: строку нельзя прочитать как «имя, метки, число». В этих случаях ни одно значение из ответа не сохранено. В правой колонке собраны нарушения порядка и повторы: каждая строка читается, но стоит не там или встречается дважды. Prometheus 3.5 такие ответы принимает, а при повторе ряда сохраняет первое значение и отбрасывает второе.
Рассчитывать на правую колонку не следует по двум причинам. Другие программы, которые читают /metrics, проверяют эти правила строго. А при повторе ряда часть данных теряется, и в интерфейсе Prometheus об этом нет сообщения.
Подробнее: тексты ошибок и редкие правила
| Нарушение | Текст ошибки в Prometheus |
|---|---|
{method=GET} | expected label value, got "G" ("INVALID") |
c 61,5 | strconv.ParseFloat: parsing "61,5": invalid syntax |
# TYPE h countr | invalid 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.
# 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 секунды, и в это число входят запросы из первой корзины. На схеме те же шесть корзин показаны в масштабе.
Полосы только удлиняются, потому что каждая следующая корзина включает предыдущую. Последняя корзина +Inf обязательна и равна общему числу запросов. Теперь на вопрос из начала раздела есть ответ: дольше полсекунды выполнялось 144 320 − 129 389 = 14 931 запрос.
Кроме корзин, у гистограммы есть две итоговые строки. Строка _sum содержит сумму всех измерений: все запросы вместе заняли 53 423 секунды. Строка _count содержит их число: 144 320. Четвёртый тип, summary, решает похожую задачу иначе: приложение считает у себя, в какое время уложилась заданная доля запросов, и отдаёт готовые числа.
Все эти строки приложение не пишет вручную. Их готовит библиотека, и следующий раздел показывает, как код превращается в ответ.
§5Как ответ готовит prometheus-client
Работу библиотеки можно сравнить с сотрудником у входа, который нажимает кнопку ручного счётчика на каждого вошедшего. Когда его спрашивают, сколько людей пришло, он смотрит на счётчик и называет число. Приложение так же вызывает .inc() на каждый запрос, а когда приходит запрос /metrics, библиотека записывает все числа в виде текста.
В приложении стенда используется библиотека prometheus-client для Python. Объявление метрики и страница /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 читает ответ.
text/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 с текстом ошибки:
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 на стенде одна, здесь она перенесена для чтения.
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:
# 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 такой ответ примет. Имена в кавычках проверка не разбирает.
Начните с примера «повтор и запятая»: в нём встречаются оба уровня. Ряд 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.Повторного запроса нет. Следующая попытка будет через интервал опроса.
- Prometheus. Exposition formats — prometheus.io/docs/instrumenting/exposition_formats
- Prometheus. Metric and label naming — prometheus.io/docs/practices/naming
- Prometheus. Migration to 3.0: scrape protocols — prometheus.io/docs/prometheus/latest/migration
- OpenMetrics specification — github.com/prometheus/OpenMetrics
- prometheus-client для Python — prometheus.github.io/client_python
- Стенд модуля: запросы и вывод библиотеки получены 29.09.2026