使用 ETW 改善调试和性能优化

http://msdn.microsoft.com/zh-cn/magazine/cc163437.aspx

 

事件跟踪
使用 ETW 改善调试和性能优化
Dr. Insung Park  and Ricky Buch

本文讨论:
  • 了解 ETW 体系结构
  • Windows Vista 中 ETW 的新功能
  • 使用事件提供程序 API 进行编程
本文使用了以下技术:
Windows Vista
目前的软件系统变得日益庞大和复杂,软件的开发和管理随之成为一项艰巨的挑战。几乎不可能对所有执行状态做出说明,应用程序所表现的行为也经常出乎开发者 的意料之外。此外,大量硬件组合和工作负载特征的不断变化也增加了各种软件故障诊断的难度。因此,可靠性和可管理性本身成为重要功能也就不足为奇了。而正 是这些功能催生了对检测的需求。
在 软件执行过程中,针对某些关键的错误状态添加的智能检测手段可以极大地缩短故障的调试时间。在其他某些方面,检测也是非常实用的。在托管的企业环境中,必 须对大量计算机的软硬件故障和资源数量偏低等不良情况进行监视并采取措施。此外,检测还非常有助于解决性能问题,因为性能问题对于外部的工作负载、配置参 数以及底层硬件和软件状态十分敏感,所以诊断起来比较困难。当性能有所下降时,开发人员和管理员能够根据实际工作环境的跟踪结果确定性能不佳的组件或服 务,或者发现开发阶段未预见到的瓶颈。最后,IT 专家可以使用各种管理工具从事务跟踪中得出资源使用率的统计数据,以用于容量规划和趋势分析。
Windows® 事件跟踪 (ETW) 是操作系统提供的一个高速通用的跟踪工具。ETW 使用内核中实现的缓冲和日志记录机制,提供对用户模式应用程序和内核模式设备驱动程序引发的事件的跟踪机制。此外,ETW 使您能够动态地启用和禁用日志记录,轻松地在实际生产环境下进行详细跟踪,而无需重新启动系统或重新启动应用程序。日志记录机制使用每处理器的缓冲区,由 异步写线程将这些缓冲区写入磁盘。这样,大型服务器应用程序在写入事件时所受的干扰能够降至最小。
最 初引入 ETW 的操作系统是 Windows 2000。自此开始,各种核心的操作系统和服务器组件开始纷纷采用 ETW 对其活动进行检测。现在,ETW 已成为 Windows 平台中的主要检测技术之一。越来越多的第三方应用程序也开始采用 ETW 进行检测,有些则利用 Windows 自身提供的事件。ETW 还被提取到 Windows 预处理器 (WPP) 软件跟踪技术中,为跟踪“printf”样式的消息提供了一组简单易用的宏,以便用于开发过程中的调试。
在 Windows Vista™ 上,ETW 发生了改头换面的变化,其中最重大的变化之一就是引入了统一的事件提供程序模型和 API。简而言之,新的统一 API 对跟踪结果进行日志记录并将结果写至事件查看器,为事件提供程序提供了一种一致的、简单易用的机制。同时,还添加了一些用于改善开发人员和用户体验的新功 能。在本文中,我们将介绍新的 ETW 提供程序模型,并为大家讲解开发人员应如何在基于 Windows Vista 的应用程序中采用这一新模型。
首 先要对 ETW 体系结构和使用模型进行概述,然后再讲解新的事件模型和 API。接下来对事件检测的设计和实现进行简要介绍,最后再了解用于控制 ETW 会话、处理所记录事件并分析这些事件以生成更高一级报告的随机工具。

Windows 事件跟踪
ETW 的核心体系结构如图 1 所示。如图所示,ETW 主要包含四种类型的组件:事件提供程序、控制器、使用者和事件跟踪会话。事件跟踪会话中会发生缓冲和日志记录,此会话用于接受事件并创建一个跟踪文件。 ETW 会话可以使用多种日志记录模式。例如,可以对会话进行配置,直接向使用者应用程序传送事件,或在文件达到特定大小时通过回绕在某个文件中重写旧事件。为每 个会话创建的单独写线程会将这些事件刷新到文件或实时使用者应用程序中。要实现高性能,可以使用每服务器的缓冲区,这样无需在日志记录路径中设置锁定。
图 1 ETW 体系结构 (单击该图像获得较大 视图)
事 件提供程序指的是一种可以将事件写入 ETW 会话的逻辑实体。任何可记录的重要活动均可作为事件,每个活动由记录到 ETW 中的一个事件表示。事件提供程序可以是用户模式应用程序、托管应用程序、驱动程序或任何其他软件实体。唯一的要求是,事件提供程序必须通过注册 API 向 ETW 注册一个提供程序 ID。提供程序首先向 ETW 注册,然后调用 ETW 日志记录 API 写入来自代码内多个点的事件。当 ETW 控制器应用程序动态启用提供程序时,对日志记录 API 的调用将事件发送给控制器指定的特定跟踪会话。事件提供程序发送给跟踪会话的每个事件由一个包含事件元数据和其他可变用户上下文数据的固定标头构成。由于 很多操作系统组件中事件检测的不断增多,因此就连为 Windows Vista 设计的一个简单程序也将包含多个事件提供程序作为组件。
当 事件被记录到会话时,除用户提供的数据外,ETW 还会添加一些额外的数据项。其中包括时间戳、进程和线程 ID、处理器编号和日志记录线程的 CPU 使用率数据。这些数据项被记录在 ETW 事件标头中,并连同提供程序提供的可变事件内容一起传递给事件使用者。很多跟踪使用者发现,这些数据字段对于他们的分析必不可少。
控 制器可以启动和停止 ETW 会话,并为会话启用提供程序。在调试和诊断等情况下,可以根据需要调用控制器工具以收集深度跟踪。相反地,对于那些始终需要传递给事件查看器的事件,例如 针对管理员的事件(稍后会给出定义),事件日志记录服务会在事件注册时自动启用提供程序。在 Windows Vista 中,控制器必须具有 ETW 权限以控制会话。默认情况下只有一小部分有特权的用户才被授予该权限。
最 后,使用者指的是用于读取日志文件或侦听会话以获取实时事件并对其进行处理的一类应用程序。事件的使用基于回调,使用者注册一个事件回调,以供 ETW 每次使用一个事件进行调用。事件是按照时间顺序传送给 ETW 使用者的。可以使用通用事件使用者工具将事件转储为各种格式。图 2 显示了“Process”事件的 XML 转储。该事件在由 Windows Vista 中的 tracerpt.exe 工具生成时,由内核提供程序记录。此事件用于表示记事本进程的启动。由于事件包含了提供程序记录的自定义用户内容,因此需要使用某些类型的元数据对其进行 正确解码。使用新 API 的提供程序应该会提供一份事件清单(一个 XML 文件),其中定义了提供程序写入的所有事件和它们的布局信息。通用使用者应用程序使用 Trace Data Helper (TDH) API 来检索事件的元数据,对事件进行解码并加以显示。
 Figure 2 进程启动事件的 XML 转储
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<System>
<Provider Guid="{9e814aad-3204-11d2-9a82-006008a86939}" />
<EventID>0</EventID>
<Version>2</Version>
<Level>0</Level>
<Task>0</Task>
<Opcode>1</Opcode>
<Keywords>0x0</Keywords>
<TimeCreated SystemTime="2006-12-18T12:26:27.887309500Z" />
<Correlation
ActivityID="{00000000-0000-0000-0000-000000000000}" />
<Execution ProcessID="3396" ThreadID="3260" ProcessorID="0"
KernelTime="390" UserTime="195" />
<Channel />
<Computer />
</System>
<EventData>
<Data Name="UniqueProcessKey">0xFFFFFA800143FA80</Data>
<Data Name="ProcessId">0x10EC</Data>
<Data Name="ParentId">0xD44</Data>
<Data Name="SessionId">1</Data>
<Data Name="ExitStatus">0</Data>
<Data Name="UserSID">guest</Data>
<Data Name="ImageFileName">notepad.exe</Data>
<Data Name="CommandLine">notepad</Data>
</EventData>
<RenderingInfo Culture="en-US">
<Opcode>Start</Opcode>
<Provider>MSNT_SystemTrace</Provider>
<EventName xmlns=
"http://schemas.microsoft.com/win/2004/08/events/trace">
Process</EventName>
</RenderingInfo>
<ExtendedTracingInfo xmlns="
http://schemas.microsoft.com/win/2004/08/events/trace">
<EventGuid>{3d6fa8d0-fe05-11d0-9dda-00c04fd7ba7c}</EventGuid>
</ExtendedTracingInfo>
</Event>
多 数情况下,跟踪意味着收集来自所关注的特定提供程序的事件。按照这种思路,事件跟踪会话要与一个或多个提供程序组成的概念上作为整体的一个集合建立关联, 而会话本身(日志记录引擎)通常被忽略。ETW 体系结构考虑到更加动态和灵活地进行跟踪和管理事件。这里的会话与提供程序存在于不同的空间内。控制器用于启动和停止 ETW 会话,并为会话动态地启用提供程序。因此,控制器可以选择为一个会话启用一组提供程序,稍后禁用其中一些提供程序,稍后再为该会话启用另一提供程序。会话 运行于内核之中,不与提供程序建立静态关联。同样,提供程序通常也不知道自己的事件被记录到哪些会话中。有很多应用程序和服务同时作为提供程序、控制器和 使用者。提供程序、控制器和使用者的所有操作都提供 API,而且应用程序可能会承担任何角色组合。但在通常情况下,开发人员仅实现事件提供程序,并使用附带工具来收集和查看跟踪。
提 供程序与跟踪会话相分离这种做法的好处之一在于,应用程序的故障(发生崩溃或挂起)不会对跟踪造成影响。 在发生崩溃前,如果提供程序所记录的事件尚未写入跟踪文件,则这些事件将存储在内核内存中,这对于调试应用程序异常尤其有用。
正 如签名提到的,开发人员、IT 管理员和管理工具开发人员会使用事件进行调试、监控、诊断和容量规划。常用的基于事件的分析方法可以分为下列几类。
扫描 用户对事件转储进行扫描,从中找到单一的重要事件或一小组已知事件。通常在使用事件调试对应于最终用户问题的故障案例时,或在事件日志中搜索重大故障时, 会使用此方法。
Delta 分析 由于 ETW 会捕获每个事件的时间戳和 CPU 使用率,因此进行诸如下列形式的简单 Delta 分析时
Property (Event B)-Property (Event A)
需要考虑到应用程序活动的响应时间和 CPU 使用率的统计数据。如果两个事件分别标记了一个活动的起点和终点,就可以采取这种方式处理从实际工作模式的应用程序收集来的大量事件,以生成有关响应时间 和 CPU 使用率统计数据的摘要。
统计分析 有时,仅对特定事件的数量进行计数可以使我们对软件行为有更深入的了解。
状态机和资源跟踪 拥有足够的事件,就可以构造状态机,从而可以进行基于跟踪的模拟。例如,由于核心操作系统的大部分活动是使用 ETW 事件进行检测的,因此可使用操作系统跟踪构建状态机,以跟踪计划程序、内存和 I/O 活动等。

统一的事件提供程序模型和 API
在 Windows Vista 中,我们引入了一组新的事件提供程序 API,不仅更易于使用,而且可提供更多功能和增强的安全选项。新的 API 还可用于向事件查看器写入事件,并将跟踪与事件日志结合成一个一致的 API 集。本部分将为您更加详细地介绍这些 API 和模型。如果有需要,还会介绍新 API 与现有 API 的不同之处。
软 件组件要想成为 ETW 提供程序,需要通过 EventRegister API 向 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 事件就具有相同的上下文信息。当使用者应用程序遇到一个事件时,会通过 TDH API 定位一个事件模板,并相应地对可变事件数据进行解码。使用旧 API 的提供程序通过 Windows Management Instrumentation (WMI) 托管对象格式 (MOF) 提供布局信息。
图 3 中包含了使用新的事件 API 进行注册和日志记录的提供程序代码示例。这是一个用户模式提供程序,使用用户模式提供程序 API,但是也可以使用相应的一组内核模式提供程序 API。图 3 中的第一个事件写入两个用户提供的数据项。其中一个类型为 ULONG,另一个为以 NULL 结尾的 WCHAR 字符串。通过调用 EventDataDescCreate 来构造合适的数据描述符数组。除了此处显示的 EventWrite API ,另外还有两个日志记录 API:EventWriteString 和 EventWriteTransfer。EventWriteString 允许对未列入清单的字符串进行简单的日志记录。在调用 EventWriteString 时,ETW 会对标头进行标记,表明事件数据是以 NULL 结尾的单一 WCHAR 字符串。如果使用者在标头内发现此标记,则将用户数据作为字符串进行处理,而不必通过 TDH 搜索事件架构。使用 EventWriteString,不必修改清单即可快速记录字符串。
 Figure 3 ETW 提供程序
#include <myevents.h>   // Header generated from manifest. 
// Contains MyProviderId and event descriptors.

REGHANDLE MyProvRegHandle;
ULONG MyInteger;
PWCHAR MyString;
ULONG MyStringLength;
EVENT_DATA_DESCRIPTOR DataDescriptor[2];

...

// Register the ETW provider.
Status = EventRegister(&MyProviderId, // ProviderId (GUID)
NULL, // Optional Callback
NULL, // OPtioanl Callback Context
&MyProvRegHandle); // Registration Handle

...

// Construct DataDescriptor and write an event with
// MyInteger and MyString.
EventDataDescCreate(&DataDescriptor[0], // DataDescriptor
&MyInteger, // Pointer to the data
sizeof(ULONG)); // Size of data
EventDataDescCreate(&DataDescriptor[1], &MyString, MyStringLength);

Status = EventWrite(MyProvRegHandle, // Registration Handle
MyEventDescriptor1, // EventDescriptor
2, // DataDescriptor array size
DataDescriptor); // DataDescriptor array

...

// Write another event with no user data.
if (EventEnabled(MyProvRegHandle, MyEventDescriptor2)) {
// Do extra work if enabled and write event.
...

Status = EventWrite(MyProvRegHandle, MyEventDescriptor2, 0, NULL);
}

...

// Unregister the ETW provider.
Status = EventUnregister(MyProvRegHandle);
EventWriteTransfer API 和 EventActivityIdControl API 是为了满足端到端跟踪检测需要而设计的。如前所述,端到端跟踪是一种针对同时为大量用户请求执行不同活动的服务器应用程序的检测方法。例如,网页上的脚本 执行请求将从客户端计算机转移到服务器的网络层。随后,该请求会经过 HTTP 驱动程序、IIS、ASP.NET 引擎,可能还包括另一台计算机上的 Exchange Server。端到端跟踪的目的是通过 ETW 事件记录与此请求相关的所有活动,以便稍后进行调试和性能分析。这需要可以标识单个请求的唯一 ID。在使用期间,可以在该唯一活动 ID 的帮助下进行关联。
ETW 通过在每个事件中引入 ActivityId 来满足这一需要。使用新 API 记录的每个事件会自动选择存储在执行线程内的当前活动 ID。活动 ID 显示在 XML 转储的 <System> 部分的 <Correlation ActivityId> 标记内。提供程序可以为使用 EventActivityIdControl API 的执行线程获取、设置和创建活动 ID。该活动 ID 可以与请求一起在多个组件之间传输。遗憾的是,由于公共协议和设计等方面的限制,有时我们无法传播该活动 ID。EventWriteTransfer API 可以编写传输事件,以表示活动 ID 的传输。除了使用 EventWrite 的所有参数外,EventWriteTransfer 还使用另外两个参数,它们是 ActivityId 和 RelatedActivityId。
每 个事件均标记有提供程序 ID,并被分配了名为事件描述符的实体。事件描述符定义了标准事件信息,并为其提供了进一步的标识和语义。开发人员在检测设计阶段确定检测点的事件描述 符,并将相应的项写入事件清单。然后,开发环境中的消息编译器从给定的事件清单在头文件中生成事件描述符,该描述符随后被放入并用于源文件。从编程的角度 看,事件说明符是一种包含下列字段的结构:Id、Version、Channel、Level、Opcode、Task 和 Keywords:
typedef struct _EVENT_DESCRIPTOR {
USHORT Id;
UCHAR Version;
UCHAR Channel;
UCHAR Level;
UCHAR Opcode;
USHORT Task;
ULONGLONG Keyword;
} EVENT_DESCRIPTOR, *PEVENT_DESCRIPTOR;
事 件 ID 用于唯一标识提供程序中的事件。在清单中定义事件时,事件的 ID 是唯一必需的项。一旦遇到事件,使用者会使用其提供程序 ID (GUID) 和事件 ID (USHORT) 定位订单以找到该事件。同样,某版本提供的事件在稍后版本中可能发生了更改和补充,但会保留相同的语义和事件 ID。因此事件 ID 和版本与提供程序 ID 一起可以唯一标识事件。
通 道定义了一组针对目标群体的事件。通道分为下列四种类型:管理通道、操作通道、分析通道和调试通道。进入管理通道的事件是可操作事件;管理员在收到事件时 应该立即知道引起事件的原因和处理方法。进入操作通道的事件针对的是高级监控工具和支持人员,这些事件可以提供更详细的上下文,发生频率也要高于管理通道 的事件。归入管理和操作通道的事件会自动被发送给事件日志并显示在事件查看器中。分析通道是为传统跟踪准备的,针对的是专家级的支持专家或详细的诊断和故 障排除工具。调试通道用于调试消息,其中包含供开发人员使用的事件。分析和调试通道事件默认情况下是未启用的。通过这些通道,使用一组 API 可以添加针对不同用途和群体的事件。
在 启用提供程序时,控制器可以指定一个级别(单字节整数)和关键字(8 字节位掩码)。级别和关键字用于向 ETW 检测添加维度。级别是为了启用基于事件严重程度或详细程度的过滤功能而设计的。关键字则用于在提供程序中表示子组件。例如,开发人员可以将事件分为信息事 件和严重错误事件。他们也可以将不同的关键字分配给应用程序的子组件。通过使用不同的级别和关键字有选择地启用过滤功能,跟踪控制器可以使提供程序仅记录 来自子组件 B 的错误事件,或记录来自子组件 A 和 C 的所有事件等等。对于使用旧 API 的提供程序,关键字为 4 个字节,而且按级别和关键字过滤需要在提供程序代码中显式地完成。值得注意的是,当控制器启用了特定级别时,级别值小于或等于(严重程度相同或更高)控制 器指定值的所有事件也将启用。虽然开发人员可以自定义设计级别和关键字并将其分配给事件,但是仍然提供了预定义的级别,如图 4 所示。
 Figure 4 严重程度级别
<levels>
<level name="win:LogAlways" symbol="WINEVENT_LEVEL_LOG_ALWAYS"
value="0" message="$(string.level.LogAlways)"> Log Always
</level>
<level name="win:Critical" symbol="WINEVENT_LEVEL_CRITICAL" value="1"
message="$(string.level.Critical)"> Only critical errors </level>
<level name="win:Error" symbol="WINEVENT_LEVEL_ERROR" value="2"
message="$(string.level.Error)"> All errors, includes win:
Critical </level>
<level name="win:Warning" symbol="WINEVENT_LEVEL_WARNING" value="3"
message="$(string.level.Warning)"> All warnings, includes
win:Error </level>
<level name="win:Informational" symbol="WINEVENT_LEVEL_INFO"
value="4"
message="$(string.level.Informational)"> All informational
content, including win:Warning </level>
<level name="win:Verbose" symbol="WINEVENT_LEVEL_VERBOSE" value="5"
message="$(string.level.Verbose)"> All tracing, including
previous levels </level>
</levels>
任 务和操作码用于向每个事件附加额外的信息。任务用于指定通用逻辑组件或正在检测的任务。它通常表示组件实现自身功能所要采取的关键的高级步骤。操作码表示 在事件被写入时正在执行的具体操作。例如,Windows 内核提供程序将所有的文件 I/O 操作事件归入一个“FileIO”任务。操作码表示操作的具体内容,例如 Create、Open、Read 和 Write。与 ID、版本、通道、级别和关键字不同,任务和操作码仅用于添加信息,它们对于控制检测或定位元数据没有任何影响。
事 件描述符和布局都在事件清单中指定。开发人员在设计检测时要编写事件清单。编写事件清单采用的是 XML 语言,其中在相应的 XML 标记中指定用户定义的通道、任务、操作码、级别和关键字。也可以使用 ETW 预定义的通道、级别和操作码;但预定义的通道为全局通道,应仅用于针对管理通道的事件。每个事件的不同元数据字段组合在一起,并在与事件 ID 唯一关联的 <Event> 标记中进行定义。事件也可以具有消息字符串,当使用者读取该事件时,将用事件数据提供的替代值显示该字符串。图 5 显示了一个事件清单示例的 XML 片段。稍后我们将介绍有关事件检测设计和检测的分步指南。
 Figure 5 Event 清单片段
<provider name="Microsoft-Windows-Kernel-Registry" 
guid="{70eb4f03-c1de-4f73-a051-33d13d5413bd}"
symbol="RegistryProvGuid"
resourceFileName="%SystemRoot%\System32\advapi32.dll"
messageFileName="%SystemRoot%\System32\advapi32.dll">
<channels>
<channel name="Microsoft-Windows-Kernel-Registry/Analytic"
chid="RegistryEvents" symbol="REG_Events" type="Analytic"
isolation="System">This channel contains registry
events.</channel>
</channels>
<opcodes>
<opcode value="32" name="CreateKey" symbol="" />

...

</opcodes>
<keywords>
<keyword name="CreateKey" symbol="" mask="0x1000" />

...

</keywords>
<templates>
<template tid="tid_RegOpenCreate">
<data name="BaseObject" inType="win:Pointer"
outType="win:HexInt64" />
<data name="KeyObject" inType="win:Pointer"
outType="win:HexInt64" />
<data name="Status" inType="win:UInt32"
outType="win:HexInt32" />
<data name="Disposition" inType="win:UInt32" />
<data name="BaseName" inType="win:UnicodeString"
outType="xs:string" />
<data name="RelativeName" inType="win:UnicodeString"
outType="xs:string" />
</template>

...

</templates>
<events>
<event value="1" symbol="ETW_REGISTRY_EVENT_CREATE_KEY"
template="tid_RegOpenCreate" opcode="CreateKey"
channel="RegistryEvents" level="win:Informational"
keywords="CreateKey"
message="$(string.Registry.RegOpenCreate)"/>

...

</events>
</provider>

...

<localization>
<resources culture="en-US">
<stringTable>
<string id="Registry.RegOpenCreate"
value="Registry key %6 was created with status %3." />
</stringTable>
</resources>
</localization>
Microsoft® .NET Framework 3.5(代号为“Orcas”)中也将提供托管的 ETW 提供程序 API。托管的使用者和控制器 API 目前正处于计划阶段。System.Diagnostics.Eventing 中包含 EventProvider 类。EventProvider 为本机应用程序提供了实现上述所有功能的方法。用户需要使用提供程序 GUID 对类进行实例化,并使用该类实例记录事件。EventDescriptor 是一个等同于本机 API 中的事件描述符的结构。与本机的情况不同,EventDescriptors 并不是为托管的应用程序生成的。但是,目前正在考虑开发一个能够生成旨在改善性能并验证的代码的工具。图 6 显示了 EventProvider 的使用示例。
 Figure 6 托管的 ETW 提供程序
using System.Diagnostics.Eventing;
...

static void Main(string[] ArgRead)
{
int MyInteger;
string MyString;

...

// Construct event descriptor.
EventDescriptor Event1 = new EventDescriptor(5, 0, 0, 2, 0, 0, 0);

// Instantiate event provider.
EventProvider etwProvider = new EventProvider(
new Guid("d58c126f-b309-11d1-969e-0000f875a5bc"));

...

// Write an event with MyInteger and MyString.
etwProvider.WriteEvent(ref Event1, MyInteger, MyString);

...
}
此 外,Windows Vista 中的 ETW 为提供程序提供了经过改善的安全选项。默认情况下,任何提供程序都可以注册并写入事件。但是,开发人员可以对 GUID 设置限制,从而只有授权用户才能使用该 GUID 注册提供程序。提供程序也可以指定谁可以启用它。此外,ETW 允许控制器将会话声明为安全的,意思是其仅接收来自某个用户组的事件。
一 旦带有事件提供程序检测的二进制文件编译完成,提供程序即完成安装。然后,用户可以使用 logman 工具(一个附带的控制器应用程序)从提供程序收集事件。以下 logman.exe 命令发出了两个 ETW 控制 API 调用,这两个调用启动了名为 mysession 的 ETW 会话并启用了提供程序:
> logman start mysession -p <provider name> -o mytest.etl -ets
会 话 mysession 向名为 mytest.etl 的文件写入事件。Logman.exe 提供了很多选项可对日志记录模式和缓冲区配置等进行自定义。此处的 <provider name> 可以是清单中的提供程序名称,也可以是提供程序使用 EventRegister API 进行注册时使用的 GUID。如果提供程序 GUID 为 11223344-5566-7788-99aa-bbccddeeff00,则实际命令行将如下所示:
> logman start mysession 
-p {11223344-5566-7788-99aa-bbccddeeff00}
-o mytest.etl -ets
ETW 允许控制器在提供程序注册和执行前预启用它。这样,对于二进制图像的加载不容易控制的 DLL 和驱动程序,用户就可以收集它们的启动跟踪。控制器启动一个会话,并在提供程序开始执行之前启用它。提供程序一经注册即被启用。
根 据需要收集事件完成后,可以使用 logman.exe 中的停止选项停止会话。
> logman stop mysession -ets
现 在,在执行 logman 命令的目录下应该出现会出现一个名为 mytest.etl 的跟踪文件。使用 Tracerpt.exe 可以从该文件中获取事件的转储:
> tracerpt mytest.etl
此 命令会创建一个转储文件(默认情况下为 dumpfile.xml )和一个文本摘要文件(默认情况下为 summary.txt)。摘要文件包含简短的事件统计摘要。转储文件中包含事件的 XML 转储,如图 2 所示。事件标头中的数据字段显示在 <System> 部分中。<System> 部分中的 <Execution> 标记中提供了进程 ID、线程 ID、处理器 ID 和 CPU 使用率。此外,<System> 部分还显示了事件的事件描述符字段的值。用户可以指定输出文件名称作为 tracerpt.exe 命令的选项。Windows Vista 还提供了其他一些能够收集和查看跟踪的 GUI 工具,稍后我们将进行讨论。
Windows Vista 中安装了数百个事件提供程序。用户可以通过 PerfMon 或事件查看器查看这些提供程序的列表,或者也可以使用以下 logman.exe 命令:
> logman query providers

设计和实现指南
在 本部分中,我们要讨论创建定义事件的清单文件的步骤、使用 ETW API 调用检测代码的步骤以及成功构建和安装事件提供程序的步骤。
设计检测清单 检测清单是一个 XML 格式的文件,它定义了事件提供程序和事件提供程序记录到 ETW 的事件。每个事件都具有标准的元数据和可变的事件数据部分。我们可以手动创建清单,也可以使用 SDK 提供的清单生成工具 (ecmangen.exe)。步骤如下:
  1. 创建一个事件提供程序并确定其提供程序 ID,即 GUID。
  2. 定义组件的逻辑任务并在这些逻辑任务中指定检测点。这一 步定义了提供程序所写入的事件的任务和操作码。
  3. 定义将记录事件的子组件并为每个子组件分配关键字。这样控制器就可以有选择地启用事件。
  4. 评 估所设计事件的目标群体并为每个目标群体定义通道。
  5. 创建不同的构造来定义可变的用户上下文数据。可以在 <templates> 部分中完成构造的创建。
  6. 将它们放在一起,并在清单的 <events> 部分中定义事件。为每个事件分配符号标识符、严重程序、关键字、通道、任务、操作码和模板。还可以添加一条可本地化的特定于事件的消息。
使用 ETW API 检测代码 在清单中定义了事件后,使用统一的 API 将事件写入 ETW。对于本机应用程序,首先会生成一个包含清单提供的事件定义的头文件,以便将其加入到提供程序代码中。然后,使用 ETW API 写入前面设计好的事件。过程如下:
  1. 在上面创建的清单上运行消息编译器 mc.exe。这一步会生成头文件,其中包含事件的事件描述符结构,每个结构实例具有与事件符号标识符相同的名称。消息编译器还会产生资源字符串文件。所 生成的头文件需要加入到提供程序代码中。
  2. 提供程序在编写代码以引发事件之前,需要调用 EventRegister 函数,使用清单中定义的提供程序 ID 进行注册。应对 EventRegister 返回的注册句柄进行存储,以供日志记录 API 调用和相应的 EventUnregister 调用使用。
  3. 调用 EventWrite 以记录清单中定义的事件。通过图 3 所示的数据描述符数组传入特定于事件的数据。(开发人员必须确保数据字段与清单内指定的相应事件模板完全匹配。)传入事件头文件中定义的正确的事件描述符 结构。
  4. 所有必需的事件记录完成后,调用 EventUnregister 并传入注册句柄。
编译已检测的二进制文件 在构造阶段应对定义事件的清单信息进行编译,并将编译好的信息作为资源附加到提供程序的可执行二进制文件中。所有可本地化字符串将从该经过编译的清单内提 取出来,并放入一个必须单独编译和链接的单独的源文件内。在部署提供程序二进制文件时,需要在系统中安装清单信息,以便控制器和使用者能够找到事件提供程 序和事件定义。所有必需工具均可从 Windows Vista 本机获得或从 SDK 获得。相关步骤如下:
  1. 如果事件存在可本地化的消息字符串,可能需要运行 rc.exe 将资源文件编译为 .res 文件。
  2. 如果使用 Visual Studio®,开发人员可以加入预建的命令来运行 rc.exe,以生成 .res 已编译资源,并将此已编译资源文件加入到项目之中。
  3. 使用 cl.exe 或 link.exe 链接已编译资源文件(Visual Studio 用户可以在步骤 2 中完成此操作),并对应用程序或驱动程序进行编译。
  4. 在安装提供 程序时,运行带 –im 选项的 wevtutil.exe 工具来安装定义事件的清单。这样,控制器就可以发现事件提供程序,并且使使用工具也可以找到对事件进行正确解码所需的信息。

工具和支持
到 目前为止,我们已经使用了命令行控制器 logman.exe 和通用使用者工具 tracerpt.exe。Logman.exe 还提供了一个选项,可以在远程计算机上安排收集。Tracerpt.exe 能够以 CSV 格式转储事件,还可以使用 –report 选项为某些已知的核心操作系统事件生成资源使用报告。图 7 显示了基于状态机构建技术对某些已知的核心操作系统事件进行处理和关联后 tracerpt.exe 生成的 Hot File 表格。
图 7 Tracerpt 生成的 Hot File 报表 (单 击该图像获得较大视图)
Windows Vista 中的可靠性和性能监视器 (RPM)(其中包含 Windows Vista 上的 PerfMon)为收集跟踪提供了一个图形界面。使用事件跟踪会话界面,用户可以建立跟踪会话,选择要启用的提供程序,并启动和停止 ETW 会话。图 8 显示了当用户查看要启用的提供程序列表时的 RPM。RPM 引入了数据收集器集的概念。其中,有关事件跟踪、性能计数器和配置(注册表和 WMI 类)数据收集的所有必需信息均被组合到一个单一的收集组中。用户可以使用启用了提供程序为跟踪和计数器数据创建数据收集组。创建完成后,将收集组设置存储 在 RPM 中,以便使用户可以轻松地启动和停止数据收集,而不必每次都指定资源。此功能支持通用的诊断情形,用户可以指定提供程序从某些已知的诊断方案收集跟踪和计 数器。
图 8 RPM 中的事件跟踪提供程序 (单击该 图像获得较大视图)
事 件查看器提供了另外一种收集和查看事件的方法。新的事件 API 也是针对指向事件日志的事件而设计的,并且管理和操作通道事件会被自动传送给事件查看器并在其中显示,以便对其进行监控,从而获得重要的软件状态信息。事 件查看器还扩展了其在分析和调试事件方面的功能。“视图”选项可以显示所有可用的分析和调试通道,用户可以通过右键单击来启用跟踪收集。在图 9 中,用户通过右键单击注册表事件提供程序的一个分析通道来启用跟踪收集。
您 可以在提供程序、控制器和使用者 API 的软件开发工具包中找到示例代码。使用新的提供程序 API 的示例提供程序中包含这些程序所记录的事件的清单。同时还提供了驱动程序示例。
图 9 用于分析事件收集的事件查看器界面 (单 击该图像获得较大视图)

总结
我 们已经向大家介绍了 Windows Vista 新推出的用于 ETW 的统一事件提供程序模型和 API。本文旨在向开发人员介绍这些 API,并为他们提供一些使用示例和一般性指南。跟踪对于很多开发人员来说仍然是一项陌生的技术,我们希望通过本文使大家对 Windows 平台上的主要检测和诊断基础结构有深入了解。自从 ETW 作为一项通用跟踪技术应用于 Windows 2000 操作系统以来,几年之间,使用 ETW 作为跟踪和可管理性功能基础的组件和应用程序如雨后春笋般出现,这种趋势很可能会持续下去。
我 们预计,更多的开发人员将开始纷纷采用事件检测技术来提供更加可靠、性能更佳同时更容易管理的应用程序。检测不仅将帮助开发人员调试应用程序,而且对于部 署和管理这些应用程序的人来说也大有帮助。此外,检测对于容量规划、趋势分析和查找性能瓶颈都是有好处的。ETW 提供了一套实用的跟踪基础结构,使应用程序能够提供检测功能,可满足上述所有情形,我们期待这一技术作为核心构造块能够在构建更易于管理和诊断的应用程序 方面发挥根本作用。

Dr. Insung Park 是 Windows 检测平台团队的开发负责人。他发表了多篇文章,内容涉及性能分析、请求跟踪、检测技术、编程方法和技术支持。他的电子邮件地址为 insungp@microsoft.com

Ricky Buch 是 Windows 检测平台团队的一名项目经理。他负责研究 Windows 事件跟踪和性能计数器库技术。您可以通过 ricky.buch@microsoft.com 与他联系。
 

 

posted @ 2010-07-02 01:11  史莱姆  阅读(1530)  评论(0编辑  收藏  举报