- PVSM.RU - https://www.pvsm.ru -
.NET – управляемая среда выполнения. Это означает, что в ней представлены высокоуровневые функции, которые управляют вашей программой за вас (из Introduction to the Common Language Runtime (CLR), 2007 г. [1]):
Среда выполнения предусматривает множество функций, поэтому их удобно разделить по следующим категориям:
- Основные функции, которые влияют на устройство других. К ним относятся:
- сборка мусора;
- обеспечение безопасности доступа к памяти и безопасности системы типов;
- высокоуровневая поддержка языков программирования.
- Дополнительные функции– работают на базе основных. Многие полезные программы обходятся без них. К таким функциям относятся:
- изолирование приложений с помощью AppDomains;
- защита приложений и изолирование в песочнице.
- Другие функции – нужны всем средам выполнения, но при этом они не используют основные функции CLR. Такие функции отражают стремление создать полноценную среду программирования. К ним относятся:
- управление версиями;
- отладка/профилирование;
- обеспечение взаимодействия.
Видно, что хотя отладка и профилирование не являются основными или дополнительными функциями, они находятся в списке из-за ‘стремления создать полноценную среду программирования’.
Оставшаяся часть поста описывает, какие функции мониторинга [2], обеспечения наблюдаемости [3] и интроспекции [4] существуют в Core CLR, почему они полезны и каким образом среда предоставляет их
Для начала взглянем на диагностическую информацию, которой нас обеспечивает CLR. Традиционно для этого используется отслеживание событий для Windows (ETW).
Событий, о которых CLR предоставляет информацию, достаточно много. Они связаны со:
Например, здесь возникает событие во время загрузки в AppDomain [5], здесь событие связано с выбросом исключения [6], а здесь с циклом выделения памяти сборщиком мусора [7].
Если вы хотите увидеть события в системе трассировки (ETW), связанные с вашими .NET приложениями, я рекомендую использовать великолепный инструмент PerfView [8] и начать с этих обучающих видео [9] или этой презентации PerfView: The Ultimate .NET Performance Tool [10]. PerfView получил широкое признание за предоставляемую бесценную информацию. Например, инженеры Microsoft регулярно используют его для анализа производительности [11].
Если вдруг непонятно из названия, трассировка событий в ETW доступна только под Windows, что не очень вписывается в кроссплатформенный мир .NET Core. Можно использовать PerfView для анализа производительности под Linux [12] (с помощью LTTng). Однако этот инструмент с командной строкой, под названием PerfCollect, только собирает данные. Возможности анализа и богатый пользовательский интерфейс (в том числе flamegraphs [13]) в настоящий момент доступны только в решениях для Windows.
Но если вы всё-таки хотите проанализировать производительность .NET под Linux, есть и другие подходы:
Вторая ссылка сверху ведёт на обсуждение новой инфраструктуры EventPipe, над которой работают в .NET Core (помимо EventSources & EventListeners). Цели её разработки можно посмотреть в документе Cross-Platform Performance Monitoring Design [16]. На высоком уровне эта инфраструктура позволит создать единое место, куда CLR будет отсылать события, связанные с диагностикой и производительностью. Затем эти события будут перенаправляться к одному или более логерам, которые, например, могут включать ETW, LTTng и BPF. Необходимый логер будет определяться, в зависимости от ОС или платформы, на которой запущена CLR. Подробное объяснение плюсов и минусов различных технологий логирования см. в .NET Cross-Plat Performance and Eventing Design [17].
Ход работы по EventPipes отслеживается в рамках проекта Performance Monitoring [18] и связанных с ним ‘EventPipe’ Issues [19].
Наконец, существуют планы по созданию контроллера профилирования производительности Performance Profiling Controller [20], перед которым стоят следующие задачи:
Контроллер должен управлять инфраструктурой профилирования и представлять данные о производительности, созданные компонентами .NET, отвечающими за диагностику рабочих характеристик, в простом и кроссплатформенном виде.
Согласно замыслу контроллер должен обеспечивать следующие функциональные возможности через HTTP-сервер [21], получая все необходимые данные из инфраструктуры EventPipes:
REST APIs
HTML-страницы, просматриваемые через браузер
Я очень хочу увидеть, что в итоге получится с контроллером профилирования производительности (КПП?). Думаю, если его встроят в CLR, он принесёт много пользы .NET. Такой функционал есть в других средах выполнения [22].
Ещё одно эффективное средство, которое есть в CLR – API профилирования. Его (в основном) используют сторонние инструменты для подключения к среде выполнения на низком уровне. Подробнее про API можно узнать из этого обзора [23], но на высоком уровне с его помощью можно выполнять обратные вызовы, которые активируются, если:
Изображение со страницы BOTR Profiling API – Overview [25]
Кроме того, у него есть другие эффективные функции. Во-первых, вы можете установить обработчики, которые вызываются каждый раз, когда выполняется метод .NET, будь то в самой среде или из пользовательского кода. Эти обратные вызовы известны как обработчики Enter/Leave. Вот здесь есть хороший пример [26], как их использовать. Однако для этого нужно понять конвенции вызовов для разных ОС и архитектур центрального процессора [27], что не всегда просто [28]. Также не забывайте, что API профилирования это COM-компонент, доступ к которому можно получить только из кода C/C++, но не из C#/F#/VB.NET.
Во-вторых, профилировщик может переписать IL-код любого .NET-метода перед JIT-компиляцией с помощью SetILFunctionBody() API [29]. Этот API действительно эффективен. Он лежит в основе многих инструментов APM .NET [30]. Подробнее о его использовании можно узнать из моего поста How to mock sealed classes and static methods [31] и сопутствующего кода.
Оказывается, чтобы API профилирования работал, в среде выполнения должны быть всяческие ухищрения. Просто посмотрите на обсуждение на странице Allow rejit on attach [32] (подробную информацию о ReJIT см. в ReJIT: A How-To Guide [33]).
Полное определение всех интерфейсов и обратных вызовов API профилирования можно найти в vminccorprof.idl [34] (см. Interface description language [35]). Оно разбивается на 2 логические части. Одна часть – это интерфейс Профилировщик -> Среда выполнения (EE), известный как ICorProfilerInfo
:
// Объявление класса, который реализует интерфейсы ICorProfilerInfo*, позволяющие
// профилировщику взаимодействовать со средой выполнения. Таким образом, библиотека DLL профилировщика получает
// доступ к частным структурам данных среды выполнения и другим вещам, которые никогда не должны
// экспортироваться за пределы среды.
Это реализуется в следующих файлах:
Другая основная часть – обратные вызовы Среда выполнения -> Профилировщик, которые группируются под интерфейсом ICorProfilerCallback
:
// Этот модуль использует обёртки вокруг вызовов
// интерфейсов ICorProfilerCallaback* профилировщика. Если коду в среде нужно вызвать
// профилировщик, он должен пройти через EEToProfInterfaceImpl.
Эти обратные вызовы реализуются в следующих файлах:
Наконец, стоит заметить, что API профилирования могут не работать на всех ОС и архитектурах, на которых работает .NET Core. Вот один из примеров: ELT call stub issues on Linux [28]. Подробную информацию см. в Status of CoreCLR Profiler APIs [42].
В качестве небольшого отступления нужно сказать, что профилирование и отладка всё-таки немного пересекаются. Поэтому полезно понимать, что предоставляют различные API в контексте .NET Runtime (взято из CLR Debugging vs. CLR Profiling [43]).
Разница между отладкой и профилированием в CLR
Отладка | Профилирование |
---|---|
Предназначена для поиска проблем с корректностью кода | Предназначено для диагностики и поиска проблем с производительностью |
Может иметь очень высокий уровень вмешательства | Как правило, имеет низкий уровень вмешательства. Хотя профилировщик поддерживает изменение IL-кода или установку обработчиков enter/leave, всё это предназначено для инструментирования, а не для радикального изменения кода. |
Основная задача – полный контроль цели. Это включает инспекцию, контроль выполнения (например команда set-next-statement) и внесение модификаций (функция Edit-and-Continue). | Основная задача – инспекция цели. Для этого предусмотрено инструментирование (изменение IL-кода, установка обработчиков enter/leave) |
Обширный API и толстая модель объекта, заполненная абстракциями. | Небольшой API. Абстракций мало или отсутствуют. |
Высокий уровень интерактивности: действия отладчика контролируются пользователем (или алгоритмом). Фактически редакторы и отладчики часто интегрированы (IDE). | Интерактивность отсутствует: данные обычно собираются без вмешательства пользователя, после чего анализируются. |
Небольшое количество критических изменений, если нужна обратная совместимость. Мы думаем, что миграция с версии 1.1 на версию 2.0 отладчика, будет простой или не очень сложной задачей. | Большое количество критических изменений, если нужна обратная совместимость. Мы думаем, что миграция с версии 1.1 на версию 2.0 отладчика, будет тяжёлой задачей, идентичной его полному переписыванию. |
Разработчики по-разному понимают, что такое отладка. Например, я спросил в твиттере «как вы отлаживаете .NET программы” и получил множество [44] разных ответов [45]. При этом ответы действительно содержали хороший список инструментов и методов, поэтому я рекомендую их посмотреть. Спасибо, #LazyWeb!
Я думаю, что лучше всего всю суть отладки отражает это сообщение:
CLR предусматривает обширный список возможностей, связанных с отладкой. Однако зачем нужны эти средства? Как минимум три причины указаны в этом великолепном посте Why is managed debugging different than native-debugging? [47]:
Поэтому для удобства использования CLR должен предоставлять API отладки высокого уровня, известный как ICorDebug
. Он показан на рисунке ниже, отображающем общий сценарий отладки (источник: BOTR):
Принцип реализации и описание разных компонентов взято из CLR Debugging, a brief introduction [48]:
Вся поддержка отладки в .Net реализуется поверх dll-библиотеки, которую мы называем The Dac. Этот файл (обычно под названием
mscordacwks.dll
) является структурным элементом как для нашего публичного API отладки (ICorDebug
), так и двух частных API отладки: SOS-Dac API и IXCLR.
В идеальном мире все бы использовалиICorDebug
, наш публичный API. Однако вICorDebug
не хватает множества функций, которые нужны разработчикам инструментов. Эта проблема, которую мы пытаемся исправить, где можем. Однако эти улучшения присутствуют только в CLR v.next, но не в более ранних версиях CLR. Фактически поддержка отладки по аварийному дампу появилась вICorDebug
API только с выходом CLR v4. Все, кто используют аварийные дампы для отладки в CLR v2 не смогут применитьICorDebug
совсем.
(Дополнительную информацию см. в SOS & ICorDebug)
На самом деле ICorDebug
API делится более чем на 70 интерфейсов. Я не буду приводить их все, но покажу, по каким категориям их можно разбить. Подробную информацию см. в Partition of ICorDebug, где этот список был опубликован.
Как и с API профилирования, уровни поддержки API отладки отличаются в зависимости от ОС и архитектуры процессора. Например, на август 2018 всё ещё нет решения для Linux ARM по диагностике и отладке управляемого кода. Подробную информацию о поддержке Linux можно посмотреть в посте Debugging .NET Core on Linux with LLDB и репозитории Diagnostics от Microsoft, которая стремится сделать отладку .NET программ под Linux проще.
Наконец, если хотите посмотреть, как ICorDebug
API выглядят в C#, взгляните на обёртки в библиотеке CLRMD, включая все доступные обратные вызовы (подробнее о CLRMD будет рассказано далее в этом посте).
Компонент доступа к данным (DAC) подробно рассматривается на странице BOTR [49]. По сути, он обеспечивает внепроцессный доступ к структурам данных CLR, чтобы информацию внутри них можно было прочитать из другого процесса. Таким образом, отладчик (через ICorDebug
) или расширение ‘Son of Strike’ (SOS) [50] может получить доступ к запущенному экземпляру CLR или дампу памяти и найти, например:
Небольшое отступление: если хотите узнать, откуда взялись эти странные названия и получить небольшой урок истории .NET, посмотрите этот ответ на Stack Overflow [51].
Полный список команд SOS впечатляет [51]. Если использовать его вместе с WinDBG, то можно узнать, что происходит внутри вашей программы и CLR на очень низком уровне. Чтобы увидеть, как всё реализовано, давайте посмотрим на команду !HeapStat
, которая выводит описание размеров различных куч, которые использует .NET GC:
(Изображение взято из SOS: Upcoming release has a few new commands – HeapStat)
Вот поток кода, который показывает, как SOS и DAC работают вместе:
!HeapStat
(ссылка [52])!HeapStat
, который работает с Workstation GC (ссылка)GCHeapUsageStats(..)
, которая выполняет самую тяжёлую часть работы (ссылка [53])DacpGcHeapDetails
, которая содержит указатели на основные данные в куче GC, такие как сегменты, битовые маски и отдельные поколения (ссылка [54]).GetGCHeapStaticData
, которая заполняет структуру DacpGcHeapDetails
(ссылка [54])DacpHeapSegmentData
, которая содержит информацию об отдельном сегменте кучи GC (ссылка [55])GetHeapSegmentData(..)
, которая заполняет структуру DacpHeapSegmentData
(ссылка [56])Поскольку Microsoft опубликовала API отладки, сторонние разработчики смогли использовать интерфейсы ICorDebug
. Вот список тех, которые мне удалось найти:
Последнее, о чём мы поговорим, это дампы памяти, которые можно получить из работающей системы и проанализировать вне её. Среда выполнения .NET всегда отлично поддерживала создание дампов памяти под Windows [68]. А теперь, когда .NET Core стала кроссплатформенной, появились инструменты, выполняющие ту же задачу на других ОС.
При использовании дампов памяти иногда сложно получить правильные, совпадающие версии файлов SOS и DAC. К счастью, Microsoft недавно выпустила dotnet symbol
CLI-инструмент, который:
может скачивать все необходимые для отладки файлы (наборы символов, модули, SOS и DAC файлы для определённого модуля coreclr) для любого определённого дампа ядра, минидампа или файлов любой поддерживаемой платформы, в том числе в формате ELF, MachO, Windows DLL, PDB и портативный PDB.
Наконец, если вы хотя-бы чуть-чуть занимаетесь анализом дампов памяти, рекомендую взглянуть на великолепную библиотеку CLR MD, которую Microsoft выпустила несколько лет назад. Я уже писал про её функции. Вкратце, с помощью библиотеки можно работать с дампами памяти через интуитивно понятный C# API, содержащий классы, которые обеспечивают доступ к ClrHeap, GC Roots, CLR Threads, Stack Frames и многому другому. Фактически CLR MD может реализовывать большинство (если не все) из SOS-команд.
Узнать о том, как она работает можно из этого поста [69]:
Управляемая библиотека ClrMD – это обёртка вокруг API отладки, предназначенных только для внутреннего применения в CLR. Несмотря на то что эти API очень эффективны для диагностики, мы не поддерживаем их в виде публичных, задокументированных релизов, поскольку их использование сложно и тесно связано с другими особенностями реализации CLR. ClrMD решает эту проблему, предоставляя лёгкую в использовании, управляемую обёртку вокруг этих API отладки низкого уровня.
Автор: Stanislav Sidristij
Источник [70]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/328993
Ссылки в тексте:
[1] Introduction to the Common Language Runtime (CLR), 2007 г.: https://github.com/dotnet/coreclr/blob/master/Documentation/botr/intro-to-clr.md#fundamental-features-of-the-clr
[2] мониторинга: https://en.wikipedia.org/wiki/Application_performance_management
[3] обеспечения наблюдаемости: https://en.wikipedia.org/wiki/Observability
[4] интроспекции: https://en.wikipedia.org/wiki/Virtual_machine_introspection
[5] событие во время загрузки в AppDomain: https://github.com/dotnet/coreclr/blob/release/2.1/src/vm/corhost.cpp#https://github.com/dotnet/coreclr/blob/release/2.1/src/vm/corhost.cpp
[6] событие связано с выбросом исключения: https://github.com/dotnet/coreclr/blob/release/2.1/src/vm/exceptionhandling.cpp#L203
[7] циклом выделения памяти сборщиком мусора: https://github.com/dotnet/coreclr/blob/release/2.1/src/vm/gctoclreventsink.cpp#L139-L144
[8] инструмент PerfView: https://github.com/Microsoft/perfview
[9] обучающих видео: https://channel9.msdn.com/Series/PerfView-Tutorial
[10] PerfView: The Ultimate .NET Performance Tool: https://www.slideshare.net/InfoQ/perfview-the-ultimate-net-performance-tool
[11] анализа производительности: https://github.com/dotnet/corefx/issues/28834
[12] PerfView для анализа производительности под Linux: https://github.com/dotnet/coreclr/blob/release/2.1/Documentation/project-docs/linux-performance-tracing.md
[13] flamegraphs: https://github.com/Microsoft/perfview/pull/502
[14] Getting Stacks for LTTng Events with .NET Core on Linux: https://blogs.microsoft.co.il/sasha/2018/02/06/getting-stacks-for-lttng-events-with-net-core-on-linux/
[15] Linux performance problem: https://github.com/dotnet/coreclr/issues/18465
[16] Cross-Platform Performance Monitoring Design: https://github.com/dotnet/designs/blob/master/accepted/cross-platform-performance-monitoring.md
[17] .NET Cross-Plat Performance and Eventing Design: https://github.com/dotnet/coreclr/blob/master/Documentation/coding-guidelines/cross-platform-performance-and-eventing.md
[18] проекта Performance Monitoring: https://github.com/dotnet/coreclr/projects/5
[19] ‘EventPipe’ Issues: https://github.com/dotnet/coreclr/search?q=EventPipe&type=Issues
[20] Performance Profiling Controller: https://github.com/dotnet/designs/blob/master/accepted/performance-profiling-controller.md
[21] следующие функциональные возможности через HTTP-сервер: https://github.com/dotnet/designs/blob/master/accepted/performance-profiling-controller.md#functionality-exposed-through-controller
[22] в других средах выполнения: https://github.com/golang/go/wiki/Performance
[23] из этого обзора: https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/bb384493(v=vs.100)
[24] и многое другое: https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/ms230818(v=vs.100)
[25] BOTR Profiling API – Overview: https://github.com/dotnet/coreclr/blob/master/Documentation/botr/profiling.md#profiling-api--overview
[26] хороший пример: https://github.com/Microsoft/clr-samples/tree/master/ProfilingAPI/ReJITEnterLeaveHooks
[27] конвенции вызовов для разных ОС и архитектур центрального процессора: https://github.com/dotnet/coreclr/issues/19023
[28] не всегда просто: https://github.com/dotnet/coreclr/issues/18977
[29] SetILFunctionBody() API: https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilerfunctioncontrol-setilfunctionbody-method
[30] инструментов APM .NET: https://stackify.com/application-performance-management-tools/
[31] How to mock sealed classes and static methods: https://mattwarren.org/2014/08/14/how-to-mock-sealed-classes-and-static-methods/
[32] Allow rejit on attach: https://github.com/dotnet/coreclr/pull/19054
[33] ReJIT: A How-To Guide: https://blogs.msdn.microsoft.com/davbr/2011/10/12/rejit-a-how-to-guide/
[34] vminccorprof.idl: https://github.com/dotnet/coreclr/blob/master/src/inc/corprof.idl
[35] Interface description language: https://en.wikipedia.org/wiki/Interface_description_language
[36] vmproftoeeinterfaceimpl.h: https://github.com/dotnet/coreclr/blob/release/2.1/src/vm/proftoeeinterfaceimpl.h
[37] vmproftoeeinterfaceimpl.inl: https://github.com/dotnet/coreclr/blob/release/2.1/src/vm/proftoeeinterfaceimpl.inl
[38] vmproftoeeinterfaceimpl.cpp: https://github.com/dotnet/coreclr/blob/release/2.1/src/vm/proftoeeinterfaceimpl.cpp
[39] vmeetoprofinterfaceimpl.h: https://github.com/dotnet/coreclr/blob/release/2.1/src/vm/eetoprofinterfaceimpl.h
[40] vmeetoprofinterfaceimpl.inl: https://github.com/dotnet/coreclr/blob/release/2.1/src/vm/eetoprofinterfaceimpl.inl
[41] vmeetoprofinterfaceimpl.cpp: https://github.com/dotnet/coreclr/blob/release/2.1/src/vm/eetoprofinterfaceimpl.cpp
[42] Status of CoreCLR Profiler APIs: https://github.com/dotnet/coreclr/blob/release/2.1/Documentation/project-docs/profiling-api-status.md
[43] CLR Debugging vs. CLR Profiling: https://blogs.msdn.microsoft.com/jmstall/2004/10/22/clr-debugging-vs-clr-profiling/
[44] множество: https://mobile.twitter.com/matthewwarren/status/1030444463385178113
[45] разных ответов: https://mobile.twitter.com/matthewwarren/status/1030580487969038344
[46] November 10, 2013: https://twitter.com/fortes/status/399339918213652480?ref_src=twsrc%5Etfw
[47] Why is managed debugging different than native-debugging?: https://blogs.msdn.microsoft.com/jmstall/2004/10/10/why-is-managed-debugging-different-than-native-debugging/
[48] CLR Debugging, a brief introduction: https://github.com/Microsoft/clrmd/blob/master/Documentation/GettingStarted.md#clr-debugging-a-brief-introduction
[49] странице BOTR: https://github.com/dotnet/coreclr/blob/master/Documentation/botr/dac-notes.md
[50] расширение ‘Son of Strike’ (SOS): https://docs.microsoft.com/en-us/dotnet/framework/tools/sos-dll-sos-debugging-extension
[51] этот ответ на Stack Overflow: https://stackoverflow.com/questions/21361602/what-the-ee-means-in-sos/21363245#21363245
[52] ссылка: https://github.com/dotnet/coreclr/blob/release/2.1/src/ToolBox/SOS/Strike/strike.cpp#L4605-L4782
[53] ссылка: https://github.com/dotnet/coreclr/blob/release/2.1/src/ToolBox/SOS/Strike/eeheap.cpp#L768-L850
[54] ссылка: https://github.com/dotnet/coreclr/blob/release/2.1/src/inc/dacprivate.h#L690-L722
[55] ссылка: https://github.com/dotnet/coreclr/blob/release/2.1/src/inc/dacprivate.h#L738-L771
[56] ссылка: https://github.com/dotnet/coreclr/blob/release/2.1/src/debug/daccess/request.cpp#L2829-L2868
[57] Debugger for .NET Core runtime: https://github.com/Samsung/netcoredbg
[58] Samsung: https://github.com/Samsung
[59] .NET Core в их Tizen OS: https://developer.tizen.org/blog/celebrating-.net-core-2.0-looking-forward-tizen-4.0
[60] dnSpy: https://github.com/0xd4d/dnSpy
[61] Очень мощный инструмент: https://github.com/0xd4d/dnSpy#features-see-below-for-more-detail
[62] MDbg.exe (.NET Framework Command-Line Debugger): https://docs.microsoft.com/en-us/dotnet/framework/tools/mdbg-exe
[63] NuGet-пакета: https://www.nuget.org/packages/Microsoft.Samples.Debugging.MdbgEngine
[64] репозитория GitHub: https://github.com/SymbolSource/Microsoft.Samples.Debugging/tree/master/src
[65] Port MDBG to CoreCLR: https://github.com/dotnet/coreclr/issues/1145
[66] ETA for porting mdbg to coreclr: https://github.com/dotnet/coreclr/issues/8999
[67] JetBrains ‘Rider’: https://blog.jetbrains.com/dotnet/2017/02/23/rider-eap-18-coreclr-debugging-back-windows/
[68] этой ветке HackerNews: https://news.ycombinator.com/item?id=17323911
[69] поста: https://blogs.msdn.microsoft.com/dotnet/2013/05/01/net-crash-dump-and-live-process-inspection/
[70] Источник: https://habr.com/ru/post/466097/?utm_source=habrahabr&utm_medium=rss&utm_campaign=466097
Нажмите здесь для печати.