
Для обнаружения аномально высокой длительности выполнения отдельных функций (а также избыточного выделения или утечек памяти) используются инструменты профилирования над виртуальной машиной (например, JProfiler или Visual VM для JVM) или интегрированные в выполняемый код, например встроенный механизм при компиляции Go-приложений. Альтернативой может стать использование универсальных механизмов профилирования, которые интегрируются со средой выполнения и отправляют результаты профилирования на сервер, который может анализировать аномальное поведение и визуализировать выделение памяти и время выполнения отдельных функций (и построить flame graph по результатам анализа приложения во время выполнения). В этой статье мы рассмотрим использование Pyroscope совместно с Go для обнаружения утечек памяти.
Прежде всего, нужно отметить что Pyroscope может интегрироваться с разными средами выполнения и поддерживает JVM, PHP, Ruby, CLR (.Net), Python и Go. Архитектурно Pyroscope состоит из агента (который одновременно является launcher’ом для запуска исследуемого приложения) и сервера, который накапливает данные и позволяет их анализировать после сбора. Сейчас Pyroscope стал частью экосистемы Grafana и позволяет интегрироваться в другие компоненты Observability (включая визуализацию метрик, распределенной трассировки на Grafana Tempo и др.).
Для начала создадим простое консольное приложение, в котором будем симулировать утечку памяти:
package main import ( "io" "math" "net/http" "runtime/debug" "sync" ) var globalSlice = make([][]byte, 0, 0) func leak() { h, _ := http.Get("https://www.google.com") body, _ := io.ReadAll(h.Body) globalSlice = append(globalSlice, body) go leak() } func main() { // disable GC debug.SetGCPercent(-1) debug.SetMemoryLimit(math.MaxInt64) var wg sync.WaitGroup wg.Add(1) go leak() wg.Wait() }
Для установки агента Pyroscope в Go установим его как часть нашего приложения, это поможет правильно сконфигурировать сбор данных. Добавим модуль:
go get github.com/pyroscope-io/client/pyroscope
И выполним инициализацию для захвата профилирования (например, в функции main):
package main import ( "io" "math" "net/http" "runtime/debug" "github.com/pyroscope-io/client/pyroscope" "sync" ) func main() { pyroscope.Start(pyroscope.Config{ ApplicationName: "simple.golang.app", ServerAddress: "http://localhost:4040", Logger: pyroscope.StandardLogger, // список поддерживаемых профилировщиков ProfileTypes: []pyroscope.ProfileType{ pyroscope.ProfileCPU, pyroscope.ProfileAllocObjects, pyroscope.ProfileAllocSpace, pyroscope.ProfileInuseObjects, pyroscope.ProfileInuseSpace, }, }) // disable GC debug.SetGCPercent(-1) debug.SetMemoryLimit(math.MaxInt64) var wg sync.WaitGroup wg.Add(1) go leak() wg.Wait() }
Для сбора данных Pyroscope использует API, которое публикуется на тот же порт, что и веб-интерфейс:
docker run -d -p 4040:4040 pyroscope/pyroscope - server
Pyroscope может получать данные как непосредственно от агента (как в нашем случае, данные профилирования будут загружаться периодически на указанный адрес сервера, по умолчанию один раз в 10 секунд), так и извлекаться со стороны сервера (через механизм поллинга). В случае поллинга поддерживается любая библиотека, которая может отправлять данные в http-ответе в формате pprof (например, в Go это может быть net/http/pprof). Конфигурация поллинга определяется в файле server.yml (при запуске в docker — /etc/pyroscope/server.yml):
scrape-configs: - job-name: pyroscope scrape-interval: 10s enabled-profiles: [cpu, mem, goroutines, mutex, block] static-configs: - application: example-app spy-name: gospy targets: - app:8080 labels: env: dev
В веб-интерфейсе Pyroscope выберем наше приложение и метрику inuse_space:
Для анализа конкретной функции мы также можем использовать тэги:
pyroscope.TagWrapper(context.Background(), pyroscope.Labels("leak"), func(c context.Context) { go leak() })
Также тэги можно задавать при запуске агента pyroscope (например, для агрегации информации в микросервисной архитектуре). На графике изменения метрики можно также добавить аннотации (например, пометить события запуска GC). Исследовать метрику можно как с помощью диаграммы Flamegraph (где показана иерархия использования метрики во вложенных вызовах), так и визуализацию на графе (через graphviz):
Также можно сохранить результаты замеров как baseline и сравнить его в дальнейшем с новыми замерами. Pyroscope также предоставляет возможность анализа ранее сохраненного файла в формате pprof (Adhoc Profiling).
Анализируя изменение в течении времени используемой памяти с привязкой к иерархии функций можно обнаружить проблему с выделением ресурсов и исследовать первопричину ее возникновения. Pyroscope позволяет визуализировать как метрики использования памяти (alloc_space, inuse_space), так и количества выделенных ресурсов (alloc_objects, inuse_objects) и использование процессора (cpu), что позволяет выявлять длительные операции и выполнять оптимизацию кода по замерам в реальных условиях.
Статья подготовлена в преддверии старта курса Golang Developer. Professional.
ссылка на оригинал статьи https://habr.com/ru/companies/otus/articles/736210/
Добавить комментарий