Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 11 из 25 ~16 мин +20 XP

Встроенные метрики процесса Go

Среди go метрик процесса, которые client_golang отдаёт сразу после запуска, есть всё для первой линии обороны: горутины, память, паузы сборщика мусора, файловые дескрипторы и процессорное время. Ни одна из этих серий не потребовала от вас ни строчки кода: два встроенных коллектора регистрируются в реестре сами. Разберём, что лежит в /metrics бесплатно, как читать каждую группу серий и в какой момент встроенного набора перестаёт хватать.

Содержание урока · 10 разделов

Откройте /metrics сервиса из третьего урока и посчитайте строки, которые вы не писали: их окажется больше, чем написанных. Несколько десятков серий с префиксами go_ и process_ появляются при старте процесса, и это не мусор, а результат работы двух коллекторов, которые client_golang регистрирует в реестре по умолчанию. GoCollector снимает данные из рантайма: горутины, потоки операционной системы, статистику кучи и сборщика мусора. ProcessCollector читает то, что операционная система знает о процессе: процессорное время, резидентную память, открытые файловые дескрипторы, момент старта. Разделение не случайное: префикс go_ — внутренний мир рантайма Go, префикс process_ — то, как процесс выглядит снаружи. Первые рассказывают о поведении вашей программы, вторые — о потреблении ресурсов. Практический вывод: если сервис из урока про первый /metrics endpoint уже поднят, половина мониторинга у вас есть, и осталось только научиться её читать.

Go метрики процесса: состав встроенного набора#

Самая частая серия — go_goroutines: gauge с числом живых горутин на момент скрейпа. Рядом go_threads: потоки ОС, которые рантайм держит под планировщик; их число растёт при блокирующих системных вызовах и возвращается не всегда — это нормально. Память занимает целая семья go_memstats_*: go_memstats_alloc_bytes — байты под живые объекты, go_memstats_sys_bytes — сколько памяти рантайм взял у системы, go_memstats_heap_inuse_bytes — куча, находящаяся в работе. Сборщик мусора представлен серией go_gc_duration_seconds — это summary с квантилями 0, 0.25, 0.5, 0.75 и 1: квантиль 1 показывает худшую паузу за жизнь процесса, а через _sum и _count выводится средняя. Отдельная серия go_info с лейблом версии — gauge со значением 1, маркирующий, каким компилятором собран процесс. Из process_-набора чаще всего смотрят process_cpu_seconds_total, process_resident_memory_bytes, process_open_fds и process_start_time_seconds — unix-время старта, из которого делается график аптайма.

Endpoint, в котором нет ни одной вашей метрики

GO
package main

import (
        "net/http"

        "github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
        http.Handle("/metrics", promhttp.Handler())
        http.ListenAndServe(":8080", nil)
}

Шесть строк, а /metrics уже отдаёт десятки серий. promhttp.Handler() собирает метрики из DefaultGatherer — глобального реестра, в котором при импорте пакета уже зарегистрированы GoCollector и ProcessCollector. Проверьте локальным curl: curl localhost:8080/metrics | head — среди первых строк будут go_ и process_ серии ещё до любого вашего запроса.

Как читать экспозицию встроенных серий

GO
go_goroutines 42
go_threads 8
go_gc_duration_seconds{quantile="0.5"} 0.000231
go_gc_duration_seconds{quantile="1"} 0.004012
go_memstats_alloc_bytes 8.7e+06
go_info{version="go1.22.5"} 1
process_cpu_seconds_total 12.4
process_resident_memory_bytes 3.2e+07
process_open_fds 27
process_start_time_seconds 1.7212e+09

Числа записаны в научной нотации: 8.7e+06 — это 8.7 мегабайта кучи, 3.2e+07 — около 32 мегабайт резидентной памяти, 1.7212e+09 — unix-время старта. Типы различаются по содержанию: go_goroutines и process_open_fds — gauge, process_cpu_seconds_total — counter, значения которого растут и с дробной частью. process_start_time_seconds редко смотрят напрямую: из него строят аптайм выражением time() - process_start_time_seconds.

Свой реестр: NewRegistry и потерянные дефолты#

Глобальный реестр удобен приложению, но библиотеке он не подходит: регистрируя метрики в DefaultRegisterer, вы мусорите в чужом процессе. Поэтому в библиотеках и тестах берут собственный: prometheus.NewRegistry() возвращает пустой изолированный набор, в котором есть ровно то, что вы зарегистрировали сами. И здесь поджидает ловушка из заголовка: go_ и process_ серии в новом реестре не появятся, потому что их туда никто не положил. Возвращаются они явно: prometheus.NewGoCollector() и prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}) регистрируются в вашем реестре вместе с бизнес-метриками. Пустые ProcessCollectorOpts означают отсутствие namespace — имена останутся process_, а не myapp_process_. Для экспорта реестра нужен уже не promhttp.Handler(), а promhttp.HandlerFor(reg, promhttp.HandlerOpts{}), который собирает выдачу именно из вашего набора. В свежих версиях client_golang есть и тонкая настройка через пакет collectors — дополнительные метрики рантайма по runtime/metrics, — но это опциональный слой, для старта хватает базовой пары.

Свой реестр с полным набором

GO
var reg = prometheus.NewRegistry()
var ordersTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{Name: "orders_total", Help: "Заказы"},
        []string{"status"},
)

func main() {
        reg.MustRegister(
                prometheus.NewGoCollector(),
                prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}),
                ordersTotal,
        )
        http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))
        http.ListenAndServe(":8080", nil)
}

Реестр — обычная переменная пакета: его удобно передавать в тесты и собирать под конкретный набор. NewGoCollector и NewProcessCollector создаются при регистрации, хранить их в отдельных переменных незачем. HandlerFor вместо Handler — принципиальная строка: без неё вы снова экспортировали бы глобальный реестр, и заказы из ordersTotal не попали бы в выдачу.

Goroutines метрика: индикатор утечки под рукой#

Из всего встроенного набора чаще всего спасает именно goroutines метрика. Нормальный профиль сервиса стабилен: число горутин зависит от пула воркеров и наплыва трафика, поднимается в пик-час, возвращается к ночной отметке. Опасная картина другая: линия растёт ступеньками после каждого релиза и никогда не опускается — значит, какая-то горутина не завершается. Классические причины — бесконечный цикл без условия выхода, чтение из канала, который никто не закроет, запрос без таймаута во внешний API. Каждая зависшая горутина висит и в памяти. График go_goroutines за неделю находит такую утечку раньше алерта по памяти: горутины занимают килобайты, и до OOM иногда неделями тишина, а линия-то уже ползёт. Разбор ловушек с каналами и счётчиками собственных горутин — в уроке про метрики горутин, а основы самих горутин при желании освежите в курсе продвинутого Go.

go_threads, GOMAXPROCS и процессор в контейнере#

Пара go_goroutines и go_threads демонстрирует модель M:N: горутин могут быть тысячи, а потоков ОС — в пределах пары десятков, потому что планировщик рантайма мультиплексирует первые на вторые. Скачок go_threads без роста горутин подсказывает на блокирующие системные вызовы: файловые операции и cgo держат поток занятым. Процессор же честнее смотреть по process_cpu_seconds_total: rate(process_cpu_seconds_total[5m]) даёт потребление в ядрах, и в контейнере это число сравнивают с лимитом из cgroup. Нюанс, о котором больно узнают на дежурстве: исторически рантайм читал GOMAXPROCS из числа ядер машины и не знал о лимитах контейнера, поэтому подожатый лимитом сервис строил очереди планировщика внутри себя — деградация латентности при спокойном CPU-графике. В свежих версиях Go поведение починили, но проверьте версию рантайма через лейбл go_info, прежде чем списывать всё на троттлинг.

Шпаргалка: встроенные серии и их смысл#

СерияТипО чём сигналит
go_goroutinesgaugeчисло горутин: ступенчатый рост без возврата — утечка
go_gc_duration_secondssummaryпаузы GC: квантиль 1 — худшая за жизнь процесса
go_memstats_alloc_bytesgaugeживые объекты кучи
process_cpu_seconds_totalcounterсуммарное процессорное время с начала работы
process_resident_memory_bytesgaugeпамять, занятая у операционной системы
process_open_fdsgaugeдескрипторы: вечный рост — незакрытые файлы и соединения
process_start_time_secondsgaugeвремя старта: база для графика аптайма

Первые графики из встроенных серий#

Четыре графика собираются из встроенных серий без единой собственной метрики, и каждый закрывает частый вопрос дежурства. Скорость потребления CPU: rate(process_cpu_seconds_total[5m]) — линия в ядрах, на которой троттлинг виден как упор в лимит. Резидентная память: process_resident_memory_bytes в динамике — медленный вечный рост при ровной куче подсказывает фрагментацию или побочный эффект очередей. Дескрипторы: process_open_fds за неделю — идеальный детектор незакрытых файлов и тел ответов, потому что дескрипторы конечны и лимит на них у процесса жёсткий. Аптайм: time() - process_start_time_seconds рисует пилу перезапусков — на одном экране видно, сколько раз сервис перезапустился за сутки, даже если никто не вёл журнал. Эти четыре панели стоит сделать первыми: они работают для любого Go-сервиса одинаково, и шаблон для них живёт в уроке про Grafana.

Когда встроенного набора мало#

Встроенные метрики отвечают на вопрос «здоров ли процесс», но не на вопрос «здоров ли сервис». В них нет доли ошибок по маршрутам, нет латентности обработки, нет числа заказов и конверсий — всё это ваши метрики из предыдущих уроков. Практичный порядок такой: встроенный набор включён всегда, он бесплатен и ловит проблемы самого рантайма; поведенческие метрики вы добавляете по мере того, как сервис обрастает логикой, начиная с бизнес-событий. Полезно и обратное знание: если тестовый сервис внезапно отдаёт тысячи серий, проверьте, не создаёте ли вы вектор на каждый запрос — MustRegister паникует на дубликатах регистрации, а вот новый вектор с тем же именем плодит серии молча. Разделение «процесс» и «поведение» экономит время разбора: процесс здоров, а сервис болеет — виновата логика, и искать нужно в собственных сериях.

Проверка: тип метрики

К какому типу относится go_goroutines?

Проверка: свой реестр

Вы создали prometheus.NewRegistry(), зарегистрировали в нём только свой счётчик и экспортировали через HandlerFor. Что увидит скрейп?

Научная нотация

Прочитайте код и выберите, что он выведет:

process_resident_memory_bytes 3.2e+07

Соберите свой реестр

Допишите создание изолированного реестра с рантайм-коллектором и его экспорт.

var reg = prometheus.() reg.MustRegister( prometheus.(), prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{}), ordersTotal, ) http.Handle("/metrics", promhttp.(reg, promhttp.HandlerOpts{}))

Порядок регистрации

Расставьте строки сервиса с изолированным реестром в правильном порядке.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: встроенные метрики

Термин

go_goroutines

нажмите, чтобы перевернуть
Определение

Gauge из GoCollector: число живых горутин на момент скрейпа. Стабильный профиль — норма; ступенчатый рост без возврата — признак утечки.

1 / 6

Следующий шаг — урок про PromQL: все серии из таблицы выше станут аргументами запросов rate и histogram_quantile, а пока закрепите чтение экспозиции. Для этого нужен локальный Go: браузерная песочница сайта client_golang не исполняет, поэтому запустите минимальный сервис из этого урока и почитайте /metrics глазами. Сравните go_memstats_alloc_bytes и process_resident_memory_bytes до и после нагрузки из hey — разница в поведении кучи и RSS станет нагляднее любой теории. Заодно посмотрите на go_gc_duration_seconds{quantile="1"} до и после: худшая пауза покажет, насколько сборщик мешает латентности. Полная программа курса — на странице Prometheus; если вы перескакивали уроки, вернитесь к материалу про первый /metrics endpoint, где дефолтный реестр фигурирует впервые.

Песочница урока

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

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • GoCollector и ProcessCollector регистрируются в реестре по умолчанию: десятки серий go_ и process_ в /metrics без единой строки кода.
  • go_goroutines — главный индикатор утечки: ступенчатый рост без возврата означает незавершающиеся горутины.
  • go_gc_duration_seconds — summary пауз GC; process_cpu_seconds_total — counter процессорного времени; RSS смотрят по process_resident_memory_bytes.
  • Свой реестр prometheus.NewRegistry() пуст: go_ и process_ возвращают явно через NewGoCollector и NewProcessCollector.
  • Экспорт изолированного реестра — promhttp.HandlerFor, глобального — promhttp.Handler().
  • alloc и RSS — про разное: живые объекты кучи против памяти, занятой у ОС; смотрят обе серии.
  • Встроенные метрики отвечают за здоровье процесса; поведение сервиса описывают ваши собственные метрики.

Домашнее задание

Запустите локально минимальный сервис урока и изучите /metrics: найдите go_goroutines, go_gc_duration_seconds и process_start_time_seconds. Соберите изолированный реестр с NewGoCollector, NewProcessCollector и своим счётчиком, убедитесь curl-ом, что в выдаче есть и встроенные, и ваши серии. Потом уберите GoCollector из регистрации и посмотрите, что исчезнет. Песочница client_golang не исполняет — нужен локальный Go.

Личные заметкиПусто
Хранятся только в вашем браузере (Prometheus · Встроенные метрики процесса Go)

Вопросы и ответы по уроку

Сколько серий добавляют встроенные коллекторы?

Около сорока суммарно на процесс: несколько десятков go_-серий, включая память и GC, плюс процессорное время, память, дескрипторы и время старта из process_-набора. Объём стабилен и не растёт со временем, поэтому встроенный набор включают всегда.

process_ метрики одинаковы на всех платформах?

Нет. На Linux ProcessCollector читает /proc и отдаёт полный набор; на других ОС часть серий может отсутствовать или считаться иначе. Прод-мониторинг почти всегда Linux, так что расхождение затрагивает в основном локальную разработку.

Нужно ли регистрировать GoCollector в глобальном реестре самому?

Нет, при импорте client_golang дефолтный реестр уже содержит GoCollector и ProcessCollector. Явная регистрация нужна только в собственных реестрах — повторная в глобальном приведёт к панике MustRegister о дубликате.

Термины урока

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

Следующая ступень учебного пути

WebSocket25 уроков~13 чпрактика25 уроков уже доступны

WebSocket в Go: real-time чаты и уведомления

Курс WebSocket на Go: gorilla/websocket, эхо-сервер, hub-паттерн, комнаты, авторизация соединений, ping/pong, reconnect и масштабирование. 25 уроков.

  • Поднимать WS-сервер и апгрейдить HTTP-соединения
  • Писать readPump/writePump без гонок на каждое соединение
  • Строить hub: регистрацию, рассылку и комнаты
  • Авторизовывать соединения и ограничивать сообщения