Создать dump dotnet на linux
На практике всё равно остались мелкие неприятности. Эпизодически выскакивали всякие StackOverflow (хорошо хоть не segmentation faults), да и мой дебагерский виндовый опыт оказался практически бесполезным на никсах.
Например, практически сразу мы заметили, что, запускаясь под контейнером, проект с ходу отъедал 300 мегабайт физической памяти и около 2-х гигов виртуальной. В виндовом продакшене, конечно, под нагрузкой бывало и на порядки больше, но вот тут, на Linux, в демо-режиме, это много, мало, или вообще как? Как это проверить в принципе? На Винде я бы сделал дамп процесса, запустил Visual Studio или WinDBG и гуглил, что теперь делать дальше. А что тут?
Как выяснилось, гугл под Linux тоже работает, так что через пару часов медитации на монитор я выучил несколько интересных штук, о которых и хотел бы рассказать сегодня.
Песочница (дебаггинг пойдёт потом)
Виртуальная машина с Ubuntu
mv microsoft . gpg / etc / apt / trusted . gpg . d / microsoft . gpg sh - c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-xenial-prod xenial main" > /etc/apt/sources.list.d/dotnetdev.list' apt - get update && apt - get install - y dotnet - sdk - 2.0.2Теперь создаём машину при помощи vagrant up , заходим внутрь через vagrant ssh и начинаем делать тестовый проект.
Тестовый проект
dotnet new console -o memApp сделает практически всё, что нужно. Я только добавил туда массив с текстовым мусором и дописал ReadLine в конце, чтобы процесс не завершался, пока не скажут:
static Random random = new Random ( ( int ) DateTime . Now . Ticks ) ; = > String . Concat ( Enumerable . Range ( 0 , length ) . Select ( _ = > RandomChar ( ) ) ) ; var dummyStringsCollection = Enumerable . Range ( 0 , 10000 ) . Select ( _ = > "Random string: " + RandomString ( 10000 ) ) . ToArray ( ) ;Теперь пример можно сбилдить, запустить и приступать к экспериментам:
Делаем дамп процесса (core dump)
Для начала посмотрим, сколько же памяти процесс уже умудрился отожрать:
2.6 гига виртуальной памяти и примерно 238 мегабайт физической. Конечно, размер виртуальной памяти не означает, что вся эта память теперь недоступна другим, или мы её хоть как-то используем, но это точно повлияет на размер дампа и он скорее всего займёт те же 2.6 гига.
Но в Ubuntu всё хорошо, и sudo gcore с айдишкой процесса делает всё, как нужно:
И, как я и говорил раньше, размер дампа оказался равен размеру виртуальной памяти:
Но я отвлёкся. У нас есть дамп, будем препарировать.
Именно такие истории мне помогают оставаться в профессии в моменты, когда хочется всё бросить и уйти в геологи или хотя бы в судмедэксперты. История про браузеровский userAgent когда-то тоже помогла. Но я снова отвлёкся.
Средство dotnet-dump просто в использовании и не зависит от каких-либо отладчиков машинного кода. dotnet-dump работает на различных платформах Linux (например, Alpine или ARM32/ARM64), где традиционные средства отладки могут быть недоступны. Однако dotnet-dump фиксирует только управляемое состояние, поэтому его нельзя использовать для отладки в машинном коде. Дампы, собранные dotnet-dump , анализируются в среде с той же ОС и архитектурой, в которой был создан дамп. Средство dotnet-gcdump можно использовать в качестве альтернативного варианта, которое фиксирует только сведения о куче GC, но создает дампы, которые можно проанализировать в Windows.
Дампы ядра с помощью createdump
Вместо dotnet-dump , создающего только управляемые дампы, createdump рекомендуется для создания основных дампов в Linux, содержащих как собственные, так и управляемые данные. Другие средства, такие как gdb или gcore, можно также использовать для создания основных дампов, однако они могут не учитывать состояние, необходимое для управляемой отладки, что приводит к неизвестному типу или именам функций во время анализа.
<input-filename>
Параметры
-f|--name <output-filename>
Файл, в который записывается дамп. Значение по умолчанию — "/tmp/coredump.%p", где % p — это идентификатор целевого процесса.
-n|--normal
-h|--withheap
Создание минидампа с кучей (по умолчанию).
-t|--triage
Создание минидампа для рассмотрения.
-u|--full
Создайте полного дампа ядра.
-d|--diag
Для сбора дампов ядра требуется либо возможность SYS_PTRACE , либо createdump необходимо запустить с помощью sudo или su.
Анализ дампов в Linux
Как управляемые дампы, собранные с помощью dotnet-dump , так и дампы ядра, собранные с помощью createdump , можно проанализировать с помощью средства dotnet-dump , используя команду dotnet-dump analyze . dotnet dump требует, чтобы среда, в которой анализируется дамп, использовала ту же ОС и архитектуру, что и среда, в которой был записан дамп.
- libmscordaccore.so
- libcoreclr.so
- dotnet (узел, используемый для запуска приложения)
После того как необходимые файлы будут доступны, можно загрузить дамп в LLDB, указав узел dotnet в качестве исполняемого файла для отладки:
После запуска LLDB может потребоваться использовать команду setsymbolserver , чтобы указать правильное расположение символов ( setsymbolserver -ms , чтобы использовать сервер символов корпорации Майкрософт, или setsymbolserver -directory <path> для указания локального пути). Собственные символы можно загрузить, запустив loadsymbols . На этом этапе для анализа дампа можно использовать команды SOS.
Я думаю, суть ясна, что инструментальная поддержка пока сильно далека от аналогичной при разработке под Windows.
Для некоторых вещей пока нету готовых средств. Например, для профилирования.
Из источников, которые доступны в сети, самыми содержательными, по моему мнению, на текущий момент являются статьи Саши Гольдштейна:
Однако, готового рецепта по поиску утечки памяти мне найти не удалось. Поэтому я решил описать найденный мной способ.
Как бы мы действовали под Windows
Лично я бы, не долго думая, подключился к работающему приложению с помощью dotMemory, снял бы 2 снапшота через некоторый промежуток времени и сравнил бы их, используя, красивый GUI.
Как здорово было бы, если бы под Linux мы могли бы сделать что-нибудь похожее.
Пример, который будем рассматривать
Тут ничего сложного. Конечно, не нужно прибегать ни к каким инструментам, чтобы найти утечку в следующем коде. Но для обучающих целей сгодится.
Как сделать снапшот работающего приложения под Linux
Сделать подобный снапшот (core dump) под Linux для работающего приложения достаточно легко. Это делают следующие 2 команды:
И через некоторое время делаем второй снапшот
Мы получили 2 дампа нашего приложения:
которые теперь можем попытаться сравнить.
Microsoft предоставляет плагин для LLDB, который может нам с этим помочь. Это портированное расширение SOS из WinDBG с аналогичным набором команд.
В теории, чтобы посмотреть аллокации памяти, имея полученный выше снапшот мы должны были бы выполнить следующие команды:
В статьях у Саши Гольдштейна ещё устанавливается путь к CLR командой
Но для исследования проблем с Debug-сборкой моего тестового приложения мне это не понадобилось.
Суровая реальность
Как писал Саша в своей статье, к сожалению, этот плагин линкуется с конкретной версией LLVM. Соответственно, потребуется конкретная версия LLDB, чтобы им воспользоваться.
Посмотреть конкретную версию, уже не получится как раньше, с помощью команды ldd :
Дело в том, что liblldd.so в разных дистрибутивах назывался по-разному и его убрали из явных зависимостей.
Казалось бы, теперь мы знаем версию и можем просто поставить в систему lldb нужной версии. Но нет. Дело в том, что lldb до версии 4.0 не мог грузить on-demand core dump. Как раз такие, какие мы и сделали (в процессе работы программы).
Собираем libsoplugin с lldb-4.0
Собственно, в репозитории CoreCLR есть инструкции по сборке под Linux. Мы только немного подправим их, чтобы воспользоваться lldb-4.0.
Я использую Ubuntu 16.04. Для других дистрибутивов набор команд может несколько отличаться.
При локальной разработке и отладке инструменты на основе VS могут легко увидеть информацию об утечках памяти:
Но когда он выходит в онлайн, он обычно находится в среде Linux, а служба работает на докере. В настоящее время утечки памяти, аномалии процессора и т. Д. Не могут быть напрямую отлажены, а только выгружает информацию в разрешенную служебную память в докере. Для анализа идеи заключаются в следующих шагах:
- Сделайте дамп оперативной памяти и перенесите ее в локальную
- Правильно считывает информацию о дампе
- Анализируйте причины
- Внутри контейнера dockername можно получить через docker ps
- Найдите каталог, соответствующий инструменту:
- Выполните createdump по указанному выше пути, и информация о текущем контейнере во время выполнения будет сохранена.
1 представляет собой номер процесса службы, который будет сохранен в контейнере. Если это только 1 служба, по умолчанию обычно используется 1, и, наконец, получается файл createdump.1
- Выньте его из емкости и поместите в нужное место.
Теперь, когда у нас есть coredump.1, мы должны найти способ прочитать coredump.1 для следующего анализа.
Есть много решений, но, в конце концов, информация о дампе просматривается с помощью команды sos.
имеет следующие три маршрута:
Прочитать windbg + sos
Среда, в которой нужно читать дамп: windows
Я не пробовал эту штуку, в теории вроде нормально. Хотя windbg может работать только в окнах, он должен иметь возможность читать дамп в окнах в сочетании с sos.
Dotnet поставляется с dotnet-dump для чтения
Среда, необходимая для тестирования и чтения дампа: linux
Преимущества этого инструмента по сравнению со следующим решением lldb + sos: простота установки,Нажмите здесь для официальной документации。
Задайте путь в сочетании с фактическим каталогом dotnet
После ввода выполните clrthreads, он перечислит управляемые потоки, запущенные в это время.
Если ожидаемого результата получить не удалось, появляется:
Failed to load data access module, 0x80004005
Can not load or initialize libmscordaccore.so. The target runtime may not be initialized.
означает, что libmscordaccore.so должен быть зависимым, но он не загружен. К сожалению, инструмент dotnet-dump не поддерживает отдельную функцию setclrpath, поэтому файл so не может быть загружен.
Начиная с github, версия после 3.1.57502 должна поддерживать отдельный путь setclrpath, поскольку код был изменен.
Используйте lldb + sos для чтения (рекомендуется)
Среда, в которой нужно тестировать и читать дамп: Ubuntu
Почему Ubuntu? Поскольку версию lldb проще установить в среде Ubuntu.
- Установка завершена, принесите файл дампа для проверки
Проверьте потоки в текущем файле дампа и введите sos Threads, чтобы увидеть:
Например, если вы хотите просмотреть сведения о потоке с идентификатором 11, вы можете сначала заблокировать поток с помощью setsostid 14 11:
, а затем sos ClrStack для просмотра внутренней информации потока
Мы смогли отобразить информацию о стеке с помощью команды sos, и с помощью этой информации можно проводить анализ.
Прежде всего, вы должны быть знакомы с каждой командой в sos, потому что независимо от того, каким способом читать дамп выше, на самом деле, это в конечном итоге зависит от плагина sos.
Затем проанализируйте разные углы в соответствии с различными встречающимися ситуациями, вот несколько общих методов анализа, связанных между собой:
Анализ чрезмерного использования памяти
Анализ высокой загрузки процессора
Читайте также: