3. 产生代码
在Visual Studio 2010中,我们并不能直接使用上面产生的XML文件来创建Provider。所以我们还需要对这个XML文件进行进一步的处理,利用Windows SDK提供的编译器来编译XML文件以产生相应的代码。
首先,我们需要利用Windows Message Compiler (mc.exe)将XML文件编译成相应的头文件和资源描述文件,在Windows SDK的命令行工具中执行:
编译成功后,我们会得到四个文件:MSG00001.bin, FileCopier.Manifest.h, FileCopier.Manifest.rc和FileCopier.ManifestTEMP.BIN。对于非托管代码,我们可以直接使用编译产生的头文件来创建Provider,而对于托管代码,我们还需要做进一步的处理,利用Resource Compiler (rc.exe)将.rc资源描述文件编译成编译后资源文件.res:
编译后我们得到FileCopy.Manifest.res文件,在Visual Studio 2010中,我们通过项目属性将这个资源文件添加到项目中:
图5 在项目中使用编译后的资源文件
4. 构造Provider并输出事件
通过前面三个步骤,我们就完成了事件资源的准备工作,现在我们就可以在应用程序代码中创建Provider,利用代码创建事件并进行事件的输出了。
public partial class MainForm : Form
{
private BackgroundWorker worker;
// 定义Provider和Descriptor
private Etw.EventProvider provider;
private Etw.EventDescriptor descriptor;
public MainForm()
{
this.InitializeComponent();
//…
// 定义事件的GUID
// 这里使用跟XML文件中相同的Provider GUID
Guid etwProviderId = new Guid("{d9b453b9-6230-486c-8dec-c7c5a2230d04}");
// 利用GUID创建Provider
provider = new Etw.EventProvider(etwProviderId);
unchecked
{
// 创建Descriptor,描述Provider
// 这里的属性,使用XML文件中定义的事件属性
descriptor = new Etw.EventDescriptor
(
0x3e9, // Event ID
0x1, // Event Version
0x10, // Operation Channel ID
0x2, // Level
0xa, // Opcode
0x1, // Task
(long)0x8000000000000001 // Keywords
);
}
// 检查Provider是否启用
if (provider.IsEnabled())
{
// 如果Provider已经启动,输出事件
object[] msgload = { "FileCopier启动", 0 };
provider.WriteEvent(ref descriptor, msgload);
}
}
在这段代码中,我们首先利用GUID创建了一个EventProvider,这里使用的GUID是我们在创建事件的XML文件时得到的GUID。然后,我们利用在XML文件中定义的事件的各种属性值,我们创建一个EventDescriptor。完成这两个对象的创建后,我们就可以利用Provider的WriteEvent函数进行事件的输出了。在文件复制过程中,我们利用Provider输出复制的文件名和耗时:
this.worker.DoWork += (o, e) =>
{
string[] files = Directory.GetFiles(source);
for (int i = 0; i < files.Length; ++i)
{
// 记录开始时间
DateTime startTime = DateTime.Now;
Thread.Sleep(1000);
// 执行复制操作
File.Copy(files[i], Path.Combine(dest,
Path.GetFileName(files[i])));
// 计算复制耗时
TimeSpan timeDiff = ( DateTime.Now - startTime );
Int32 dDiff = (Int32)timeDiff.TotalSeconds ;
//copyLog.WriteEntry("复制:" + files[i] +
" 耗时:" + dDiff.ToString(),
EventLogEntryType.Information);
// 构造事件消息
object[] message = { files[i], dDiff };
// 记录事件,填充事件模板
provider.WriteEvent(ref descriptor, message);
this.worker.ReportProgress((int)((100.0f * i) / files.Length));
//…
}
};
5. 编译部署
完成代码编辑后,整个Provider的创建过程就完成了。现在我们可以编译整个项目生产应用程序,也就是一个全新的Provider。得到编译生成的应用程序后,我们需要将其部署到目标机器上。这里需要注意的是,首先我们需要修改定义事件的XML文件,将其中的元素的messageFileName和 resourceFileName属性修改为应用程序的执行位置。 例如,我们的应用程序安装到了“D:\Programs\”目录下并从这个目录运行,则相应的XML文件应该为:
<provider name="SimpleEtwProvider" guid="{D9B453B9-6230-486C-8DEC-C7C5A2230D04}" symbol="SimpleEtwProvider" resourceFileName="D:\Programs\FileCopier.exe" messageFileName="D:\Programs\FileCopier.exe" message="$(string.ProviderMessage)">
然后,我们利用Windows Event Command-line utility (wevtutil.exe)将这个Provider在目标机器上进行注册:
这样,我们的应用程序在执行文件复制的过程中就是一个注册后的消息Provider,就可以进行自定义事件的输出了。当文件复制完成后,我们可以在事件查看器中看到这样更加人性化,更加直观的日志:
图6 更加直观的自定义事件
在这个视图中,我们可以清楚的看到我们输出的文件名和复制操作耗时,当然,我们还可以切换到XML视图查看更加详尽的事件信息,这对于我们利用事件信息进行应用程序的诊断和调试都很有帮助。
通过Performance Counters,我们可以定位应用程序的性能瓶颈;利用ETW,我们可以创建事件和输出丰富的自定义消息以进行进一步的诊断和调试。借助Windows 7所提供的这倚天剑和屠龙刀,配合Visual Studio 2010,发现并解决应用程序的性能问题只在谈笑间!
系列文章索引: