.NET运行时中的监测和可观测性
今年5月份的时候研究分布式追踪的问题知道了的拦截方式比较零散, 刚好8月份的时候看到这篇文章,这个文章总结的比较完整。存档了很久,趁今天有空翻译给大家。原文地址,校验:张蘅水
.NET是一个托管运行时,这意味着它提供了“管理”您的程序的高级功能,从简介到公共语言运行时(CLR)(2007年编写):
运行时具有许多功能,因此按如下方式对它们进行分类很有用:
- 基本功能 对其他功能设计有广泛影响的功能。这些包括:
1.垃圾收集
2.记忆安全和类型安全
3.对编程语言的高级支持。 - 辅助功能 - 许多有用的程序可能不需要基本特性所支持的功能:
1.使用AppDomains进行程序隔离
2.程序安全和沙盒 - 其他功能 - 所有运行时环境都需要但不利用CLR基本功能的功能。相反,它们是创建完整编程环境的结果。其中包括:
1.版本
2.Debugging/Profiling
3.互操作
您可以看到,“Debugging/Profiling”虽然不是基本或辅助功能,但由于“ 需要创建完整的编程环境 ” ,它仍然会进入列表。
这篇文章的其余部分将看什么 监测,可观测性和内省功能核心CLR提供,为什么他们是有用的,如何提供他们。
为了便于浏览,帖子分为3个主要部分(最后有一些“额外阅读材料”):
- 诊断(Diagnostics)
- Perf View(性能分析工具)
- 共同基础设施
- 未来的计划
- 剖析(Profiling)
- ICorProfiler API
- 分析 v .调试
- 调试(Debugging)
- ICorDebug API
- SOS和DAC
- 第三方调试器
- 记忆转储
诊断(Diagnostics)
首先,我们将查看CLR提供的诊断信息,传统上这些信息是通过“Windows事件跟踪”(ETW)提供的。
CLR提供的各种事件涉及:
- 垃圾收集(GC)
- 即时(JIT)编译
- 模块和AppDomains
- 线程和锁争用
- 以及更多
例如,这是触发AppDomain Load事件的地方,这是Exception Thrown事件,这里是GC Allocation Tick事件。
Perf View
如果你想看到来自你的.NET程序的ETW事件,我建议使用优秀的PerfView工具,从这些PerfView教程开始,或者这个优秀的演讲PerfView:终极.NET性能工具。PerfView被广泛认可,因为它提供了宝贵的信息,例如Microsoft工程师经常将其用于性能调查。
共同基础设施
但是,如果从名称中不清楚,ETW事件仅在Windows上可用,这并不适合新的.NET“跨平台”世界。您可以在Linux上使用PerfView进行性能跟踪(通过LTTng),但这只是cmd-line集合工具,称为“PerfCollect”,分析和丰富的UI(包括flamegraphs)目前仅适用于Windows。
但是如果你想分析.NET Performance Linux,还有其他一些方法:
上面的第二个链接讨论了在.NET Core中正在使用的新“EventPipe”基础架构(以及EventSources和EventListeners,你能发现一个主题!),你可以看到它在跨平台性能监控设计中的目标。在高层次上,它将为CLR提供一个单独的位置来推动与诊断和性能相关的“事件”。然后,这些“事件”将被路由到一个或多个记录器,例如,可能包括ETW,LTTng和BPF,精确记录器由CLR运行的OS /平台确定。.NET Cross-Plat性能和事件设计中还有更多背景信息可以解释不同日志记录技术的优缺点。
“事件管道”中正在进行的所有工作都在“性能监控”项目和相关的“EventPipe”问题中进行跟踪。
未来的计划
最后,还有一个性能分析控制器的(Performance Profiling Controller )未来计划,其目标如下:
控制器负责以简单和跨平台的方式控制性能分析基础结构和.NET性能诊断组件生成的性能数据。
我们的想法是通过从“事件管道”中提取所有相关数据,通过HTTP服务器公开以下功能:
REST API
- Pri 1:简单分析:为运行时间配置X个时间并返回跟踪。
- Pri 1:高级分析:开始跟踪(以及配置)
- Pri 1:高级分析:停止跟踪(对此调用的响应将是跟踪本身)
- Pri 2:获取与所有EventCounters或指定EventCounter相关的统计信息。
可浏览的HTML页面
- Pri 1:流程中所有托管代码堆栈的文本表示。
- 提供当前正在运行的用作简单诊断报告的快照概述。
- Pri 2:显示EventCounters的当前状态(可能具有历史记录)。
* 提供现有计数器及其值的概述。
* 开放性问题:我不相信存在必要的公共API来枚举EventCounters。
我很高兴看到“性能分析控制器(Performance Profiling Controller)”(PPC?)的位置,我认为将这种内置到CLR中确实非常有价值,这是其他运行时的内容。
剖析(Profiling)
CLR提供的另一个强大功能是Profiling API,它(大部分)被第三方工具用于在非常低级别挂钩到运行时。您可以在此概述中找到有关API的更多信息,但在较高级别,它允许您连接在以下情况下触发的回调:
- GC相关事件发生
- 抛出异常
- 装配/卸载装配
- 更多,更多
来自BOTR页面的图像分析API - 概述
此外还有其他非常强大的功能。首先,您可以设置每次执行.NET方法时调用的挂钩,无论是在运行时还是用户代码中。这些回调被称为“进入/离开”钩子,并且有一个很好的示例显示如何使用它们,但为了使它们工作,您需要了解不同操作系统和CPU架构的“调用约定”,这并不总是容易的。另外,作为警告,Profiling API是一个只能通过C / C ++代码访问的COM组件,你不能在C#/ F#/ VB.NET中使用它!
其次,Profiler能够通过SetILFunctionBody()API在JIT 之前重写任何.NET方法的IL代码。这个API功能非常强大,构成了许多.NET APM工具的基础,您可以在我之前的文章中了解更多关于如何使用它的方法。如何模拟密封类和静态方法以及随附的代码。
ICorProfiler API
事实证明,运行时必须执行各种疯狂的技巧才能使Profiling API正常工作,只需查看进入此PR的内容允许重新连接(有关'ReJIT'的详细信息,请参阅ReJIT:A How-To指南)。
所有Profiling API接口和回调的总体定义可在\vm\inc\corprof.idl中找到(请参阅接口说明语言)。但它分为2个逻辑部分,一个是Profiler - >'Execution Engine'(EE)接口,称为ICorProfilerInfo
:
// Declaration of class that implements the ICorProfilerInfo* interfaces, which allow the
// Profiler to communicate with the EE. This allows the Profiler DLL to get
// access to private EE data structures and other things that should never be exported
// outside of the EE.
这在以下文件中实现:
另一个主要部分是EE - > Profiler回调,它们在ICorProfilerCallback
界面下组合在一起:
// This module implements wrappers around calling the profiler's
// ICorProfilerCallaback* interfaces. When code in the EE needs to call the
// profiler, it goes through EEToProfInterfaceImpl to do so.
这些回调在以下文件中实现:
- VM\eetoprofinterfaceimpl.h
- VM\eetoprofinterfaceimpl.inl
- VM\eetoprofinterfaceimpl.cpp
- VM\eetoprofinterfacewrapper.inl
最后,值得指出的是,Profiler API可能无法在.NET Core运行的所有操作系统和CPU-arch上运行,例如Linux上的ELT调用存根问题,有关详细信息,请参阅CoreCLR Profiler API的状态。
分析和调试(Profiling v. Debugging)
除此之外,“分析”和“调试”确实有一些重叠,因此从CLR调试与CLR分析中了解.NET运行时上下文中不同的API提供了什么是有帮助的。
调试(Debugging)
调试意味着不同的事情不同的人,比如我问在Twitter上“ 什么是你调试的.NET程序的途径 ”,并得到了广泛的不同反应,虽然反应两组含有一个很好的工具清单和技术,所以他们值得一试,谢谢#LazyWeb!
但也许这句话最好总结一下Debugging究竟是什么😊
CLR提供了与调试相关的非常广泛的功能,但为什么需要提供这些服务,优秀的帖子为什么托管调试与本机调试不同?提供了3个理由:
- 可以在硬件级别抽象本机调试,但需要在IL级别抽象管理调试
- 托管调试需要大量的信息,直到运行时才可用
- 托管调试器需要与垃圾收集器(GC)协调
所以给一个体面的经验,CLR 具有提供更高级别的调试API称ICorDebug
,这将在下面从“常用的调试方案”的图像中显示的BOTR:
此外,还有很好的描述了不同部分如何在管理断点如何工作中相互作用?,虽然描述左和右是上图中的相反!
Here’s an overview of the pipeline of components:
1) End-user
2) Debugger (such as Visual Studio or MDbg).
3) CLR Debugging Services (which we call "The Right Side"). This is the implementation of ICorDebug (in mscordbi.dll).
---- process boundary between Debugger and Debuggee ----
4) CLR. This is mscorwks.dll. This contains the in-process portion of the debugging services (which we call "The Left Side") which communicates directly with the RS in stage #3.
5) Debuggee's code (such as end users C# program)
ICorDebug API
但是如何实现所有这些以及从CLR Debugging简要介绍的不同组件是什么:
所有.Net调试支持都在我们称之为“The Dac”的dll之上实现。此文件(通常命名
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!我不会在这里列出所有内容,但是我将展示它们所属的类别,有关更多信息,请参阅ICorDebug的分区,其中包含此列表,因为它更详细。
- 顶级(Debugging): ICorDebug + ICorDebug2是顶级接口,有效地充当ICorDebugProcess对象的集合。
- 回调(Callbacks):通过调试器实现的回调对象上的方法调度托管调试事件
- 进程(Process):这组接口表示正在运行的代码,并包含与事件相关的API。
- 代码/类型检查(Code / Type Inspection): 主要可以在静态PE映像上运行,但实时数据有一些便捷方法。
- 执行控制(Execution Control):执行是“检查”线程执行的能力。实际上,这意味着放置断点(F9)和踩踏(F11步入,F10步进,S + F11步出)等。ICorDebug的执行控制仅在托管代码中运行。
- 线程+调用堆栈(Threads + Callstacks):调用堆栈是调试器检查功能的支柱。以下接口与获取callstack有关。ICorDebug仅公开调试托管代码,因此堆栈跟踪仅受管理。
- 对象检查(Object Inspection):对象检查是API的一部分,它允许您在整个调试对象中查看变量的值。对于每个接口,我列出了“MVP”方法,我认为必须简洁地传达该接口的用途。
另外需要注意的是,与Profiling APIs一样,调试API的支持级别因操作系统和CPU架构而异。例如,截至2018年8月,“没有针对Linux ARM进行托管调试和诊断的解决方案”。有关“Linux”支持的更多信息,请参阅这篇很棒的文章,在Linux上使用LLDB调试.NET Core,并从Microsoft 检出诊断存储库,其目标是更容易在Linux上调试.NET程序。
最后,如果你想看看ICorDebug
API在C#中的样子,看一下CLRMD库中包含的包装器,包括所有可用的回调(CLRMD将在后面的文章中进行更深入的介绍)。
SOS和DAC
“数据访问组件(Data Access Component)”(DAC)在BOTR页面中有详细讨论,但实际上它提供了对CLR数据结构的“进程外”访问,因此可以从另一个进程读取其内部详细信息。这允许调试器(via ICorDebug
)或'Son of Strike'(SOS)扩展进入CLR的运行实例或内存转储,并找到如下内容:
- 所有正在运行的线程
- 托管堆上有哪些对象
- 有关方法的完整信息,包括机器代码
- 当前的'堆栈跟踪'
除此之外,如果您想要解释所有奇怪的名称和一点'.NET历史课',请参阅此Stack Overflow答案。
SOS命令的完整列表非常令人印象深刻,并且在WinDBG旁边使用它可以让您非常低级地了解程序和CLR中发生的情况。要了解它是如何实现的,让我们看一下这个!HeapStat
命令,该命令可以为您提供.NET GC正在使用的不同堆大小的摘要:
(来自SOS的图片:即将发布的版本有一些新命令 - HeapStat)
这是代码流,显示了SOS和DAC如何协同工作:
- SOS完整
!HeapStat
命令(链接) - SOS
!HeapStat
处理'Workstation GC' 的命令中的代码(链接) - SOS
GCHeapUsageStats(..)
功能,重负荷(链接) - 共享
DacpGcHeapDetails
包含指向GC堆中主数据的指针的数据结构,例如段,卡表和各代(链接) GetGCHeapStaticData
填充DacpGcHeapDetails
结构的DAC函数(链接)- 共享
DacpHeapSegmentData
包含GC堆的单个“段”的详细信息的数据结构(链接) GetHeapSegmentData(..)
填充DacpHeapSegmentData
结构的DAC(链接)
第三方'调试器'(3rd Party ‘Debuggers’)
由于Microsoft发布了调试API,它允许第三方使用ICorDebug
接口,这里列出了我遇到的一些内容:
- 调试器.NET Core运行时来自三星
- 调试器提供GDB / MI或VSCode调试适配器接口,并允许在.NET Core运行时下调试.NET应用程序。
- 可能是他们将.NET Core移植到他们的Tizen OS的工作的一部分
- dnSpy - “.NET调试器和汇编编辑器”
- 一个非常令人印象深刻的工具,它是一个'调试器','汇编编辑器','十六进制编辑器','反编译器'等等!
- MDbg.exe(.NET Framework命令行调试程序)
- 可以作为NuGet包和GitHub存储库使用,也可以从Microsoft下载。
- 但是,目前MDBG似乎不适用于.NET Core,请参阅端口MDBG到CoreCLR和ETA以将mdbg移植到coreclr以获取更多信息。
- JetBrains'Rider'允许在Windows上进行.NET Core调试
- 虽然由于许可问题引起了一些争议
- 有关更多信息,请参阅此HackerNews主题
记忆转储(Memory Dumps)
我们要看的最后一个区域是“内存转储”,可以从实时系统中捕获并离线分析。.NET运行时一直很好地支持在Windows上创建“内存转储”,现在.NET Core是“跨平台”,也可以在其他操作系统上使用相同的工具。
“内存转储”的一个问题是,获取SOS和DAC文件的正确匹配版本可能会非常棘手。幸运的是,Microsoft刚刚发布了以下dotnet symbol
CLI工具:
可以下载任何给定核心转储,minidump或任何支持平台的文件格式(如ELF,MachO,Windows DLL,PDB和便携式PDB)的调试所需的所有文件(给出coreclr模块的符号,模块,SOS和DAC)。
最后,如果你花费任何时间分析'内存转储',你真的应该看看微软几年前发布的优秀的CLR MD库。我之前已经写过你可以用它做什么,但简而言之,它允许你通过一个直观的C#API与内存转储交互,其中的类可以访问ClrHeap,GC Roots,CLR Threads,Stack Frames和更多。实际上,除了实现工作所需的时间之外,CLR MD还可以实现大多数(如果不是全部)SOS命令。
ClrMD托管库是CLR仅内部调试API的包装器。虽然这些仅内部API对于诊断非常有用,但我们不支持它们作为公开的,有文档的版本,因为它们非常难以使用并且与CLR的其他实现细节紧密耦合。ClrMD通过围绕这些低级调试API提供易于使用的托管包装来解决此问题。
通过在官方支持的库中提供这些API,Microsoft使开发人员能够在CLRMD之上构建各种工具,这是一个很好的结果!
总而言之,.NET Runtime提供了广泛的诊断,调试和分析功能,可以深入了解CLR内部的情况。