Диагностика
Введение
Экосистема Go предоставляет обширный набор API и инструментов для диагностики логических и производственных проблем в программах на Go. Эта страница кратко описывает доступные инструменты и помогает разработчикам выбрать подходящий инструмент для решения их конкретной задачи.
Решения для диагностики можно разделить на следующие группы:
- Профилирование: Инструменты профилирования анализируют сложность и затраты программы на Go, такие как использование памяти и часто вызываемые функции, чтобы выявить наиболее затратные участки кода.
- Трассировка: Трассировка — это способ инструментирования кода для анализа задержек на протяжении жизненного цикла вызова или пользовательского запроса. Трассировки предоставляют обзор того, какую долю задержки каждый компонент вносит в общую задержку в системе. Трассировки могут охватывать несколько процессов на Go.
- Отладка: Отладка позволяет приостановить выполнение программы на Go и проанализировать её состояние. С помощью отладки можно проверить состояние и ход выполнения программы.
- Статистика и события среды выполнения: Сбор и анализ статистики и событий среды выполнения предоставляют общую картину здоровья программ на Go. Пики и спады метрик помогают выявить изменения пропускной способности, загрузки и производительности.
Примечание: Некоторые инструменты диагностики могут мешать друг другу. Например, точное профилирование памяти искажает CPU профили, а профилирование блокировки горутин влияет на трассировку планировщика. Для получения более точной информации используйте инструменты по отдельности.
Профилирование
Профилирование полезно для выявления наиболее затратных или часто вызываемых участков кода. Среда выполнения Go
предоставляет данные профилирования в формате, ожидаемом инструментом визуализации pprof. Данные
профилирования можно собирать во время тестирования с помощью go test или через конечные
точки, предоставляемые пакетом net/http/pprof. Пользователям необходимо собирать
данные профилирования и использовать инструменты pprof для фильтрации и визуализации наиболее значимых путей
выполнения кода.
Предопределённые профили, предоставляемые пакетом runtime/pprof:
- cpu : Профиль CPU определяет, где программа тратит своё время во время активного потребления циклов процессора (в отличие от времени, когда программа спит или ожидает ввода-вывода).
- heap: Профиль кучи (heap profile) сообщает о выборках выделения памяти; используется для мониторинга текущего и исторического использования памяти, а также для проверки на утечки памяти.
- threadcreate: Профиль создания потоков (thread creation profile) отчитывается о разделах программы, которые приводят к созданию новых операционных потоков (OS threads).
- goroutine: Профиль горутин (goroutine profile) отчитывается о трассировках стека всех текущих горутин.
-
block: Профиль блокировок (block profile) показывает, где горутины блокируются, ожидая
синхронизационных примитивов (включая таймерные каналы). Профиль блокировок по умолчанию не включён; чтобы его
включить, используйте
runtime.SetBlockProfileRate. -
mutex: Профиль мьютексов (mutex profile) отчитывается о конфликтах блокировок. Если вы
подозреваете, что ваш процессор не полностью загружен из-за конфликтов мьютексов, используйте этот профиль.
Профиль мьютексов по умолчанию не включён, см.
runtime.SetMutexProfileFractionдля его включения.
Какие ещё профайлеры можно использовать для профилирования программ на Go?
В Linux можно использовать perf tools для профилирования программ на Go. Perf может профилировать и раскручивать cgo/SWIG код и ядро, поэтому он может быть полезен для получения информации о производительности на уровне нативного/ядра. В macOS, Instruments можно использовать для профилирования программ на Go.
Можно ли профилировать сервисы в продакшене?
Да. Безопасно профилировать программы в продакшене, но включение некоторых профилей (например, профиля CPU) добавляет издержки. Следует ожидать снижения производительности. Потенциальную нагрузку можно оценить, измерив накладные расходы профайлера до его включения в продакшен.
Можно периодически профилировать сервисы в продакшене. Особенно в системе с множеством реплик одного процесса, выбор случайной реплики периодически — безопасная опция. Выберите продакшен-процесс, профилируйте его в течение X секунд каждые Y секунд и сохраняйте результаты для визуализации и анализа; затем повторяйте периодически. Результаты можно вручную и/или автоматически просматривать, чтобы выявить проблемы. Сбор профилей может мешать друг другу, поэтому рекомендуется собирать только один профиль за раз.
Какие лучшие способы визуализации данных профилирования существуют?
Инструменты Go предоставляют текстовую, графическую и callgrind
визуализацию данных профилей с использованием
go tool pprof.
Ознакомьтесь с Profiling Go programs
чтобы увидеть их в действии.
Список самых затратных вызовов в текстовом виде.
Визуализация самых затратных вызовов в виде графа.
Представление в виде weblist отображает самые затратные части исходного кода построчно в HTML-странице. В следующем
примере показано, что 530 мс затрачено на runtime.concatstrings, а стоимость каждой строки представлена
в списке.
Визуализация самых затратных вызовов в виде weblist.
Еще один способ визуализации данных профилирования — это flame graph. Flame graphs позволяют перемещаться по конкретному пути наследования, поэтому можно увеличивать или уменьшать масштаб определенных участков кода. Upstream pprof поддерживает flame graphs.
Flame graphs обеспечивают визуализацию для выявления самых затратных путей в коде.
Ограничены ли я только встроенными профайлерами?
Помимо того, что предоставляется средой выполнения, пользователи Go могут создавать свои собственные профили с помощью pprof.Profile и использовать существующие инструменты для их анализа.
Можно ли обслуживать обработчики профайлера (/debug/pprof/...) по другому пути и порту?
Да. Пакет net/http/pprof по умолчанию регистрирует свои обработчики в стандартном mux, но вы также
можете зарегистрировать их самостоятельно, используя экспортируемые из пакета обработчики.
Например, следующий пример будет обслуживать обработчик pprof.Profile на :7777 по пути /custom_debug_path/profile:
package main
import (
"log"
"net/http"
"net/http/pprof"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/custom_debug_path/profile", pprof.Profile)
log.Fatal(http.ListenAndServe(":7777", mux))
}
Трассировка
Трассировка — это способ инструментирования кода для анализа задержек на протяжении жизненного цикла цепочки вызовов. Go предоставляет пакет golang.org/x/net/trace как минимальную трассировочную подсистему для каждого узла Go и предоставляет простую библиотеку инструментирования с минимальной панелью управления. Go также предоставляет трассировщик выполнения для отслеживания событий среды выполнения в определённом интервале.
Трассировка позволяет нам:
- Инструментировать и анализировать задержки приложения в процессе Go.
- Оценивать стоимость конкретных вызовов в длинной цепочке вызовов.
- Определить использование ресурсов и улучшить производительность. Узкие места не всегда очевидны без данных трассировки.
В монолитных системах relatively легко собирать диагностические данные из составных частей программы. Все модули находятся в одном процессе и разделяют общие ресурсы для записи логов, ошибок и другой диагностической информации. Как только система выходит за рамки одного процесса и начинает становиться распределённой, становится труднее проследить вызов, начиная с фронтенд-веб-сервера, до всех его бэкендов, пока ответ не будет возвращён пользователю. Именно здесь играет важную роль распределённое трассирование для инструментирования и анализа рабочих систем.
Распределённое трассирование — это способ инструментирования кода для анализа задержек на всём протяжении жизненного цикла пользовательского запроса. Когда система распределена и когда традиционные инструменты профилирования и отладки не масштабируются, вы можете использовать инструменты распределённого трассирования для анализа производительности пользовательских запросов и RPC.
Распределённое трассирование позволяет нам:
- Инструментировать и профилировать задержки приложений в крупных системах.
- Отслеживать все RPC в жизненном цикле пользовательского запроса и выявлять интеграционные проблемы, которые видны только в рабочей среде.
- Определять улучшения производительности, которые можно применить к нашим системам. Многие узкие места не очевидны до сбора данных трассировки.
Экосистема Go предоставляет различные библиотеки распределённого трассирования для разных систем трассировки и независимые от бэкенда.
Существует ли способ автоматически перехватывать каждый вызов функции и создавать трассировки?
Go не предоставляет способа автоматического перехвата каждого вызова функции и создания span-ов трассировки. Необходимо вручную инструментировать код для создания, завершения и аннотирования span-ов.
Как следует распространять заголовки трассировки в библиотеках Go?
Вы можете распространять идентификаторы трассировки и теги в context.Context.
Пока ещё нет канонического ключа трассировки или общего представления заголовков трассировки в индустрии. Каждый
поставщик трассировки отвечает за предоставление утилит распространения в своих Go-библиотеках.
Какие ещё низкоуровневые события из стандартной библиотеки или среды выполнения могут быть включены в трассировку?
Стандартная библиотека и среда выполнения пытаются предоставить дополнительные API для уведомления о низкоуровневых
внутренних событиях. Например, httptrace.ClientTrace
предоставляет API для отслеживания низкоуровневых событий в жизненном цикле исходящего запроса. Идёт работа по
получению низкоуровневых событий из трассировщика выполнения среды выполнения и возможности определять и записывать
пользовательские события.
Отладка
Отладка — это процесс выявления причин некорректной работы программы. Отладчики позволяют нам понять поток выполнения программы и её текущее состояние. Существует несколько стилей отладки; в данном разделе будет рассмотрен только способ подключения отладчика к программе и отладка с помощью core dump.
Пользователи Go в основном используют следующие отладчики:
- Delve: Delve — это отладчик для языка программирования Go. Он поддерживает концепции среды выполнения Go и встроенные типы. Delve стремится стать полнофункциональным и надежным отладчиком для программ на Go.
- GDB: Go предоставляет поддержку GDB через стандартный компилятор Go и Gccgo. Управление стеком, потоки и среда выполнения имеют аспекты, которые достаточно отличаются от модели выполнения, ожидаемой GDB, что может запутать отладчик, даже если программа компилируется с помощью gccgo. Несмотря на то, что GDB может использоваться для отладки программ на Go, он не является идеальным и может вызывать путаницу.
Насколько хорошо отладчики работают с программами на Go?
Компилятор gc выполняет оптимизации, такие как встраивание функций и регистрализация переменных. Эти
оптимизации иногда усложняют отладку с помощью отладчиков. Идет работа по улучшению качества информации DWARF,
генерируемой для оптимизированных бинарных файлов. Пока эти улучшения не станут доступны, рекомендуется отключать
оптимизации при сборке отлаживаемого кода. Следующая команда собирает пакет без оптимизаций компилятора:
$ go build -gcflags=all="-N -l"В рамках усилий по улучшению, в Go 1.10 был введен новый флаг компилятора
-dwarflocationlists. Этот флаг заставляет компилятор добавлять списки расположений, которые помогают отладчикам работать с оптимизированными бинарными файлами. Следующая команда собирает пакет с оптимизациями, но с добавлением списков расположений DWARF:
$ go build -gcflags="-dwarflocationlists=true"
Какой интерфейс пользователя рекомендуется для отладчиков?
Несмотря на то, что и delve, и gdb предоставляют CLI, большинство интеграций с редакторами и IDE предоставляют специфичные для отладки пользовательские интерфейсы.
Возможно ли выполнять postmortem-отладку с программами на Go?
Файл дампа ядра — это файл, содержащий дамп памяти выполняющегося процесса и его статус. Он в основном используется для postmortem-отладки программы и для понимания её состояния во время выполнения. Эти два случая делают отладку дампов ядра хорошим диагностическим инструментом для postmortem-анализа и анализа рабочих сервисов. Возможно получить файлы дампов ядра из программ на Go и использовать delve или gdb для отладки, см. страницу отладки дампов ядра для пошагового руководства.
Статистика и события среды выполнения
Среда выполнения предоставляет статистику и отчеты о внутренних событиях для пользователей, чтобы диагностировать проблемы производительности и использования на уровне среды выполнения.
Пользователи могут отслеживать эти показатели, чтобы лучше понять общее состояние и производительность программ на Go. Некоторые часто отслеживаемые показатели и состояния:
runtime.ReadMemStatsсообщает метрики, связанные с выделением памяти в куче и сборкой мусора. Статистика памяти полезна для мониторинга того, какое количество ресурсов памяти потребляет процесс, способен ли процесс эффективно использовать память, а также для выявления утечек памяти.debug.ReadGCStatsчитает статистику о сборке мусора. Полезно для понимания, сколько ресурсов тратится на паузы сборщика мусора. Также отображается хронология пауз сборщика мусора и перцентили времени пауз.debug.Stackвозвращает текущий стек вызовов. Стек вызовов полезен для того, чтобы увидеть, сколько горутин в данный момент выполняется, чем они заняты, а также определить, заблокированы ли они или нет.debug.WriteHeapDumpприостанавливает выполнение всех горутин и позволяет выгрузить содержимое кучи в файл. Дамп кучи — это снимок памяти процесса Go в определённый момент времени. Он содержит все выделенные объекты, а также горутины, финализаторы и другие данные.runtime.NumGoroutineвозвращает количество текущих горутин. Значение можно отслеживать, чтобы проверить, достаточно ли горутин используется, или чтобы выявить утечки горутин.
Трассировщик выполнения
Go поставляется с трассировщиком выполнения среды выполнения для записи широкого круга событий среды выполнения.
Планировщик, системные вызовы, сборка мусора, размер кучи и другие события собираются средой выполнения и доступны
для визуализации с помощью инструмента go tool trace. Трассировщик выполнения — это инструмент для
выявления проблем с задержками и использованием ресурсов. Вы можете проанализировать, насколько эффективно
используется ЦП, и когда сетевые операции или системные вызовы становятся причиной принудительной остановки горутин.
Трассировщик полезен для:
- Понимания того, как выполняются ваши горутины.
- Понимания некоторых ключевых событий среды выполнения, таких как запуски сборщика мусора.
- Выявления плохо параллелизованного выполнения.
Однако он не является идеальным инструментом для выявления горячих точек, например, анализа причин чрезмерного использования памяти или процессора. Вместо этого рекомендуется сначала использовать инструменты профилирования.
Выше показано визуальное представление трассировки, предоставленное инструментом go tool trace. Видно,
что выполнение началось корректно, а затем стало сериализованным. Это может указывать на конкуренцию за совместный
ресурс, который создаёт узкое место.
См. go tool trace
для сбора и анализа трассировок среды выполнения.
GODEBUG
Среда выполнения также выводит события и информацию, если переменная окружения GODEBUG установлена соответствующим образом.
GODEBUG=gctrace=1выводит события сборщика мусора при каждой сборке, указывая объем освобождённой памяти и продолжительность паузы.GODEBUG=inittrace=1выводит сводку времени выполнения и информации о выделении памяти для завершённой инициализации пакета.GODEBUG=schedtrace=Xвыводит события планировщика каждые X миллисекунд.
Переменная окружения GODEBUG может быть использована для отключения использования расширений набора инструкций в стандартной библиотеке и среде выполнения.
- GODEBUG=cpu.all=off отключает использование всех необязательных расширений набора инструкций.
- GODEBUG=cpu.extension=off отключает использование инструкций из указанного расширения набора
инструкций.
extension — это название расширения набора инструкций в нижнем регистре, например sse41 или avx.