使用 ETW 改善调试和性能优化docWord格式文档下载.docx
《使用 ETW 改善调试和性能优化docWord格式文档下载.docx》由会员分享,可在线阅读,更多相关《使用 ETW 改善调试和性能优化docWord格式文档下载.docx(10页珍藏版)》请在冰豆网上搜索。
现在,ETW已成为Windows平台中的主要检测技术之一。
越来越多的第三方应用程序也开始采用ETW进行检测,有些则利用Windows自身提供的事件。
ETW还被提取到Windows预处理器(WPP)软件跟踪技术中,为跟踪"
printf"
样式的消息提供了一组简单易用的宏,以便用于开发过程中的调试。
在WindowsVista上,ETW发生了改头换面的变化,其中最重大的变化之一就是引入了统一的事件提供程序模型和API。
简而言之,新的统一API对跟踪结果进行日志记录并将结果写至事件查看器,为事件提供程序提供了一种一致的、简单易用的机制。
同时,还添加了一些用于改善开发人员和用户体验的新功能。
在本文中,我们将介绍新的ETW提供程序模型,并为大家讲解开发人员应如何在基于WindowsVista的应用程序中采用这一新模型。
首先要对ETW体系结构和使用模型进行概述,然后再讲解新的事件模型和API。
接下来对事件检测的设计和实现进行简要介绍,最后再了解用于控制ETW会话、处理所记录事件并分析这些事件以生成更高一级报告的随机工具。
Windows事件跟踪ETW的核心体系结构如图1所示。
如图所示,ETW主要包含四种类型的组件:
事件提供程序、控制器、使用者和事件跟踪会话。
事件跟踪会话中会发生缓冲和日志记录,此会话用于接受事件并创建一个跟踪文件。
ETW会话可以使用多种日志记录模式。
例如,可以对会话进行配置,直接向使用者应用程序传送事件,或在文件达到特定大小时通过回绕在某个文件中重写旧事件。
为每个会话创建的单独写线程会将这些事件刷新到文件或实时使用者应用程序中。
要实现高性能,可以使用每服务器的缓冲区,这样无需在日志记录路径中设置锁定。
图1ETW体系结构(单击该图像获得较大视图)事件提供程序指的是一种可以将事件写入ETW会话的逻辑实体。
任何可记录的重要活动均可作为事件,每个活动由记录到ETW中的一个事件表示。
事件提供程序可以是用户模式应用程序、托管应用程序、驱动程序或任何其他软件实体。
唯一的要求是,事件提供程序必须通过注册API向ETW注册一个提供程序ID。
提供程序首先向ETW注册,然后调用ETW日志记录API写入来自代码内多个点的事件。
当ETW控制器应用程序动态启用提供程序时,对日志记录API的调用将事件发送给控制器指定的特定跟踪会话。
事件提供程序发送给跟踪会话的每个事件由一个包含事件元数据和其他可变用户上下文数据的固定标头构成。
由于很多操作系统组件中事件检测的不断增多,因此就连为WindowsVista设计的一个简单程序也将包含多个事件提供程序作为组件。
当事件被记录到会话时,除用户提供的数据外,ETW还会添加一些额外的数据项。
其中包括时间戳、进程和线程ID、处理器编号和日志记录线程的CPU使用率数据。
这些数据项被记录在ETW事件标头中,并连同提供程序提供的可变事件内容一起传递给事件使用者。
很多跟踪使用者发现,这些数据字段对于他们的分析必不可少。
控制器可以启动和停止ETW会话,并为会话启用提供程序。
在调试和诊断等情况下,可以根据需要调用控制器工具以收集深度跟踪。
相反地,对于那些始终需要传递给事件查看器的事件,例如针对管理员的事件(稍后会给出定义),事件日志记录服务会在事件注册时自动启用提供程序。
在WindowsVista中,控制器必须具有ETW权限以控制会话。
默认情况下只有一小部分有特权的用户才被授予该权限。
最后,使用者指的是用于读取日志文件或侦听会话以获取实时事件并对其进行处理的一类应用程序。
事件的使用基于回调,使用者注册一个事件回调,以供ETW每次使用一个事件进行调用。
事件是按照时间顺序传送给ETW使用者的。
可以使用通用事件使用者工具将事件转储为各种格式。
图2显示了"
Process"
事件的XML转储。
该事件在由WindowsVista中的tracerpt.exe工具生成时,由内核提供程序记录。
此事件用于表示记事本进程的启动。
由于事件包含了提供程序记录的自定义用户内容,因此需要使用某些类型的元数据对其进行正确解码。
使用新API的提供程序应该会提供一份事件清单(一个XML文件),其中定义了提供程序写入的所有事件和它们的布局信息。
通用使用者应用程序使用TraceDataHelper(TDH)API来检索事件的元数据,对事件进行解码并加以显示。
Figure2进程启动事件的XML转储
Eventxmlns="
SystemProviderGuid="
{9e814aad-3204-11d2-9a82-006008a86939}"
/EventID0/EventIDVersion2/VersionLevel0/LevelTask0/TaskOpcode1/OpcodeKeywords0x0/KeywordsTimeCreatedSystemTime="
2006-12-18T12:
26:
27.887309500Z"
/CorrelationActivityID="
{00000000-0000-0000-0000-000000000000}"
/ExecutionProcessID="
3396"
ThreadID="
3260"
ProcessorID="
0"
KernelTime="
390"
UserTime="
195"
/Channel/Computer//SystemEventDataDataName="
UniqueProcessKey"
0xFFFFFA800143FA80/DataDataName="
ProcessId"
0x10EC/DataDataName="
ParentId"
0xD44/DataDataName="
SessionId"
1/DataDataName="
ExitStatus"
0/DataDataName="
UserSID"
guest/DataDataName="
ImageFileName"
notepad.exe/DataDataName="
CommandLine"
notepad/Data/EventDataRenderingInfoCulture="
en-US"
OpcodeStart/OpcodeProviderMSNT_SystemTrace/ProviderEventNamexmlns="
Process/EventName/RenderingInfoExtendedTracingInfoxmlns="
EventGuid{3d6fa8d0-fe05-11d0-9dda-00c04fd7ba7c}/EventGuid/ExtendedTracingInfo/Event多数情况下,跟踪意味着收集来自所关注的特定提供程序的事件。
按照这种思路,事件跟踪会话要与一个或多个提供程序组成的概念上作为整体的一个集合建立关联,而会话本身(日志记录引擎)通常被忽略。
ETW体系结构考虑到更加动态和灵活地进行跟踪和管理事件。
这里的会话与提供程序存在于不同的空间内。
控制器用于启动和停止ETW会话,并为会话动态地启用提供程序。
因此,控制器可以选择为一个会话启用一组提供程序,稍后禁用其中一些提供程序,稍后再为该会话启用另一提供程序。
会话运行于内核之中,不与提供程序建立静态关联。
同样,提供程序通常也不知道自己的事件被记录到哪些会话中。
有很多应用程序和服务同时作为提供程序、控制器和使用者。
提供程序、控制器和使用者的所有操作都提供API,而且应用程序可能会承担任何角色组合。
但在通常情况下,开发人员仅实现事件提供程序,并使用附带工具来收集和查看跟踪。
提供程序与跟踪会话相分离这种做法的好处之一在于,应用程序的故障(发生崩溃或挂起)不会对跟踪造成影响。
在发生崩溃前,如果提供程序所记录的事件尚未写入跟踪文件,则这些事件将存储在内核内存中,这对于调试应用程序异常尤其有用。
正如签名提到的,开发人员、IT管理员和管理工具开发人员会使用事件进行调试、监控、诊断和容量规划。
常用的基于事件的分析方法可以分为下列几类。
扫描用户对事件转储进行扫描,从中找到单一的重要事件或一小组已知事件。
通常在使用事件调试对应于最终用户问题的故障案例时,或在事件日志中搜索重大故障时,会使用此方法。
Delta分析由于ETW会捕获每个事件的时间戳和CPU使用率,因此进行诸如下列形式的简单Delta分析时
Property(EventB)-Property(EventA)需要考虑到应用程序活动的响应时间和CPU使用率的统计数据。
如果两个事件分别标记了一个活动的起点和终点,就可以采取这种方式处理从实际工作模式的应用程序收集来的大量事件,以生成有关响应时间和CPU使用率统计数据的摘要。
统计分析有时,仅对特定事件的数量进行计数可以使我们对软件行为有更深入的了解。
状态机和资源跟踪拥有足够的事件,就可以构造状态机,从而可以进行基于跟踪的模拟。
例如,由于核心操作系统的大部分活动是使用ETW事件进行检测的,因此可使用操作系统跟踪构建状态机,以跟踪计划程序、内存和I/O活动等。
统一的事件提供程序模型和API在WindowsVista中,我们引入了一组新的事件提供程序API,不仅更易于使用,而且可提供更多功能和增强的安全选项。
新的API还可用于向事件查看器写入事件,并将跟踪与事件日志结合成一个一致的API集。
本部分将为您更加详细地介绍这些API和模型。
如果有需要,还会介绍新API与现有API的不同之处。
软件组件要想成为ETW提供程序,需要通过EventRegisterAPI向ETW注册。
EventRegister需要一个名为ProviderID的GUID,用于唯一地标识提供程序。
任何软件实体(应用程序、共享的DLL或驱动程序)都可以注册为提供程序,因为提供程序不必绑定到任何操作系统实体。
注册过程通常在组件的入口点完成,例如一个DLL附加例程或一个驱动程序入口点。
返回的注册句柄用于接下来的日志记录API调用。
最后,在提供程序执行结束后会调用EventUnregister。
利用现有的提供程序API,提供程序必须提供回调以启用/禁用通知。
也就是说,如果控制器启用了提供程序,则要用启用设置来调用已注册的回调函数。
该回调函数的参数之一是启用提供程序的会话的句柄。
一旦收到启用回调,提供程序会设置一个全局变量(例如TracingOn)以表明跟踪处于开启还是关闭状态,并存储会话句柄。
随后,提供程序将从回调函数获得的会话句柄用于日志记录API调用(按条件基于TracingOn的值)。
在新的ETW提供程序模型中,ETW可以代表提供程序记住启用设置。
换句话说,提供程序不必检查其当前是否已启用即可注册和调用日志记录调用。
在日志记录API内部,ETW会快速查看启用设置,仅当其已启用时才将事件发送给会话。
如果它们未启用,则会放弃日志记录调用。
因此,在新的模型中,启用/禁用回调是可选的。
但在某些情况下,仍然需要启用回调。
例如,针对状态机构造的检测在跟踪开始和结束时通常需要快照或状态断开事件。
日志记录API,即EventWrite采用注册句柄(与旧模型中的会话句柄相对)。
在日志记录调用中使用注册句柄使启用设置对于提供程序来说是透明的。
由于日志记录所用句柄是新模型中的不透明句柄,因此ETW能够对事件进行多播。
也就是说,提供程序能够为多个ETW会话启用,这在旧模型中是无法实现的,后者使用会话句柄。
ETW提供单独的API(EventEnabled和EventProviderEnabled)用于测试是否启用了提供程序。
虽然日志记录API在写入事件前已经检查了启用设置,但有时提供程序可能需要在启用跟踪时进行其他工作。
收集和构造对于程序执行来说不是必需的信息事件数据,就属于这种情况。
如果有需要,提供程序可以随时借助这些API查明跟踪是否已启用。
正如前面提到的,用户可以向每个事件添加可变的上下文数据。
日志记录API使用一种分散/收集机制选择特定于事件的数据项。
调用方通过构造数据描述符数组传入额外的事件数据项。
数据描述符是一种带有指针和大小字段的结构。
因此,用户会为要记录的每个数据项添加一个数据描述符。
我们可以使用宏EventDataDescCreate轻松地构造数据描述符。
然后,ETW会在日志记录期间将用户提供的内容复制到其会话缓冲区。
在相应的事件清单中,应通过Template标记来指定事件的布局。
模板描述了每个事件包含的由用户指定的上下文数据。
模板可以定义布局,布局内可包含单独的数据字段,例如整数和字符串;
也可包含复杂的数据结构,例如结构数组。
并非所有的事件都需要模板;
如果未指定模板,则认为此事件不包含用户提供的数据。
在清单中,多个事件可以共享一个模板,例如Start和Stop事件就具有相同的上下文信息。
当使用者应用程序遇到一个事件时,会通过TDHAPI定位一个事件模板,并相应地对可变事件数据进行解码。
使用旧API的提供程序通过WindowsManagementInstrumentation(WMI)托管对象格式(MOF)提供布局信息。
图3中包含了使用新的事件API进行注册和日志记录的提供程序代码示例。
这是一个用户模式提供程序,使用用户模式提供程序API,但是也可以使用相应的一组内核模式提供程序API。
图3中的第一个事件写入两个用户提供的数据项。
其中一个类型为ULONG,另一个为以NULL结尾的WCHAR字符串。
通过调用EventDataDescCreate来构造合适的数据描述符数组。
除了此处显示的EventWriteAPI,另外还有两个日志记录API:
EventWriteString和EventWriteTransfer。
EventWriteString允许对未列入清单的字符串进行简单的日志记录。
在调用EventWriteString时,ETW会对标头进行标记,表明事件数据是以NULL结尾的单一WCHAR字符串。
如果使用者在标头内发现此标记,则将用户数据作为字符串进行处理,而不必通过TDH搜索事件架构。
使用EventWriteString,不必修改清单即可快速记录字符串。
Figure3ETW提供程序
#includemyevents.h//Headergeneratedfrommanifest.//ContainsMyProviderIdandeventdescriptors.REGHANDLEMyProvRegHandle;
ULONGMyInteger;
PWCHARMyString;
ULONGMyStringLength;
EVENT_DATA_DESCRIPTORDataDescriptor[2];
.//RegistertheETWprovider.Status=EventRegister(&
MyProviderId,//ProviderId(GUID)NULL,//OptionalCallbackNULL,//OPtioanlCallbackContext&
MyProvRegHandle);
//RegistrationHandle.//ConstructDataDescriptorandwriteaneventwith//MyIntegerandMyString.EventDataDescCreate(&
DataDescriptor[0],//DataDescriptor&
MyInteger,//Pointertothedatasizeof(ULONG));
//SizeofdataEventDataDescCreate(&
DataDescriptor[1],&
MyString,MyStringLength);
Status=EventWrite(MyProvRegHandle,//RegistrationHandleMyEventDescriptor1,//EventDescriptor2,//DataDescriptorarraysizeDataDescriptor);
//DataDescriptorarray.//Writeanothereventwithnouserdata.if(EventEnabled(MyProvRegHandle,MyEventDescriptor2)){//Doextraworkifenabledandwriteevent..Status=EventWrite(MyProvRegHandle,MyEventDescriptor2,0,NULL);
}.//UnregistertheETWprovider.Status=EventUnregister(MyProvRegHandle);
EventWriteTransferAPI和EventActivityIdControlAPI是为了满足端到端跟踪检测需要而设计的。
如前所述,端到端跟踪是一种针对同时为大量用户请求执行不同活动的服务器应用程序的检测方法。
例如,网页上的脚本执行请求将从客户端计算机转移到服务器的网络层。
随后,该请求会经过HTTP驱动程序、IIS、ASP.NET引擎,可能还包括另一台计算机上的ExchangeServer。
端到端跟踪的目的是通过ETW事件记录与此请求相关的所有活动,以便稍后进行调试和性能分析。
这需要可以标识单个请求的唯一ID。
在使用期间,可以在该唯一活动ID的帮助下进行关联。
ETW通过在每个事件中引入ActivityId来满足这一需要。
使用新API记录的每个事件会自动选择存储在执行线程内的当前活动ID。
活动ID显示在XML转储的System部分的CorrelationActivityId标记内。
提供程序可以为使用EventActivityIdControlAPI的执行线程获取、设置和创建活动ID。
该活动ID可以与请求一起在多个组件之间传输。
遗憾的是,由于公共协议和设计等方面的限制,有时我们无法传播该活动ID。
EventWriteTransferAPI可以编写传输事件,以表示活动ID的传输。
除了使用EventWrite的所有参数外,EventWriteTransfer还使用另外两个参数,它们是ActivityId和RelatedActivityId。
每个事件均标记有提供程序ID,并被分配了名为事件描述符的实体。
事件描述符定义了标准事件信息,并为其提供了进一步的标识和语义。
开发人员在检测设计阶段确定检测点的事件描述符,并将相应的项写入事件清单。
然后,开发环境中的消息编译器从给定的事件清单在头文件中生成事件描述符,该描述符随后被放入并用于源文件。
从编程的角度看,事件说明符是一种包含下列字段的结构:
Id、Version、Channel、Level、Opcode、Task和Keywords:
typedefstruct_EVENT_DESCRIPTOR{USHORTId;
UCHARVersion;
UCHARChannel;
UCHARLevel;
UCHAROpcode;
USHORTTask;
ULONGLONGKeyword;
}EVENT_DESCRIPTOR,*PEVENT_DESCRIPTOR;
事件ID用于唯一标识提供程序中的事件。
在清单中定义事件时,事件的ID是唯一必需的项。
一旦遇到事件,使用者会使用其提供程序ID(GUID)和事件ID(USHORT)定位订单以找到该事件。
同样,某版本提供的事件在稍后版本中可能发生了更改和补充,但会保留相同的语义和事件ID。
因此事件ID和版本与提供程序ID一起可以唯一标识事件。
通道定义了一组针对目标群体的事件。
通道分为下列四种类型:
管理通道、操作通道、分析通道和调试通道。
进入管理通道的事件是可操作事件;
管理员在收到事件时应该立即知道引起事件的原因和处理方法。
进入操作通道的事件针对的是高级监控工具和支持人员,这些事件可以提供更详细的上下文,发生频率也要高于管理通道的事件。
归入管理和操作通道的事件会自动被发送给事件日志并显示在事件查看器中。
分析通道是为传统跟踪准备的,针对的是专家级的支持专家或详细的诊断和故障排除工具。
调试通道用于调试消息,其中包含供开发人员使用的事件。
分析和调试通道事件默认情况下是未启用的。
通过这些通道,使用一组API可以添加针对不同用途和群体的事件。
在启用提供程序时,控制器可以指定一个级别(单字节整数)和关键字(8字节位掩码)。
级别和关键字用于向ETW检测添加维度。
级别是为了启用基于事件严重程度或详细程度的过滤功能而设计的。
关键字则用于在提供程序中表示子组件。
例如,开发人员可以将事件分为信息事件和严重错误事件。
他们也可以将不同的关键字分配给应用程序的子组件。
通过使用不同的级别和关键字有选择地启用过滤功能,跟踪控制器可以使提供程序仅记录来自子组件B的错误事件,或记录来自子组件A和C的所有事件等等。
对于使用旧API的提供程序,关键字为4个字节,而且按级别和关键字过滤需要在提供程序代码中显式地完成。
值得注意的是,当控制器启用了特定级别时,级别值小于或等于(严重程度相同或更高)控制器指定值的所有事件也将启用。
虽然开发人员可以自定义设计级别和关键字并将其分配给事件,但是仍然提供了预定义的级别,如图4所示。
Figure4严重程度级别
levelslevelname="
win:
LogAlways"
symbol="
WINEVENT_LEVEL_LOG_ALWAYS"
value="
message="
$(string.level.LogAlways)"
LogAlways/levellevelname="
Critical"
WINEVENT_LEVEL_CRITICAL"
1"
$(string.level.C