1. Сбор снапшотов в Docker и Production
Подготовка инфраструктуры для профилирования в Docker
Профилирование высоконагруженных сервисов в изолированных контейнерах требует предварительной подготовки. Инструменты dotTrace и dotMemory используют нативные библиотеки (Profiler Agent), которые внедряются в процесс через CLR Profiling API. Чтобы безопасно снять снапшот (снимок состояния приложения) в среде Linux, необходимо обеспечить доступ профилировщика к файловой системе контейнера.
Для этого используется механизм volumes в Docker. Создается общая директория на хост-машине, которая монтируется внутрь контейнера. Это позволяет передать исполняемые файлы профилировщика внутрь и безопасно извлечь результаты наружу, не раздувая образ самого приложения.
Пример настройки директорий на хост-машине:
После скачивания и распаковки архивов JetBrains.dotTrace.CommandLineTools и JetBrains.dotMemory.Console в соответствующие папки, можно выполнять команды профилирования через docker exec, указывая PID процесса (обычно 1 для главного процесса в контейнере).
Альтернативные способы снятия снапшотов без консоли
Работа с консолью внутри Docker не всегда удобна, особенно если доступ к production-серверам ограничен. В современных версиях инструментов существуют альтернативные подходы:
dotnet-trace, dotnet-dump и dotnet-gcdump могут быть включены в базовый образ. Они генерируют файлы в форматах nettrace и gcdump, которые затем можно открыть в dotTrace и dotMemory соответственно.Выбор режима профилирования производительности
Безопасность production-среды напрямую зависит от выбранного режима профилирования. Неправильный выбор может привести к деградации производительности (высокий overhead) или падению сервиса.
| Режим | Принцип работы | Влияние на систему | Когда использовать | | :--- | :--- | :--- | :--- | | Sampling | Периодический опрос стеков вызовов (например, каждые 10 мс). | Минимальное. Безопасно для production. | Поиск "горячих" методов, оценка общей загрузки CPU. | | Timeline | Регистрация событий (ETW на Windows, LTTng на Linux) и состояний потоков. | Низкое. Безопасно для production. | Анализ блокировок, работы с БД, асинхронного кода и пауз сборщика мусора (GC). | | Tracing | Запись времени начала и окончания каждого вызова метода. | Очень высокое. Только для локальной разработки. | Детальный анализ алгоритмической сложности, точный подсчет количества вызовов. | | Line-by-Line | Анализ времени выполнения каждой строки кода. | Критически высокое. | Микрооптимизация конкретных функций. |
> Для production-систем используйте только Sampling или Timeline. Режимы Tracing и Line-by-line замедляют выполнение приложения в десятки раз, что неминуемо приведет к тайм-аутам у клиентов.
Анализ производительности: локализация узких мест
При анализе снапшота в dotTrace важно правильно интерпретировать метрики. Одна из самых частых ошибок — слепая вера показателю .NET total.
Почему показатель .NET total может обманывать
Метрика .NET total (или Total Time) показывает общее время, прошедшее от входа в метод до выхода из него. Однако это время не всегда означает активную работу процессора. Если метод делает HTTP-запрос к стороннему API или выполняет SQL-запрос, поток переходит в состояние ожидания (Waiting). В этот момент процессор свободен, но Total Time продолжает расти.
Чтобы понять, где кроется проблема — в коде приложения, в базе данных или во взаимных блокировках (deadlocks), необходимо использовать режим Timeline и анализировать состояния потоков:
* Running: Поток активно выполняет инструкции на CPU. Проблема в самом коде (сложные вычисления, бесконечные циклы).
* Waiting: Поток ждет завершения операции ввода-вывода (I/O), ответа от сети или базы данных. Проблема во внешних зависимостях.
* Blocked: Поток пытается захватить блокировку (например, lock в C#), которая уже занята другим потоком. Это признак проблем с синхронизацией или взаимных блокировок.
Поиск медленных методов и агрегация потоков
Современные .NET-приложения активно используют асинхронное программирование (async/await). Из-за этого выполнение одного HTTP-запроса пользователя может начинаться на одном потоке из пула потоков (Thread Pool), приостанавливаться при обращении к БД, а возобновляться уже на совершенно другом потоке.
Именно поэтому при анализе производительности требуется агрегировать вызовы со всех потоков. Если смотреть историю только одного потока, вы увидите разорванные фрагменты разных пользовательских запросов. Агрегация по логическому стеку вызовов (Call Tree) позволяет собрать асинхронный запрос воедино и найти конкретный медленный вызов среди множества повторов.
Для поиска самых медленных методов контроллера используйте представление Hotspots (Горячие точки). Оно инвертирует дерево вызовов, показывая методы, которые суммарно заняли больше всего времени (по показателю Own Time — времени выполнения самого метода без учета вызванных им подпрограмм).
Группировка по Namespace и активность системы
Группировка вызовов по Namespace (пространству имен) — мощный инструмент для поиска архитектурных ошибок. Например, если вы сгруппировали вызовы и видите, что пространство имен Microsoft.EntityFrameworkCore потребляет 80% времени в слое представления (Views или Controllers), это явный признак проблемы N+1 (выполнение множества мелких SQL-запросов в цикле вместо одного большого).
Чтобы понять, что в момент снятия снапшота делал пользователь, операционная система или СУБД, режим Timeline собирает системные события. Вы можете увидеть моменты выделения памяти, паузы сборщика мусора (GC) и операции файлового ввода-вывода. Если приложение "зависло", но CPU не загружен, а потоки находятся в состоянии Waiting, проблема находится за пределами приложения.
Мониторинг базы данных
Как определить допустимую длительность SQL-запроса? Универсального ответа нет, так как это зависит от SLA (соглашения об уровне обслуживания). Однако для OLTP-систем (онлайн-обработка транзакций) запросы длительностью более 100-200 мс обычно считаются подозрительными.
Если dotTrace показывает, что приложение просто ждет ответа от БД, профилирование .NET-кода заканчивается. Необходимо переходить к инструментам СУБД. Например, в Microsoft SQL Server следует использовать динамические административные представления (DMV), такие как sys.dm_exec_requests для просмотра текущих выполняющихся запросов, или инструмент SQL Server Profiler / Extended Events для анализа планов выполнения и поиска отсутствующих индексов.
Анализ памяти: поиск утечек в dotMemory
Утечка памяти в управляемой среде .NET означает, что объекты больше не используются логикой приложения, но сборщик мусора (Garbage Collector) не может их удалить, так как на них остались активные ссылки (например, забытая подписка на событие или статическая коллекция).
Для выявления утечек необходимо снять снапшот памяти с помощью команды get-snapshot в момент, когда потребление памяти контейнером достигло аномальных значений.
Кто удерживает сущности в памяти?
Чтобы выяснить, кто именно является причиной утечки, в dotMemory используются два ключевых инструмента:
Сравнительный анализ двух снапшотов (до выполнения бизнес-операции и после ее завершения) — самый надежный способ найти утечку. Если после завершения операции объекты, созданные во время ее выполнения, остались в памяти (появились в секции Survived), необходимо изучить их граф удержания.