Для обнаружения аномально высокой длительности выполнения отдельных функций (а также избыточного выделения или утечек памяти) используются инструменты профилирования над виртуальной машиной (например, JProfiler или Visual VM для JVM) или интегрированные в выполняемый код, например встроенный механизм при компиляции Go-приложений. Альтернативой может стать использование универсальных механизмов профилирования, которые интегрируются со средой выполнения и отправляют результаты профилирования на сервер, который может анализировать аномальное поведение и визуализировать выделение памяти и время выполнения отдельных функций (и построить flame graph по результатам анализа приложения во время выполнения). В этой статье мы рассмотрим использование Pyroscope совместно с Go для обнаружения утечек памяти. Прежде всего, нужно отметить что Pyroscope может интегрироваться с разными средами выполнения и поддерживает JVM, PHP, Ruby, CLR (.Net), Python и Go. Архитектурно Pyroscope состоит из агента (который одновременно является launcher'ом для запуска исследуемого приложения) и сервера, который накапливает данные и позволяет их анализировать после сбора. Сейчас Pyroscope стал частью экосистемы Grafana и позволяет интегрироваться в другие компоненты Observability (включая визуализацию метрик, распределенной трассировки на Grafana Tempo и др.). Для начала создадим простое консольное приложение, в котором будем симулировать утечку памяти: Для установки агента Pyroscope в Go установим его как часть нашего приложения, это поможет правильно сконфигурировать сбор данных. Добавим модуль: И выполним инициализацию для захвата профилирования (например, в функции main): Для сбора данных Pyroscope использует API, которое публикуется на тот же порт, что и веб-интерфейс: Pyroscope может получать данные как непосредственно от агента (как в нашем случае, данные профилирования будут загружаться периодически на указанный адрес сервера, по умолчанию один раз в 10 секунд), так и извлекаться со стороны сервера (через механизм поллинга). В случае поллинга поддерживается любая библиотека, которая может отправлять данные в http-ответе в формате pprof (например, в Go это может быть net/http/pprof). Конфигурация поллинга определяется в файле server.yml (при запуске в docker - /etc/pyroscope/server.yml): В веб-интерфейсе Pyroscope выберем наше приложение и метрику inuse_space: Для анализа конкретной функции мы также можем использовать тэги: Также тэги можно задавать при запуске агента pyroscope (например, для агрегации информации в микросервисной архитектуре). На графике изменения метрики можно также добавить аннотации (например, пометить события запуска GC). Исследовать метрику можно как с помощью диаграммы Flamegraph (где показана иерархия использования метрики во вложенных вызовах), так и визуализацию на графе (через graphviz): Также можно сохранить результаты замеров как baseline и сравнить его в дальнейшем с новыми замерами. Pyroscope также предоставляет возможность анализа ранее сохраненного файла в формате pprof (Adhoc Profiling). Анализируя изменение в течении времени используемой памяти с привязкой к иерархии функций можно обнаружить проблему с выделением ресурсов и исследовать первопричину ее возникновения. Pyroscope позволяет визуализировать как метрики использования памяти (alloc_space, inuse_space), так и количества выделенных ресурсов (alloc_objects, inuse_objects) и использование процессора (cpu), что позволяет выявлять длительные операции и выполнять оптимизацию кода по замерам в реальных условиях. Статья подготовлена в преддверии старта курса Golang Developer. Professional. Узнать подробнее о курсе Flutter Developer@YandexPro, DevOps, IT Architect