От профилирования до отладки и всего, что между ними

Как только вы поймете основы, Golang сможет сделать вас более продуктивным, чем когда-либо прежде. Но что делать, когда что-то идет не так?
Возможно, вы этого не знаете, но Go изначально включает pprof для записи и визуализации данных профилирования во время выполнения. Сторонние инструменты, такие как delve, добавляют поддержку построчной отладки. Детекторы утечек и гонок могут защитить от недетерминированного поведения.
Если вы раньше не видели и не использовали эти инструменты, они быстро станут мощным дополнением к вашему арсеналу инструментов Golang.
Почему бы мне просто не распечатать все?
Я встречал много разработчиков, которые редко открывают отладчик, когда сталкиваются с проблемами в своем коде. Я не думаю, что это неправильно. Если вы пишете модульные тесты, выполняете линтинг кода и по ходу выполняете рефакторинг, то быстрый и грязный подход может работать в большинстве случаев.
И наоборот, я мучился с проблемами устранения неполадок и понял, что быстрее добавить некоторые точки останова и открыть интерактивный отладчик, чем постоянно добавлять утверждения и операторы печати.

Например, однажды я смотрел на график памяти для веб-приложения, которое помогал поддерживать. Каждый день общее использование памяти медленно увеличивалось до такой степени, что сервер требовал перезапуска, чтобы оставаться стабильным. Это классический пример утечки памяти.
Быстрый и грязный подход предполагает, что мы прочитаем код, гарантируя, что порожденные горутины завершатся, выделенные переменные будут собраны в мусор, соединения будут закрыты должным образом и т. Д. Вместо этого мы профилировали приложение и обнаружили утечку памяти за считанные минуты. Это вызвано неуловимым единичным утверждением - обычно так бывает для этого класса ошибок.
Этот обзор познакомит вас с некоторыми инструментами, которые я использую почти каждый день для решения подобных проблем.
Профилирование записи и визуализации
Для начала возьмем базовый веб-сервер Golang с плавным завершением работы и отправим искусственный трафик. Затем мы воспользуемся инструментом pprof, чтобы собрать как можно больше информации.
Мы можем убедиться, что это работает, если:
$ go run main.go & $ curl localhost:8080 Hello World!
Теперь мы можем профилировать процессор, включив этот фрагмент:
Мы будем использовать инструмент нагрузочного тестирования, чтобы тщательно протестировать веб-сервер и смоделировать нормальный и интенсивный трафик. Для этого я использовал инструмент тестирования Vegeta:
$ echo "GET http://localhost:8080" | vegeta attack -duration=5s Hello world! ...
Когда мы выключаем веб-сервер Go, мы видим файл cpu.prof, содержащий профиль ЦП. Затем этот профиль можно визуализировать с помощью инструмента pprof:
$ go tool pprof cpu.prof
Type: cpu
Time: Jan 16, 2020 at 4:51pm (EST)
Duration: 9.43s, Total samples = 50ms ( 0.53%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top 10
Showing nodes accounting for 50ms, 100% of 50ms total
Showing top 10 nodes out of 24
flat flat% sum% cum cum%
20ms 40.00% 40.00% 20ms 40.00% syscall.syscall
...
Это хорошее начало, но Go может сделать лучше. Мы хотим профилировать наше приложение по мере того, как оно получает трафик, чтобы нам не приходилось полагаться на имитацию трафика или добавлять дополнительный код для записи профилей в файл. Добавление net/http/pprof импорта автоматически добавит дополнительные обработчики на наш веб-сервер:
import _ "net/http/pprof"
После того, как это добавлено, мы можем перейти по /debug/pprof/ маршруту через наш веб-браузер и увидеть pprof страницу, изобилующую информацией.

Мы можем получить ту же информацию, что и раньше, выполнив команду:
$ go tool pprof -top http://localhost:8080/debug/pprof/heap
Вы также можете:
- Создавайте изображения на основе типа профиля.
- Создайте Графики пламени, чтобы визуализировать время, затраченное приложением.
- Отслеживайте горутины для обнаружения утечек до того, как они приведут к ухудшению качества обслуживания.
Обратите внимание, что для производственных веб-серверов мы редко хотим избежать раскрытия этой информации миру и вместо этого должны привязать их к другому внутреннему порту.
Откройте для себя интерактивный отладчик
Delve рекламируется как:
… Простой полнофункциональный инструмент отладки для Go. Delve должно быть легко вызывать и легко использовать. Скорее всего, если вы используете отладчик, все идет не так. Имея это в виду, Delve следует как можно больше не мешать вам.
С этой целью он отлично работает, когда у вас есть проблема, на решение которой уходит слишком много времени.
Начать работу с инструментом довольно просто, просто следуйте инструкциям по установке. Добавьте оператор runtime.Breakpoint() и запустите свой код, используя dlv:
$ dlv debug main.go Type 'help' for list of commands. (dlv) continue
Как только вы нажмете точку останова, вы увидите блок кода, например, на веб-сервере выше я помещаю it в обработчик:
> main.handler() ./main.go:20 (PC: 0x1495476)
15: _ "net/http/pprof"
16: )
17:
18: func handler(w http.ResponseWriter, r *http.Request) {
19: runtime.Breakpoint()
=> 20: fmt.Fprintf(w, "Hello World!\n")
21: }
22:
23: func main() {
24: srv := http.Server{
25: Addr: ":8080",
(dlv)
Теперь вы можете переходить по очереди, используя команду next или n, или углубляться в функцию, используя команду step или s.

Если вы поклонник красивого пользовательского интерфейса и нажатия кнопок вместо клавиатуры, VS Code имеет отличную поддержку. При написании модульных тестов с использованием собственной библиотеки тестирования вы увидите кнопку debug test, которая инициализирует delve и позволит вам выполнить код через VS Code в интерактивном сеансе.
Дополнительную информацию об отладке кода Go с помощью VS Code можно найти в Microsoft wiki.
Delve может упростить добавление точек останова, тестирование утверждений и глубокое погружение в пакеты. Не бойтесь использовать его в следующий раз, когда столкнетесь с проблемой и захотите узнать больше о том, что происходит.
Детекторы утечек и гонок
Последняя тема, которую я собираюсь затронуть, - это добавить в ваши тесты детекторы утечек и гонок Golang. Если вы не сталкивались с состоянием гонки или испытывали утечку памяти Goroutine, считайте, что вам повезло.
В 2017 году Uber открыл исходный код пакета goleak, который представляет собой простой инструмент для проверки, помечающий данный TestingT как сбойный, если Find обнаружит какие-либо дополнительные горутины.
Это выглядит так:
func TestA(t *testing.T) {
defer goleak.VerifyNone(t)
// test logic here.
}
Выполняя сложную асинхронную работу, вы можете одновременно избегать регрессий и следовать пятому принципу Дзен Го:
Прежде чем запускать горутину, узнайте, когда она остановится.
Наконец, убедившись, что у вас нет голеков, вы захотите защитить себя от условий гонки. К счастью, детектор гонки данных встроен. Рассмотрим пример из документации детектора гонок:
Это гонка данных, которая может привести к сбоям и повреждению памяти. Запуск этого фрагмента с флагом -race приводит к панике с полезным сообщением об ошибке:
go run -race main.go
==================
WARNING: DATA RACE
Write at 0x00c0000e2210 by goroutine 8:
runtime.mapassign_faststr()
/usr/local/Cellar/go/1.13.6/libexec/src/runtime/map_faststr.go:202 +0x0
main.main.func1()
/PATH/main.go:19 +0x5d
Previous write at 0x00c0000e2210 by main goroutine:
runtime.mapassign_faststr()
/usr/local/Cellar/go/1.13.6/libexec/src/runtime/map_faststr.go:202 +0x0
main.main()
/PATH/main.go:22 +0xc6
Goroutine 8 (running) created at:
main.main()
/PATH/main.go:18 +0x97
==================
2 b
1 a
Found 1 data race(s)
Хотя вы можете использовать этот флаг во время выполнения кода, лучше всего добавить его в команду go test, чтобы определять расы при написании тестов.
Заключение
Это лишь некоторые из замечательных инструментов, доступных в экосистеме Golang для помощи в наблюдении, отладке и предотвращении производственных сбоев в вашей кодовой базе. Если вы хотите пойти дальше, рекомендую взглянуть на:
- Распределенная трассировка типа Open-tracing Go.
- Мониторинг временных рядов с помощью инструмента типа Prometheu.
- Структурированное ведение журнала с использованием логруса.
Для получения дополнительной информации о любом из перечисленных выше инструментов ознакомьтесь с полной документацией и руководствами в разделе ресурсов.