通过 System.Diagnostics 监控您的应用程序

发布日期: 2006-08-14 | 更新日期:
2006-08-14

Brad McCabe

*

本页内容

问题总会有解决办法。您刚刚部署的应用程序在开发计算机上一切都好,但在生产中却出了差错。问题可能会马上显现,也可能会随着时间的推移慢慢显露。现在怎么办呢?

开发人员会匆忙推出代码的调试版本并重新部署,这种情形太常见了。加载这一特别版本时,通常会向磁盘上的某个文件中随机写入信息,以便开发人员以后进行研究,以弄清究竟发生了什么问题。除此之外一定还有更好的办法。

解决方案就在 System.Diagnostics
命名空间中,该命名空间提供了用于与事件日志、性能计数器和系统进程进行交互的类。您可以在生产应用程序中保持对此监控代码的启用,并在发生问题时查看相关信息。下面让我们看一下如何在应用程序中使用这些类。

事件日志

无数开发人员都会通过向外部文件中写入信息来调试其应用程序。除了会在硬盘上留下少量垃圾外,此方法通常需要花费很长的时间,无法进行分类或筛选,并且极少能够提供丰富数据。那么为什么还有如此多的人使用这种方法呢?原因只是他们没有意识到使用事件日志有多么轻松。

让我们从耗费时间的写入事件日志的方法入手,说明相关的概念,然后研究快速而容易的方法。首先,向您的代码中添加一个导入语句来引用
System.Diagnostics 命名空间,然后在您的错误处理例程中插入以下代码:111

‘ 如果源不存在,则创建该
‘ 源。
If Not EventLog.SourceExists("MSDNDemo") Then
    EventLog.CreateEventSource("MSDNDemo", _
    "CustomLog")
End If

‘ 创建一个 EventLog 实例并指定其
‘ 源。
Dim myLog As New EventLog()
myLog.Source = "MSDNDemo"

‘ 向事件日志中写入一个信息性条目。    
myLog.WriteEntry("Hello from the event log.")

上面部分的代码会检查您是否已经创建了事件的源。大多数开发人员都会使用其应用程序的名称,从而轻松快速地从事件日志的众多条目中找出该应用程序的事件。

如果源不存在,则会创建源,并将其与一个事件日志相关联。您可以使用该事件日志的现有部分,比如 Application 和 System
部分,也可以通过指定名称来创建您自己的部分(就像本演示这样)。

创建源之后,向事件日志中添加事件就像创建新的 EventLog 对象并将其与源相关联一样简单。本示例中仅写入了“Hello from the event
log”,但您可以向 WriteEntry 方法传递详细的信息(比如堆栈跟踪),并可将任何信息放在事件日志中。

a

图 1 用户在 Custom 部分中生成的事件日志条目

让此代码执行数次,您的事件日志将会显示图 1 所示的活动。您可以在 Windows® 事件查看器
(%windir%\system32\eventvwr.exe) 中进行查看,如图 1 所示。您也可以从服务器资源管理器中的 Visual Studio®
内部查看事件日志,如图 2 所示。如果您双击这些条目之一,您即可以查看详细信息和您的自定义错误消息(请参阅图 3)。

a

图 2 Visual Studio 内的事件日志

EventLog.WriteEntry 方法具有 10 个重载,为您提供了极大的灵活性和控制能力。其中最有用的一个重载允许您指定您要记录的事件的类型。在图
1 中请注意,每个项目都为“Information”(信息)类型。对于演示目的,这就已经足够了,但在实际情况下,您可能会有大量不同的事件。WriteEntry
方法允许您提供事件类型,比如错误、警告、信息、成功审核或失败审核:

     myLog.WriteEntry("Your error here.", _
    EventLogEntryType.Error)
a

图 3 用户生成的事件日志

下面让我们来试试使用快速而容易的方法。Visual Basic® 2005 引入了 My 对象作为快速进入框架的手段。您可以使用 My
向事件日志中快速添加事件:

My.Application.Log.WriteException( _
    "Written from My", TraceEventType.Warning, _
    "Additional Information")

您也可以通过 WriteEntry 方法使用 My:My.Application.Log.WriteEntry(“Written from My”,
TraceEventType.Error)

但如果您调用此方法并查看事件日志,则可能会看不到消息,因为 My
对象允许您根据配置文件中的设置,将您的消息路由到不同的侦听程序。有关这一极其强大的功能的详细论述不属于本专栏文章的范围,但您可以在“My.Application.Log
对象
”找到更多信息。

使用事件日志时应当审慎,没有比应用程序在事件日志中罗里罗嗦地写满毫无价值的消息更令人讨厌的了。如果有错误、重要警告或信息,则应记录。对条目指定类型将具有极大的帮助,因为
Windows 的事件查看器可以对大多数列进行筛选。

性能计数器

事件日志是写入消息的理想场所,但如果想要知道应用程序运行时内部发生了什么,应该怎么办呢?此时理所当然应当选择性能计数器。

许多开发人员都精通于使用 Windows 性能监视器来查看 CPU 和内存的使用情况或硬盘信息(请参阅图 4)。您知道可以使用
PerformanceCounter 类添加您自己的计数器和数据点吗?当 PerformanceCounter 和 Windows
性能监视器结合使用时,就像随时可以将 EKG 计算机与您的应用程序挂钩一样。

a

图 4 标准 Windows 性能工具

在讨论添加自定义计数器之前,让我们研究一下已经存在的计数器。在您的一台计算机上,打开 Visual Studio
中的服务器资源管理器,并导航到“Performance
Counters”(性能计数器)。您将看到一个巨大的计数器类别列表,展开后可以显示每个类别的众多计数器(请参阅图 5)。

a

图 5 性能计数器

您可以在您的应用程序中使用这些系统托管的计数器。接着展开“Memory”(内存)类别,然后展开“Available
Bytes”(可用字节)计数器。将该“Available Bytes”(可用字节)计数器拖动到您的应用程序中的新 Windows 窗体上。

在该窗体上,放置一个标签和一个新计时器对象,使其 Enabled 属性设置为 true。添加以下代码:

Private Sub Timer1_Tick(ByVal sender As System.Object, _
     ByVal e As System.EventArgs) Handles Timer1.Tick
     Label1.Text = Me.PerformanceCounter1.NextValue
End Sub

这是一个非常简单的示例,在实际的应用程序中您不会这样做,但它向您显示了如何向应用程序中添加基本的监控或检索系统计数器。在实际情况下,您可以会根据某个值的状态采取行动、记录此信息或执行许多操作中的任意一项操作。

很可能您打算编写一些自定义的计数器,以便监控您的应用程序在任何特定的时间的运行状况和性能。若要添加新的计数器,请在服务器资源管理器中右键单击“Performance
Counters”(性能计数器)项目,然后选择“Create New Category”(创建新类别)。

在本示例中,让我们创建一个名为 TestCategory 的类别,并添加一个名为 TestCounter
的新计数器。这一次,您将通过代码访问该计数器。

首先将一个新计时器和按钮拖放到窗体上,然后向窗体中添加一个名为 counter 的私有变量:

Private counter As PerformanceCounter

在按钮单击事件中,添加以下代码:

‘初始化计数器对象
counter = New PerformanceCounter("TestCategory", "TestCounter", False)
   ‘ 将其值设置为 0
   counter.RawValue = 0
   ‘ 启动时间以更新该计数器
   Timer2.Enabled = True

在计时器事件中,添加以下代码:

   Counter.IncrementBy(New Random().Next(-5, 10))

这会导致该计数器按照 -5 到 10
之间的某个随机数递增。本示例使用了随机数,但这个数字还可以是已处理的销售或订单数、登录的用户数、记录的错误数或在您的应用程序中进行跟踪的任何有意义的事物。

在启动该示例应用程序之前,请从管理工具下加载性能监视器。这一次,请单击 + 号来添加一个新计数器。在“Performance
Object”(性能对象)下,您现在会看到您新创建的 TestCategory。您可以从下拉列表中选择该对象,并从计数器列表中选择
TestCounter。

在性能监视器运行的情况下,单击示例应用程序的按钮。您应该可以看到一个类似于图 6 的图形。

您在示例代码中可以看到使用了 IncrementBy 方法来增加或减少计数器的数值。如果您想要使计数器以 1 为增减幅度增加或减少,只需调用
Increment 和 Decrement 方法即可。

a

图 6 性能监视器中显示的 TestCounter 结果

对于像本示例这样的使用服务器资源管理器的情形,一些开发人员会想是否可以通过代码完成所有操作。是的,您可以编写如下代码:

   If Not 
     PerformanceCounterCategory.Exists("MSDNSample")
   Then
     PerformanceCounterCategory.Create("MSDNSample", _
       "用于显示如何添加自定义计数器", _
        PerformanceCounterCategoryType.SingleInstance, _
       "MSDNSampleCounter", _
       "用于显示如何添加自定义计数器")
   End If

并将其添加到您的按钮单击事件中,以便完全通过代码创建不同的类别和计数器。如果您更新其他代码,使其指向这个类别和计数器,您将发现该项目可以完全像该示例一样运行和工作。

Process 类

本专栏文章至此已经让您了解了如何使用事件日志和性能计数器;下面让我们深入研究 System.Diagnostics 命名空间的第三个方面,即
Process 类。

首先,让我们定义一个进程:进程就是一个运行的应用程序。在具有 Process
类和正确权限的情况下,您可以访问有关用户系统中各个进程的信息,并执行诸如启动或中止这样的操作。

Process 类可以让您访问大量有关应用程序或其他进程的数据。对于此处的示例,让我们提升示例应用程序的基本优先级。

下面是具体方案。您的应用程序正运行于一台服务器上;该应用程序是该服务器上运行的主要应用程序和功能,但由于后台任务占用了过多的处理器时间,该应用程序性能不佳。您可以执行以下两种操作之一:降低后台任务的优先级,或提升您的任务的优先级。(在大多数情况下,您不需要调整某个进程的优先级,因为
Windows 可以很好地运行多个进程并换入和换出各个进程。)

为了使用 Process 类,您首先需要获取对您的进程或其他进程的引用。由于您想要提升当前进程,因此您将使用:

   Dim myProcess As Process = _
       Process.GetCurrentProcess()

若要访问您的本地计算机或远程计算机上的其他进程,您需要改用 GetProcessessByName 方法:

   Dim theProcesses() As Process = _
       myprocess.GetProcessesByName( _
       "ProcessName", "MachineName")

获得了进程引用后,您可以使用其属性找到有关该进程的大量信息。有关属性的完整列表,请参阅“.NET Framework Class Library Process Members”(英文)。

提升优先级仅需要一行代码:

myProcess.PriorityClass = ProcessPriorityClass.RealTime

您已将应用程序提升为 RealTime。这是您在 OS
中可以设置的最高级别,它可以使应用程序的优先级高于几乎任何其他进程。如果您的应用程序占用所有系统资源,则可能会导致问题。启动任务管理器,以确定 OS
能够看到已提升的该应用程序,如图 7 所示。

a

图 7 提升至 RealTime

您可以使用 process 命令执行大量常见任务。现在假设您想要以最小化方式启动 Microsoft Internet Explorer®,并导航到
Visual Basic Developer Center。您只需使用以下代码即可:

Dim startInfo As New ProcessStartInfo("IExplore.exe")
startInfo.WindowStyle = ProcessWindowStyle.Minimized
startInfo.Arguments = "msdn.microsoft.com/vbasic"
Process.Start(startInfo)

Process 类还适合于打印。

Dim myDocumentsPath As String = _
    Environment.GetFolderPath(Environment.SpecialFolder.Personal)
myProcess.StartInfo.FileName = myDocumentsPath + "\MyFile.doc"
myProcess.StartInfo.Verb = "Print"
myProcess.StartInfo.CreateNoWindow = True
myProcess.Start()

您可以看到,System.Diagnostics
命名空间有许多用途:从向事件日志中记录事件到读写性能计数器,再到处理系统进程。这是一组功能异常强大、用途极为广泛的工具,每个开发人员都应熟悉并在工作中使用。