首页 服务器 编程 必备知识 搜索引擎 圩日手册
站内搜索
最近浏览
推荐文章
热文排行

使用 WMI 监视系统性能


   摘要:WMI 提供了一整套易于使用的性能计数器类。本文介绍如何利用这些类创建监视脚本来监视处理器使用情况、内存使用情况、网络连接、磁盘使用情况、Web 服务性能、服务器连接、打印机使用情况以及其他各种性能参数。


    好久不见,大家好吗?很抱歉,很久没来这里了。但是,坦率地讲,我们这些 Scripting Guys 还有许多其他重要任务,这些任务比脚本编写工作更重要,因此我们不得不将诸如 Scripting Clinic 专栏之类相对次要的工作先放在一边,而专注于真正有益于 Microsoft 的事情。您不会认为 Bill Gates 的车会自行清洗吧?

    然而,实际上 Bill 的车确实会自行清洗。当然,大多数其他 Microsoft 执行官则没有这么好的车,结果,他们就只能自己一次又一次地洗车。随着天气越来越冷,草长得也不那么快了,因此我就有时间,可以再次坐到计算机前,打造一个全新的 Scripting Clinic 专栏。

    但是,在开始之前,我需要确保阅读此专栏的每个人都熟悉《Frankenstein》(英文)故事。现在,无疑你们都读过了这本书(当然,这也意味着你们都看过了这部电影)。但是为了预防万一,我们还是简单回顾一下:一位名叫 Victor Frankenstein 的雄心勃勃的青年科学家决定收集一些死尸,然后从每个死尸上截取最好的部分并将这些部分缝合在一起,再赋予这个“人”生命,来创造一个超人。(顺便提一句,这个过程与 Peter Costantini 加入最初只有四个人的 Scripting Guys 团队非常相似。)事情最初进展得非常顺利,但是您知道当赋予死尸以生命后会怎样:无论初衷如何,坏事总是坏事。简而言之,情况很快就变得不可收拾,最后甚至每个相关的人都死了。

    当然,《Frankenstein》的寓意是很清楚的:有些事情人们根本就不应该去尝试;特别是像“让死尸复活”这样的想法。(或者至少,如果您要让死尸复活,最好从一些较小且容易控制的对象开始,例如沙鼠或仓鼠。)从《Frankenstein》中我们所有人都可以得到这样一个教训:有些事情最好不要去做。这是我每次接受新任务时都试图遵循的一点。

    那么,为什么我要谈论 Frankenstein 而不去打扫人行道、喝杯咖啡或是做些有用的事呢?其实,很少有人会注意到这一点,Mary Shelley 实际上已经开始了《Frankenstein》续集的创作,但她从未完成。在续集中,Shelley 不是让死尸复活,而是要做一件更加恐怖的事。Shelley 最终放弃了这本书,学者们认为其原因是她感到根本没有人能够承受这种恐惧。这同样说明了相同的问题:有些事情人们没有必要去尝试,这也包括编写用于监视性能的 WMI 脚本。

    文学上的巧合:您知道吗?Mary Shelley 在写《Frankenstein》时只有 19 岁,而 Microsoft Scripting Guys 的每个成员也恰好是这个年龄!
Shelley 没有坚持写完她的书,而许多人甚至可能没有意识到可以使用 WMI 来监视性能。但这却是真的。从 Microsoft® Windows® 2000 开始,Microsoft 向 WMI 中添加了称为 Win32_PerfRawData 的性能监视类。这些 Win32_PerfRawData 类能够让您做什么?其实最好这样问:它们不能让您做什么?使用 Win32_PerfRawData 类,您可以编写能够脚本来完成性能监视器所能完成的所有任务。您要监视什么?处理器使用情况、内存使用情况、网络连接、磁盘使用情况、Web 服务性能、服务器连接或打印机使用情况?Win32_PerfRawData 类可用于监视所有这些内容以及其他更多内容。此外,因为它们是 WMI 类,非常有利于脚本编写者使用。这些类确实很不错。

    注意:这意味着我们要展示给您的脚本可以在 Windows 2000、Windows XP 以及 Microsoft® Windows Server™ 2003 上使用。但是很抱歉,它们不能在 Windows NT 4.0 或 Windows 98 上使用。
那么,如果通过 WMI 进行性能监视有这么好,为什么没有人听说过它?为什么性能监视脚本示例在 Script Center 中并非随处可见?为什么没有人使用这种技术?我们这样来解释一下:让死尸复活,听起来是一个好主意,但是当 Victor Frankenstein 尝试后,他发现有许多意想不到的后果。而这也是一样:尝试编写 WMI 脚本来监视性能的人发现了许多意想不到的后果。

    让我们看一个经典的示例,即,使用一个性能监视类来确定磁盘驱动器上的磁盘可用空间百分比。以下是一个看上去会返回磁盘驱动器上的磁盘可用空间百分比的脚本:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colDisks = objWMIService.ExecQuery _
    ("Select * from Win32_PerfRawData_PerfDisk_LogicalDisk " _
        & "Where Name = 'C:'")
For Each objDisk in colDisks
    Wscript.Echo objDisk.PercentFreeSpace & "% 的空间可用。"
Next

    那么,如果在一台总硬盘空间为 11.5 GB 而磁盘可用空间为 4.2 GB 的计算机上运行此脚本,会发生什么情况呢?我们不假思索就可以算出大约有三分之一 (33%) 的磁盘空间未被使用,而脚本报告的结果为:

 


图 1:错误地报告磁盘可用空间百分比

    竟然有 4145% 磁盘可用空间!而我们一直认为 100% 是所能获得的最大百分比。(看上去似乎您在高中所学的东西中有 92% 都没有什么用,对不对?)

    但是不要太激动;您还没有发现某些新的磁盘压缩方法的强大功能呢。其原因是,Win32_PerfRawData 类正如其名称所暗示的:它们是“原始”数据类,这意味着返回的值并不一定是最终的值。您通常需要对其进行“修正”;也就是说,您必须通过某种数学公式对返回的数值进行计算以便获得真正的值。在本例中,我们需要将所获得的值 4145 乘以 100 并除以 11507,才能获得实际的磁盘可用空间百分比,即,更为真实的 36%。

    注意:不必担心,我们会解释所有这些数值都是怎么来的。
现在,如果所有性能计数器都使用相同的数学公式,则情况可能不会那么糟;您可以编写一个函数,通过该函数对返回的数据进行计算,然后报告“修正”后的结果。当然,这有些麻烦,但是没有更好的方法了。

    但实际情况是,并非所有的性能计数器都是相同的:有许多不同的性能计数器类型可供软件开发人员使用,并且这些不同的计数器类型似乎都使用了不同的数学公式。换句话说,您会(至少在理论上)发现自己在每个脚本中编写了 30 个函数,具体数目取决于您试图监视的内容。这让人感到无法承受,以至于大多数人甚至在开始之前便放弃了。

    但是,有些事情是大多数人所不知道的。在那本书中,Frankenstein 创造的怪物并不是一个真正的怪物。实际的情况是,每个人都认为他是一个怪物,并且让这种想法影响了他们的行为。使用 WMI 来监视性能也是如此。它其实不是那么糟;只是每个人都这样认为,并且让这种想法影响了他们的行为。但实际的情况是:使用 WMI 来监视性能并不是那么困难。实际上,当阅读完此本专栏后,您将能够充分利用 Win32_PerfRawData 类来编写脚本 - 我保证。

    注意:保证归保证,修行在个人。
等等,您可能会说:如果有 9 亿种不同的性能计数器类型可供软件开发人员使用呢?当然,这是正确的(可能有几亿的出入)。但是,实际使用的却只是其中的少数类型。事实上,您完全可以忽略绝大部分计数器类型。不相信吗?我们拿两台计算机,一台 Windows XP Professional 计算机和一台 Windows Server 2003 计算机,并使用一个脚本(稍后将展现给您)从各种 Win32_PerfRawData 类来检索各个性能计数器及其类型。以下是我们发现的结果汇总:

计数器类型 XP 上的实例 2003 上的实例
PERF_COUNTER_RAWCOUNT 475 750
PERF_COUNTER_COUNTER 211 320
PERF_COUNTER_LARGE_RAWCOUNT 97 122
PERF_COUNTER_BULK_COUNT 63 78
PERF_RAW_FRACTION 13 30
PERF_100NSEC_TIMER 23 23
PERF_PRECISION_100NS_TIMER 8 8
PERF_AVERAGE_BULK 6 6
PERF_AVERAGE_TIMER 6 6
PERF_COUNTER_100NS_QUELEN_TYPE 6 6
PERF_SAMPLE_FRACTION 0 5
PERF_ELAPSED_TIME 4 4
PERF_COUNTER_TIMER 0 2
PERF_100NSEC_TIMER_INV 1 1
PERF_COUNTER_RAWCOUNT_HEX 1 1
PERF_COUNTER_LARGE_RAWCOUNT_HEX 1 1
PERF_COUNTER_LARGE_QUELEN_TYPE 0 0
PERF_COUNTER_TIMER_INV 0 0
PERF_COUNTER_TEXT 0 0
PERF_COUNTER_MULTI_TIMER_INV 0 0
PERF_COUNTER_DELTA 0 0
PERF_COUNTER_LARGE_DELTA 0 0
PERF_SAMPLE_COUNTER 0 0
PERF_COUNTER_QUELEN_TYPE 0 0
PERF_PRECISION_SYSTEM_TIMER 0 0
PERF_OBJ_TIME_TIMER 0 0
PERF_COUNTER_MULTI_TIMER 0 0
PERF_100NSEC_MULTI_TIMER 0 0
PERF_100NSEC_MULTI_TIMER_INV 0 0
PERF_COUNTER_OBJ_TIME_QUELEN_TYPE 0 0


   如果看一下该表,您会发现一些有趣的事情:六种计数器类型(PERF_COUNTER_RAWCOUNT、PERF_COUNTER_COUNTER、PERF_COUNTER_LARGE_RAWCOUNT、PERF_COUNTER_BULK_COUNT、PERF_RAW_FRACTION 和 PERF_100NSEC_TIMER)几乎涵盖了 Windows XP 和 Windows Server 2003 上使用的所有性能计数器。(您可能还注意到,许多有效的计数器类型甚至没有使用。)在 Windows XP 上,96% 的性能计数器都属于这六种类型之一;在 Windows Server 2003 上,超过 97% 的计数器都属于这六种类型之一。也就是说,您只需了解如何使用这六种类型便可以监视几乎所有内容的性能。它是如此简单,甚至 Scripting Guys 中的一个成员就可以完成它!

    实际上,它甚至比这还要简单。有两种最常用的类型(PERF_COUNTER_RAWCOUNT 和 PERF_COUNTER_LARGE_RAWCOUNT)不需要任何计算;您可以直接使用返回的值。其他两种类型(PERF_COUNTER_COUNTER 和 PERF_COUNTER_BULK_COUNT)使用相同的公式。所以现在,我们只需了解三个公式。这样,当阅读完本文后,您将能够监视性能而无需了解任何内容。

    为使事情更简单,我们将向您展示如何使用其中的各种计数器类型来编写脚本。然后,我们将说明如何确定给定性能计数器的计数器类型。那时,您就可以让“死尸”复活了。或者,编写性能监视脚本。就像他们说的,一切顺利。

    注意一个问题
    在开始之前,我们需要指出一个重要问题,它适用于您使用的任何性能计数器类型。以下是一个看上去非常简单的脚本。它连接到 Win32_PerfRawData_PerfOS_Memory 类,然后回显 AvailableMbytes 计数器的值。(本示例选择 AvailableMbytes 是因为它是一个不需要修正的计数器。)此脚本将暂停 5 秒,然后不断回显 AvailableMbytes 的值,直到完成 20 次这种测量。

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery _
    ("Select * From Win32_PerfRawData_PerfOS_Memory")
For i = 1 to 20
    For Each objItem in colItems
        intValue = objItem.AvailableMbytes
        Wscript.Echo "可用内存 = " & intValue & " MB"
        Wscript.Sleep(5000)
    Next
Next

    假设您运行此脚本。在第一次循环中,如果您有 53 MB 的可用内存,则脚本将正确报告该情况。在第二次循环中,脚本仍将报告可用内存为 53 MB。实际上,在每次循环中它都将报告可用内存为 53 MB。您可以启动或停止程序;该脚本仍将报告可用内存为 53 MB。您可以启动服务、停止服务、通过网络复制 Windows Media 文件,甚至可以拆下计算机的外壳并取出所有 RAM 芯片,该脚本仍将报告可用内存为 53 MB。无论您做什么,该脚本都将“固执地”认为计算机有 53 MB 的可用内存。

    为什么是这样呢?我们仔细看一下前面脚本中的粗体行。您将注意到,在前面,我们检索了 Win32_PerfRawData_PerfOS_Memory 类的所有实例。结果,这将在我们运行 ExecQuery(您会看出它很重要,因为我们采用了斜体)时获得 Win32_PerfRawData_PerfOS_Memory 属性值的快照。然后,我们设置了一个循环 20 次的 For-Next 循环;在每次循环中,我们都报告 AvailableMbytes 属性的值。

    这看上去似乎没有问题,但实际上我们在这里犯了一个严重错误:我们只运行了一次 ExecQuery,这意味着我们只获得了内存性能计数器的一个快照。此后,我们从未获得过其他快照;从未检索过一组新的值。而是仅具有一个快照,并且只是反复显示该快照。在我们的 For-Next 循环中,回显了 AvailableMbytes 的值,但每次它都是相同的值;我们从未做过任何操作来更新它。如果我们在第一次循环中获得的是 53,则该脚本每次都会报告 53,因为我们没有废除它,也没有检索最新的一组数据。只是反复显示相同的内容。就像网络电视一样。

    如果这有些让人困惑,我们来看一个能够在每次循环中正确报告可用内存的脚本,这可能会有所帮助。请注意 ExecQuery 方法在此脚本中的位置:它位于 For-Next 循环的内部。这意味着我们将运行 20 次 ExecQuery(每次循环运行一次)。这样,每次我们运行循环,都会获得 AvailableMbytes 的当前值(即一个全新的快照)。结果,每次我们都将回显可用内存的真实数量,而不仅仅是第一次启动脚本时的可用内存数量。

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
For i = 1 to 20
    Set colItems = objWMIService.ExecQuery _
        ("Select * From Win32_PerfRawData_PerfOS_Memory")
    For Each objItem in colItems
        intValue = objItem.AvailableMbytes
        Wscript.Echo "可用内存 = " & intValue & " MB"
    Next
    Wscript.Sleep(5000)
Next

    要弄清楚这是如何工作的,最好是您自己尝试一下。在您的计算机上复制并运行这两个脚本。当这两个脚本运行时,打开并关闭一些程序。您会看到,在运行第二个脚本时,可用内存的数量将发生变化,而在运行第一个脚本时不会发生变化。这个示例说明:确保您的性能监视脚本始终检索当前的一组性能值。要实现此目的,请在每次要回显一组新值时都运行 ExecQuery。

    说明:的确,这有一些麻烦,并且如果您从多个类收集性能计数器(我们将在稍后讨论此问题),则会难以跟踪。在 Windows XP 和 Windows 2003 中,有一个新的 WMI 对象(SWbemRefresher 对象)可为您处理好这一切。您只需告诉 SwbemRefresher 要跟踪什么计数器,然后当任何时候希望检索所有性能计数器的一组最新值,只需在脚本中使用一行代码(它将调用该对象的 Refresh 方法)。在以后的专栏中,我们将告诉您如何完成此任务。
现在,我们快速了解一下这六种最常用的计数器类型。

PERF_COUNTER_RAWCOUNT
    可供您使用的性能计数器半数以上都是这种类型,这很好,因为这些计数器不需要进行数学计算,也不需要念什么咒语或任何其他类型的神奇转换。您只需检索值并直接使用它。如果此脚本报告 Microsoft® Word 打开了 916 个句柄,那么打电话给您的赌友,因为您可以打赌 Word 确实打开了 916 个句柄。

    注意:请不要打电话给您的赌友。让您下这种赌注我不能心安,特别是当我押了 5000 美元认为幼兽队能赢得世界职业棒球锦标赛后。
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
For i = 1 to 5
    Wscript.Sleep(1000)
    Set colItems = objWMIService.ExecQuery _
       ("Select * From Win32_PerfRawData_PerfProc_Process Where " & _
            "Name = 'WINWORD'")
    For Each objItem in colItems
        intValue = objItem.HandleCount
        Wscript.Echo "句柄数 = " & intValue
    Next
Next

    如果您认为:“哼,这对我来说类似于原来的常规 WMI 脚本”,则您猜会怎样:您是绝对正确的。我们将连接到 WMI 服务,从 Win32_PerfRawData_PerfProc_Process 类获取数据,然后回显属性值 HandleCount。这与大多数脚本之间的唯一不同在于:我们在循环内执行所有这些工作;这样,我们便可以多次回显句柄计数。(如果句柄计数稳定增长而从不下降,则通常表明存在内存泄漏。)

    注意:在上述脚本中,我们只对 Word 中的情况感兴趣,因此我们包含了 Where 子句 Where Name = 'WINWORD'。这样,该脚本将只报告 Winword.exe 的进程数据。如果我们要获得计算机上当前运行的所有进程的类似性能数据,该如何做?这不是什么问题,只需略去 Where 子句即可:
Set colItems = objWMIService.ExecQuery _
       ("Select * From Win32_PerfRawData_PerfProc_Process")

    当然,如果您这样做了,则可能应当同时回显进程名称和句柄计数。这样,您便可以将句柄计数与进程相匹配。

PERF_COUNTER_LARGE_RAWCOUNT
这是另一个可直接使用的计数器类型;您只需检索并报告值。那还有什么可怕的吗?

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
For i = 1 to 5
    Wscript.Sleep(1000)
    Set colItems = objWMIService.ExecQuery _
        ("Select * From Win32_PerfRawData_PerfProc_Process Where " & _
            "Name = 'WINWORD'")
    For Each objItem in colItems
        intValue = objItem.WorkingSet
        Wscript.Echo "工作集 = " & intValue
    Next
Next

    PERF_RAW_FRACTION
    到目前为止,一切都很好。无需付出太多努力,就可以获得许多有用的性能信息。但是对于 PERF_RAW_FRACTION,就没有这么好了;我们不得不开始第一次计算。但是,老兄,这又有什么难的?

(100 * CounterValue) / BaseValue

    困难之处在于弄清变量代表的内容。因此,我们将告诉您这些。在此公式中,CounterValue 代表性能计数器的值,而 BaseValue 数值用于保证计算的结果是正确的。坦白地讲,我们并不确切知道 BaseValue 是如何计算出来的。幸运的是,我们不需要知道它是如何计算出来的,只要我们能够找出该值是什么就行,我们马上向您展示如何完成此任务。

    以下是该公式在某个实际脚本中的样子:我们连接到 WMI 服务,从 Win32_PerfRawData_PerfDisk_LogicalDisk 类获取数据,获取 PercentFreeSpace 计数器的值,将其乘以 100,然后将所得的结果除以基础值 (11507)。

intBaseValue = 11507
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colDisks = objWMIService.ExecQuery _
    ("Select * from Win32_PerfRawData_PerfDisk_LogicalDisk Where Name = 'C:'")
For Each objDisk in colDisks
    dblActualFreeSpace = (100 * objDisk.PercentFreeSpace) / intBaseValue
    Wscript.Echo Int(dblActualFreeSpace)
Next

    听起来好像有许多工作要做,但实际上没有那么复杂。我们假设 PercentFreeSpace 返回的值为 4145。在这种情况下,计算将如下所示:

(100 * 4145) / 11507
或者:
414500 / 11507 = 36
实际上,除以下一点外,这是非常简单的:如何知道 PercentFreeSpace 的基础值为 11507?我查阅了一下。只要您具有一个 PERF_RAW_FRACTION 性能计数器,就会还具有一个伴随的基础值性能计数器。要找到该计数器,只需打开 Wbemtest 进行查看即可:

图 2:在 Wbemtest 中找到基础值性能计数器

    那么,我们接着如何获得 PercentFreeSpace_Base 的值呢?在 Wbemtest 中单击 Instances(实例)按钮,然后双击所得到的任何实例(单击任何一个都可以)。PercentFreeSpace_Base 的值就是您将在脚本中使用的基础值。

 

图 3:确定基础值性能计数器的值

    这里只有一件事情需要注意:PercentFreeSpace_Base 的值会随着您的硬件、操作系统、时间以及其他内容的不同而不同。(实际上,它取决于您的逻辑磁盘的大小。)因此,您不能将其硬编码为某个数值(例如 11507,这只适用于特定大小的磁盘),而应当使用 PercentFreeSpace_Base 的检索值。请注意以下脚本中的粗体行。

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colDisks = objWMIService.ExecQuery _
    ("Select * from Win32_PerfRawData_PerfDisk_LogicalDisk Where Name = 'C:'")
For Each objDisk in colDisks
    intBaseValue = objDisk.PercentFreeSpace_Base
    dblActualFreeSpace = (100 * objDisk.PercentFreeSpace) / intBaseValue
    Wscript.Echo Int(dblActualFreeSpace)
Next

    提示:如何知道脚本是否返回了正确的数据?这里有一种快捷方法可以仔细检查您的工作。启动性能监视器并加载您的脚本中正在运行的性能计数器。同时运行该脚本,观察并比较您的值与性能监视器的值。这将有助于您知道返回的数据是否正确。
PERF_COUNTER_COUNTER
仍然在看吗?好的。接下来,这个类型看上去可能有点难对付,但是不必惊慌。做一次深呼吸并保持冷静,您会发现根本不像您开始想象的那样糟。

    PERF_COUNTER_COUNTER(与 Boutros Boutros-Ghali [英文] 没有关系)用于测量每秒发生的事件数(“事件”的定义视不同的计数器而定)。让我们暂时忘记性能监视(你们中的某些人可能在几页之前就已经忘记了),考虑一下将如何测量每秒发生的事情数。这实际非常简单。

    计算出发生了多少事件。
计算出用了多长时间。
将所用的时间转换为秒。例如,如果所用的时间为 2 分钟,则需要乘以 60,将 2 分钟转换为 120 秒。
PERF_COUNTER_COUNTER 的基本思想与此相同:

    获取两个测量值(CounterValue1 和 CounterValue2),然后计算出在这两个测量值之间发生了多少事情(CounterValue2 减去 CounterValue1)。
通过以下方式计算所用的时间:获取开始测量时的时间 (TimeValue1),然后从结束测量时的时间中减去该时间(TimeValue2 减去 TimeValue1)。
除以时间基数,将所用的时间转换为秒(操作系统通常以毫微秒为单位来测量事件)。
以下是 PERF_COUNTER_COUNTER 的实际公式:

(CounterValue2 - CounterValue1) / ((TimeValue2 - TimeValue1) / TimeBase)

   当然,我们知道这看上去有点复杂,但是请注意:在此公式中只有五个变量,我们即将讨论其中的三个。请看下面的脚本。看到那三个粗体行了吗?这里我们查询了 Win32_PerfRawData_PerfOS_Processor 类并检索了三个属性值:

    InterruptsPerSec。这是我们要测量的 PERF_COUNTER_COUNTER 值。我们将通过它获取一个值,该值将作为等式中的 CounterValue1。
TimeStamp_PerfTime。这是我们的开始时间(即,TimeValue1)。我们如何知道使用它作为开始时间?这将在后面解释。
    Frequency_PerfTime。这是我们的 TimeBase。基本上讲,该值用于确保得到的时间以秒为单位(因为我们要测量每秒的中断数)。同样,我们稍后将解释其来源。
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery _
    ("Select * From Win32_PerfRawData_PerfOS_Processor Where Name = '0'")
For Each objItem in colItems
    CounterValue1 = objItem.InterruptsPerSec
    TimeValue1 = objItem.TimeStamp_PerfTime
    TimeBase = objItem.Frequency_PerfTime
Next
For i = 1 to 5
    Wscript.Sleep(1000)
    Set colItems = objWMIService.ExecQuery _
       ("Select * From Win32_PerfRawData_PerfOS_Processor Where Name = '0'")
    For Each objItem in colItems
        CounterValue2 = objItem.InterruptsPerSec
        TimeValue2 = objItem.TimeStamp_PerfTime
        If TimeValue2 - TimeValue1 = 0 Then
            Wscript.Echo "每秒的中断数 = 0"
        Else
            intInterrupts = (CounterValue2 - CounterValue1) / _
                ( (TimeValue2 - TimeValue1) / TimeBase)
            Wscript.Echo "每秒的中断数 = " & Int(intInterrupts)
        End if
        CounterValue1 = CounterValue2
        TimeValue1 = TimeValue2
    Next
Next

也就是说,只需几行代码,我们就已经计算出了五个变量中的三个:

(CounterValue2 - CounterValue1) / ((TimeValue2 - TimeValue1) / TimeBase)

那么,怎样获得其他两个变量?这很简单。我们创建另一个 For-Next 循环并进行 ExecQuery 调用,就像前面的脚本中所做的那样。然后,获取 InterruptsPerSec 的当前值并将其赋给 CounterValue2,获取 TimeStamp_PerfTime 的当前值并将其赋给 TimeValue2。(我们不需要检索 Frequency_PerfTime,因为该值保持不变。)现在,我们获取了所有变量:

(InterruptsPerSec No. 2 - InterruptsPerSec No. 1 ) / _
    ((TimeStamp_PerfTime No. 2 - TimeStamp_PerfTime No. 1)_
         / Frequency_PerfTime

基本上到此为止已经全部完成了。如果我们只获取一个测量值,则可以运行该公式,回显结果,并使用此脚本完成任务。但是,因为我们要获取多个测量值,所以需要进行另一项任务。在接近脚本结尾处的两个粗体行中,我们将第二组计数器数据的值赋给 CounterValue1,将第二个时间戳赋给 TimeValue1。为什么是这样呢?假设我们的计数器值如下:

CounterValue1: 14
CounterValue2: 25
通过从 25 中减去 14,我们知道在测量间隔内发生了 11 次中断。假设现在我们决定再次测量中断数。我们不希望 CounterValue1 等于 14;该信息是旧信息且过期了。对于下一轮测量,我们不是从 14 开始,而是从 25 开始。因此,我们将 25(CounterValue2 的值)赋给 CounterValue1。下一轮测量的 CounterValue2 可能是,例如,37,因此,我们的公式将为 37-25,即 12 次中断。明白了吗?我们对时间戳也这样处理,以确保我们同样具有正确的时间间隔。

PERF_COUNTER_BULK_COUNT
计算 PERF_COUNTER_COUNTER 的值就不那么有趣了。(或者,就像 Frankenstein 创造的超人所说:“我看到每个人都很快乐,而我却只有孤寂。”因此,毫不奇怪,制片人决定不让他在影片中这么说!)

但是,从好的一方面看,我们确实获得了一石二鸟的效果:不管怎样,当您知道了如何计算 PERF_COUNTER_COUNTER 后,也就同时知道了如何计算 PERF_COUNTER_BULK_COUNT。这两者使用相同的公式。

请动物权利保护主义者注意:我们在编写此专栏时并未真正杀死任何鸟类,尽管我们的编辑总是抱怨每次看到对《Frankenstein》的引用时胃就感到不舒服。
不信吗?请看下面的 PERF_COUNTER_BULK_COUNT 脚本。看到粗体项了吗?那些特定于类的更改表示此脚本与 PER_COUNTER_COUNTER 脚本之间仅有的不同。

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery _
    ("Select * From Win32_PerfRawData_PerfOS_System")
For Each objItem in colItems
    CounterValue1 = objItem.FileControlBytesPerSec
    TimeValue1 = objItem.TimeStamp_PerfTime
    TimeBase = objItem.Frequency_PerfTime
Next
For i = 1 to 5
    Wscript.Sleep(1000)
    Set colitems = objWMIService.ExecQuery _
        ("Select * From Win32_PerfRawData_PerfOS_System")
    For Each objItem in colItems
        CounterValue2 = objItem.FileControlBytesPerSec
        TimeValue2 = objItem.TimeStamp_PerfTime
        If TimeValue2 - TimeValue1 = 0 Then
            Wscript.Echo "File Control Bytes Per Second = 0"
        Else
            intInterrupts = (CounterValue2 - CounterValue1) / _
                ( (TimeValue2 - TimeValue1) / TimeBase)
            Wscript.Echo "File Control Bytes Per Second = " & intInterrupts
        End if
        CounterValue1 = CounterValue2
        TimeValue1 = TimeValue2
    Next
Next

    提示:您是否在想我们所想的事情?是否在想此专栏中的示例脚本可以作为您自己的性能监视脚本的绝好模板?是否在想只需通过复制这些脚本并更改不同地方的类特定属性即可轻松创建自己的脚本?这可不是我们所想的内容,但这仍然是一个相当好的主意。
PERF_100NSEC_TIMER
现在介绍最后一种计数器类型 PERF_100NSEC_TIMER,但是,它绝对不是我们最常用的性能计数器类型中最不常用的一个。此计数器类型的公式并不那么复杂,同五个变量相比,它只有四个:

    100* (CounterValue2 - CounterValue1) / (TimeValue2 - TimeValue1)

    实际上,此计数器类型公式和我们展现给您的上一个计数器类型公式之间仅有的不同在于:

    这里,您不必担心时间基数;计算中并不使用它。
使用 TimeStamp_Sys100NS 来确定时间间隔,而不是使用 TimeStamp_PerfTime 属性。
这最后一点您可能需要注意。如何知道何时使用 TimeStamp_PerfTime 以及何时使用 TimeStamp_Sys100NS?我们稍后将给出一些说明。但是请注意,只要知道了计数器类型,便可以知道要使用的时间间隔属性。如果您具有一个 PERF_100NSEC_TIMER 计数器,则将始终使用 TimeStamp_Sys100NS。

    为什么是这样呢?原因实际上很简单:如果不使用 TimeStamp_Sys100NS,则根据定义,您不会具有一个 PERF_100NSEC_TIMER 计数器。

    当然,这是一个粗略的解释,但也是您不必担心的事情。无论是谁创建了 PERF_100NSEC_TIMER 计数器类型,他在公式中也使用 TimeStamp_Sys100NS,这意味着您在处理此计数器类型时将始终使用 TimeStamp_Sys100NS。为什么他们使用 / 而不是 \ 来表示除法?谁知道?但是这没有关系,因为任何时候您要进行除法运算时都使用 /。这是常用的不变规律之一。对于性能计数器类型也是如此。

    以下是使用的一个 PERF_100NSEC_TIMER 示例。同样,粗体项表示:当您决定修改此脚本并检索不同性能计数器的值时,所必须更改的仅有的项。

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery _
    ("Select * From Win32_PerfRawData_PerfOS_Processor Where Name = '0'")
For Each objItem in colItems
    CounterValue1 = objItem.PercentUserTime
    TimeValue1 = objItem.TimeStamp_Sys100NS
Next
For i = 1 to 5
    Wscript.Sleep(1000)
    Set colItems = objWMIService.ExecQuery _
       ("Select * From Win32_PerfRawData_PerfOS_Processor Where Name = '0'")
    For Each objItem in colItems
        CounterValue2 = objItem.PercentUserTime
        TimeValue2 = objItem.TimeStamp_Sys100NS
        If TimeValue2 - TimeValue1 = 0 Then
            Wscript.Echo "Percent User Time = 0%"
        Else
            PercentProcessorTime = 100 * (CounterValue2 - CounterValue1) / _
                (TimeValue2 - TimeValue1)
            Wscript.Echo "Percent User Time = " & _
                PercentProcessorTime & "%"
        End if
        CounterValue1 = CounterValue2
        TimeValue1 = TimeValue2
    Next
Next

    更有用的脚本:从多个类检索值
    我们在此专栏中尝试做的一件事情是让我们的示例尽可能简单。因此,我们没有说明某些您可能必须要做的事情:从多个 Win32_PerfRawData 类检索性能计数器。例如,您可能需要同时知道可用内存(通过 Win32_PerfRawData_PerfOS_Memory 获得)和磁盘可用空间(通过 Win32_PerfRawData_PerfDisk_LogicalDisk 获得)。能否在一个脚本中完成此任务?或者,是否需要分别为每个性能类编写单独的脚本?

    答案是,可以在一个脚本中完成它;只是必须要为每个性能类进行单独的 ExecQuery 调用。(为什么是这样呢?因为 WMI 不允许采用“Select * From Win32_PerfRawData_PerfOS_Memory and * From Win32_PerfRawData_PerfDisk_LogicalDisk”这样的行来编写查询。)

    以下是从两个不同的类检索性能计数器值的示例脚本。请注意粗体行;在 For-Next 循环中有单独的 ExecQuery 调用(针对每个性能类都有一个调用)。

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
For i = 1 to 5
    Wscript.Sleep(5000)
    Set colItems = objWMIService.ExecQuery _
        ("Select * From Win32_PerfRawData_PerfOS_Memory")
    For Each objItem in colItems
        intValue = objItem.AvailableMbytes
        Wscript.Echo "可用内存 = " & intValue & " MB"
    Next
    Set colItems = objWMIService.ExecQuery _
        ("Select * From Win32_PerfRawData_PerfDisk_LogicalDisk Where " & _
            "Name = 'C:'")
    For Each objItem in colItems
        intValue = objItem.FreeMegabytes
        Wscript.Echo "Available disk space = " & intValue & " megabytes"
    Next
Next

    升级的一个原因:在 Windows XP 和 Windows Server 2003 中,有一个更加清楚、灵巧、简便的方法来检索和更新从多个类获取的性能计数器。同样,我们将在以后的专栏中讨论它。
如何知道性能计数器属于哪种类型?
好的。那么现在,我们已经为您展现了六种基本计数器类型,还说明了如何在脚本中使用每种计数器类型。但仍然留下了一个主要问题:如果查看 Win32_PerfRawData_PerfOS_Processor 类并看到一个有趣的属性(例如 PercentProcessorTime),则如何知道单个计数器属于这六种类型中的哪一种(如果有)?

    这需要深入探讨一下,但是通过利用 WMI SDK(英文)和 MSDN(英文),您可以自己确定这些公式,这是人力所能及的。(当然,攀登珠穆朗玛峰也是人力所能及的,但是我们并不会建议大家都冲出去并进行尝试。)在 WMI SDK 中,您将发现 Win32_PerfRawData 类中的每个属性都具有如下信息:

PercentProcessorTime
数据类型:uint64
访问类型:只读
限定符:DisplayName("% Processor Time")、PerfDefault、CounterType(558957824)、DefaultScale(0)、PerfDetail(100)
出于我们目的的需要,我们对 CounterType 限定符(本例中是 558957824)感兴趣。如果您在 WMI SDK 中搜索 558957824,则最终会发现这是一个十进制值,表示一种 PERF_100NSEC_TIMER_INV 计数器类型。借助该信息,您便可以接着利用 MSDN,在那里您将发现与下面完全相同的表:

运行情况如何?使用 WMI 监视性能
Greg Stemp
Microsoft Corporation

2003 年 11 月 10 日

摘要:WMI 提供了一整套易于使用的性能计数器类。本文介绍如何利用这些类创建监视脚本来监视处理器使用情况、内存使用情况、网络连接、磁盘使用情况、Web 服务性能、服务器连接、打印机使用情况以及其他各种性能参数。


好久不见,大家好吗?很抱歉,很久没来这里了。但是,坦率地讲,我们这些 Scripting Guys 还有许多其他重要任务,这些任务比脚本编写工作更重要,因此我们不得不将诸如 Scripting Clinic 专栏之类相对次要的工作先放在一边,而专注于真正有益于 Microsoft 的事情。您不会认为 Bill Gates 的车会自行清洗吧?

然而,实际上 Bill 的车确实会自行清洗。当然,大多数其他 Microsoft 执行官则没有这么好的车,结果,他们就只能自己一次又一次地洗车。随着天气越来越冷,草长得也不那么快了,因此我就有时间,可以再次坐到计算机前,打造一个全新的 Scripting Clinic 专栏。

但是,在开始之前,我需要确保阅读此专栏的每个人都熟悉《Frankenstein》(英文)故事。现在,无疑你们都读过了这本书(当然,这也意味着你们都看过了这部电影)。但是为了预防万一,我们还是简单回顾一下:一位名叫 Victor Frankenstein 的雄心勃勃的青年科学家决定收集一些死尸,然后从每个死尸上截取最好的部分并将这些部分缝合在一起,再赋予这个“人”生命,来创造一个超人。(顺便提一句,这个过程与 Peter Costantini 加入最初只有四个人的 Scripting Guys 团队非常相似。)事情最初进展得非常顺利,但是您知道当赋予死尸以生命后会怎样:无论初衷如何,坏事总是坏事。简而言之,情况很快就变得不可收拾,最后甚至每个相关的人都死了。

当然,《Frankenstein》的寓意是很清楚的:有些事情人们根本就不应该去尝试;特别是像“让死尸复活”这样的想法。(或者至少,如果您要让死尸复活,最好从一些较小且容易控制的对象开始,例如沙鼠或仓鼠。)从《Frankenstein》中我们所有人都可以得到这样一个教训:有些事情最好不要去做。这是我每次接受新任务时都试图遵循的一点。

那么,为什么我要谈论 Frankenstein 而不去打扫人行道、喝杯咖啡或是做些有用的事呢?其实,很少有人会注意到这一点,Mary Shelley 实际上已经开始了《Frankenstein》续集的创作,但她从未完成。在续集中,Shelley 不是让死尸复活,而是要做一件更加恐怖的事。Shelley 最终放弃了这本书,学者们认为其原因是她感到根本没有人能够承受这种恐惧。这同样说明了相同的问题:有些事情人们没有必要去尝试,这也包括编写用于监视性能的 WMI 脚本。

文学上的巧合:您知道吗?Mary Shelley 在写《Frankenstein》时只有 19 岁,而 Microsoft Scripting Guys 的每个成员也恰好是这个年龄!
Shelley 没有坚持写完她的书,而许多人甚至可能没有意识到可以使用 WMI 来监视性能。但这却是真的。从 Microsoft® Windows® 2000 开始,Microsoft 向 WMI 中添加了称为 Win32_PerfRawData 的性能监视类。这些 Win32_PerfRawData 类能够让您做什么?其实最好这样问:它们不能让您做什么?使用 Win32_PerfRawData 类,您可以编写能够脚本来完成性能监视器所能完成的所有任务。您要监视什么?处理器使用情况、内存使用情况、网络连接、磁盘使用情况、Web 服务性能、服务器连接或打印机使用情况?Win32_PerfRawData 类可用于监视所有这些内容以及其他更多内容。此外,因为它们是 WMI 类,非常有利于脚本编写者使用。这些类确实很不错。

注意:这意味着我们要展示给您的脚本可以在 Windows 2000、Windows XP 以及 Microsoft® Windows Server™ 2003 上使用。但是很抱歉,它们不能在 Windows NT 4.0 或 Windows 98 上使用。
那么,如果通过 WMI 进行性能监视有这么好,为什么没有人听说过它?为什么性能监视脚本示例在 Script Center 中并非随处可见?为什么没有人使用这种技术?我们这样来解释一下:让死尸复活,听起来是一个好主意,但是当 Victor Frankenstein 尝试后,他发现有许多意想不到的后果。而这也是一样:尝试编写 WMI 脚本来监视性能的人发现了许多意想不到的后果。

让我们看一个经典的示例,即,使用一个性能监视类来确定磁盘驱动器上的磁盘可用空间百分比。以下是一个看上去会返回磁盘驱动器上的磁盘可用空间百分比的脚本:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colDisks = objWMIService.ExecQuery _
    ("Select * from Win32_PerfRawData_PerfDisk_LogicalDisk " _
        & "Where Name = 'C:'")
For Each objDisk in colDisks
    Wscript.Echo objDisk.PercentFreeSpace & "% 的空间可用。"
Next

那么,如果在一台总硬盘空间为 11.5 GB 而磁盘可用空间为 4.2 GB 的计算机上运行此脚本,会发生什么情况呢?我们不假思索就可以算出大约有三分之一 (33%) 的磁盘空间未被使用,而脚本报告的结果为:

 

图 1:错误地报告磁盘可用空间百分比

竟然有 4145% 磁盘可用空间!而我们一直认为 100% 是所能获得的最大百分比。(看上去似乎您在高中所学的东西中有 92% 都没有什么用,对不对?)

但是不要太激动;您还没有发现某些新的磁盘压缩方法的强大功能呢。其原因是,Win32_PerfRawData 类正如其名称所暗示的:它们是“原始”数据类,这意味着返回的值并不一定是最终的值。您通常需要对其进行“修正”;也就是说,您必须通过某种数学公式对返回的数值进行计算以便获得真正的值。在本例中,我们需要将所获得的值 4145 乘以 100 并除以 11507,才能获得实际的磁盘可用空间百分比,即,更为真实的 36%。

注意:不必担心,我们会解释所有这些数值都是怎么来的。
现在,如果所有性能计数器都使用相同的数学公式,则情况可能不会那么糟;您可以编写一个函数,通过该函数对返回的数据进行计算,然后报告“修正”后的结果。当然,这有些麻烦,但是没有更好的方法了。

但实际情况是,并非所有的性能计数器都是相同的:有许多不同的性能计数器类型可供软件开发人员使用,并且这些不同的计数器类型似乎都使用了不同的数学公式。换句话说,您会(至少在理论上)发现自己在每个脚本中编写了 30 个函数,具体数目取决于您试图监视的内容。这让人感到无法承受,以至于大多数人甚至在开始之前便放弃了。

但是,有些事情是大多数人所不知道的。在那本书中,Frankenstein 创造的怪物并不是一个真正的怪物。实际的情况是,每个人都认为他是一个怪物,并且让这种想法影响了他们的行为。使用 WMI 来监视性能也是如此。它其实不是那么糟;只是每个人都这样认为,并且让这种想法影响了他们的行为。但实际的情况是:使用 WMI 来监视性能并不是那么困难。实际上,当阅读完此本专栏后,您将能够充分利用 Win32_PerfRawData 类来编写脚本 - 我保证。

注意:保证归保证,修行在个人。
等等,您可能会说:如果有 9 亿种不同的性能计数器类型可供软件开发人员使用呢?当然,这是正确的(可能有几亿的出入)。但是,实际使用的却只是其中的少数类型。事实上,您完全可以忽略绝大部分计数器类型。不相信吗?我们拿两台计算机,一台 Windows XP Professional 计算机和一台 Windows Server 2003 计算机,并使用一个脚本(稍后将展现给您)从各种 Win32_PerfRawData 类来检索各个性能计数器及其类型。以下是我们发现的结果汇总:

计数器类型 XP 上的实例 2003 上的实例
PERF_COUNTER_RAWCOUNT 475 750
PERF_COUNTER_COUNTER 211 320
PERF_COUNTER_LARGE_RAWCOUNT 97 122
PERF_COUNTER_BULK_COUNT 63 78
PERF_RAW_FRACTION 13 30
PERF_100NSEC_TIMER 23 23
PERF_PRECISION_100NS_TIMER 8 8
PERF_AVERAGE_BULK 6 6
PERF_AVERAGE_TIMER 6 6
PERF_COUNTER_100NS_QUELEN_TYPE 6 6
PERF_SAMPLE_FRACTION 0 5
PERF_ELAPSED_TIME 4 4
PERF_COUNTER_TIMER 0 2
PERF_100NSEC_TIMER_INV 1 1
PERF_COUNTER_RAWCOUNT_HEX 1 1
PERF_COUNTER_LARGE_RAWCOUNT_HEX 1 1
PERF_COUNTER_LARGE_QUELEN_TYPE 0 0
PERF_COUNTER_TIMER_INV 0 0
PERF_COUNTER_TEXT 0 0
PERF_COUNTER_MULTI_TIMER_INV 0 0
PERF_COUNTER_DELTA 0 0
PERF_COUNTER_LARGE_DELTA 0 0
PERF_SAMPLE_COUNTER 0 0
PERF_COUNTER_QUELEN_TYPE 0 0
PERF_PRECISION_SYSTEM_TIMER 0 0
PERF_OBJ_TIME_TIMER 0 0
PERF_COUNTER_MULTI_TIMER 0 0
PERF_100NSEC_MULTI_TIMER 0 0
PERF_100NSEC_MULTI_TIMER_INV 0 0
PERF_COUNTER_OBJ_TIME_QUELEN_TYPE 0 0


如果看一下该表,您会发现一些有趣的事情:六种计数器类型(PERF_COUNTER_RAWCOUNT、PERF_COUNTER_COUNTER、PERF_COUNTER_LARGE_RAWCOUNT、PERF_COUNTER_BULK_COUNT、PERF_RAW_FRACTION 和 PERF_100NSEC_TIMER)几乎涵盖了 Windows XP 和 Windows Server 2003 上使用的所有性能计数器。(您可能还注意到,许多有效的计数器类型甚至没有使用。)在 Windows XP 上,96% 的性能计数器都属于这六种类型之一;在 Windows Server 2003 上,超过 97% 的计数器都属于这六种类型之一。也就是说,您只需了解如何使用这六种类型便可以监视几乎所有内容的性能。它是如此简单,甚至 Scripting Guys 中的一个成员就可以完成它!

    实际上,它甚至比这还要简单。有两种最常用的类型(PERF_COUNTER_RAWCOUNT 和 PERF_COUNTER_LARGE_RAWCOUNT)不需要任何计算;您可以直接使用返回的值。其他两种类型(PERF_COUNTER_COUNTER 和 PERF_COUNTER_BULK_COUNT)使用相同的公式。所以现在,我们只需了解三个公式。这样,当阅读完本文后,您将能够监视性能而无需了解任何内容。

为使事情更简单,我们将向您展示如何使用其中的各种计数器类型来编写脚本。然后,我们将说明如何确定给定性能计数器的计数器类型。那时,您就可以让“死尸”复活了。或者,编写性能监视脚本。就像他们说的,一切顺利。

注意一个问题
在开始之前,我们需要指出一个重要问题,它适用于您使用的任何性能计数器类型。以下是一个看上去非常简单的脚本。它连接到 Win32_PerfRawData_PerfOS_Memory 类,然后回显 AvailableMbytes 计数器的值。(本示例选择 AvailableMbytes 是因为它是一个不需要修正的计数器。)此脚本将暂停 5 秒,然后不断回显 AvailableMbytes 的值,直到完成 20 次这种测量。

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery _
    ("Select * From Win32_PerfRawData_PerfOS_Memory")
For i = 1 to 20
    For Each objItem in colItems
        intValue = objItem.AvailableMbytes
        Wscript.Echo "可用内存 = " & intValue & " MB"
        Wscript.Sleep(5000)
    Next
Next

假设您运行此脚本。在第一次循环中,如果您有 53 MB 的可用内存,则脚本将正确报告该情况。在第二次循环中,脚本仍将报告可用内存为 53 MB。实际上,在每次循环中它都将报告可用内存为 53 MB。您可以启动或停止程序;该脚本仍将报告可用内存为 53 MB。您可以启动服务、停止服务、通过网络复制 Windows Media 文件,甚至可以拆下计算机的外壳并取出所有 RAM 芯片,该脚本仍将报告可用内存为 53 MB。无论您做什么,该脚本都将“固执地”认为计算机有 53 MB 的可用内存。

为什么是这样呢?我们仔细看一下前面脚本中的粗体行。您将注意到,在前面,我们检索了 Win32_PerfRawData_PerfOS_Memory 类的所有实例。结果,这将在我们运行 ExecQuery(您会看出它很重要,因为我们采用了斜体)时获得 Win32_PerfRawData_PerfOS_Memory 属性值的快照。然后,我们设置了一个循环 20 次的 For-Next 循环;在每次循环中,我们都报告 AvailableMbytes 属性的值。

这看上去似乎没有问题,但实际上我们在这里犯了一个严重错误:我们只运行了一次 ExecQuery,这意味着我们只获得了内存性能计数器的一个快照。此后,我们从未获得过其他快照;从未检索过一组新的值。而是仅具有一个快照,并且只是反复显示该快照。在我们的 For-Next 循环中,回显了 AvailableMbytes 的值,但每次它都是相同的值;我们从未做过任何操作来更新它。如果我们在第一次循环中获得的是 53,则该脚本每次都会报告 53,因为我们没有废除它,也没有检索最新的一组数据。只是反复显示相同的内容。就像网络电视一样。

如果这有些让人困惑,我们来看一个能够在每次循环中正确报告可用内存的脚本,这可能会有所帮助。请注意 ExecQuery 方法在此脚本中的位置:它位于 For-Next 循环的内部。这意味着我们将运行 20 次 ExecQuery(每次循环运行一次)。这样,每次我们运行循环,都会获得 AvailableMbytes 的当前值(即一个全新的快照)。结果,每次我们都将回显可用内存的真实数量,而不仅仅是第一次启动脚本时的可用内存数量。

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
For i = 1 to 20
    Set colItems = objWMIService.ExecQuery _
        ("Select * From Win32_PerfRawData_PerfOS_Memory")
    For Each objItem in colItems
        intValue = objItem.AvailableMbytes
        Wscript.Echo "可用内存 = " & intValue & " MB"
    Next
    Wscript.Sleep(5000)
Next

要弄清楚这是如何工作的,最好是您自己尝试一下。在您的计算机上复制并运行这两个脚本。当这两个脚本运行时,打开并关闭一些程序。您会看到,在运行第二个脚本时,可用内存的数量将发生变化,而在运行第一个脚本时不会发生变化。这个示例说明:确保您的性能监视脚本始终检索当前的一组性能值。要实现此目的,请在每次要回显一组新值时都运行 ExecQuery。

说明:的确,这有一些麻烦,并且如果您从多个类收集性能计数器(我们将在稍后讨论此问题),则会难以跟踪。在 Windows XP 和 Windows 2003 中,有一个新的 WMI 对象(SWbemRefresher 对象)可为您处理好这一切。您只需告诉 SwbemRefresher 要跟踪什么计数器,然后当任何时候希望检索所有性能计数器的一组最新值,只需在脚本中使用一行代码(它将调用该对象的 Refresh 方法)。在以后的专栏中,我们将告诉您如何完成此任务。
现在,我们快速了解一下这六种最常用的计数器类型。

PERF_COUNTER_RAWCOUNT
可供您使用的性能计数器半数以上都是这种类型,这很好,因为这些计数器不需要进行数学计算,也不需要念什么咒语或任何其他类型的神奇转换。您只需检索值并直接使用它。如果此脚本报告 Microsoft® Word 打开了 916 个句柄,那么打电话给您的赌友,因为您可以打赌 Word 确实打开了 916 个句柄。

注意:请不要打电话给您的赌友。让您下这种赌注我不能心安,特别是当我押了 5000 美元认为幼兽队能赢得世界职业棒球锦标赛后。
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
For i = 1 to 5
    Wscript.Sleep(1000)
    Set colItems = objWMIService.ExecQuery _
       ("Select * From Win32_PerfRawData_PerfProc_Process Where " & _
            "Name = 'WINWORD'")
    For Each objItem in colItems
        intValue = objItem.HandleCount
        Wscript.Echo "句柄数 = " & intValue
    Next
Next

如果您认为:“哼,这对我来说类似于原来的常规 WMI 脚本”,则您猜会怎样:您是绝对正确的。我们将连接到 WMI 服务,从 Win32_PerfRawData_PerfProc_Process 类获取数据,然后回显属性值 HandleCount。这与大多数脚本之间的唯一不同在于:我们在循环内执行所有这些工作;这样,我们便可以多次回显句柄计数。(如果句柄计数稳定增长而从不下降,则通常表明存在内存泄漏。)

注意:在上述脚本中,我们只对 Word 中的情况感兴趣,因此我们包含了 Where 子句 Where Name = 'WINWORD'。这样,该脚本将只报告 Winword.exe 的进程数据。如果我们要获得计算机上当前运行的所有进程的类似性能数据,该如何做?这不是什么问题,只需略去 Where 子句即可:
Set colItems = objWMIService.ExecQuery _
       ("Select * From Win32_PerfRawData_PerfProc_Process")

当然,如果您这样做了,则可能应当同时回显进程名称和句柄计数。这样,您便可以将句柄计数与进程相匹配。

PERF_COUNTER_LARGE_RAWCOUNT
这是另一个可直接使用的计数器类型;您只需检索并报告值。那还有什么可怕的吗?

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
For i = 1 to 5
    Wscript.Sleep(1000)
    Set colItems = objWMIService.ExecQuery _
        ("Select * From Win32_PerfRawData_PerfProc_Process Where " & _
            "Name = 'WINWORD'")
    For Each objItem in colItems
        intValue = objItem.WorkingSet
        Wscript.Echo "工作集 = " & intValue
    Next
Next

PERF_RAW_FRACTION
到目前为止,一切都很好。无需付出太多努力,就可以获得许多有用的性能信息。但是对于 PERF_RAW_FRACTION,就没有这么好了;我们不得不开始第一次计算。但是,老兄,这又有什么难的?

(100 * CounterValue) / BaseValue

困难之处在于弄清变量代表的内容。因此,我们将告诉您这些。在此公式中,CounterValue 代表性能计数器的值,而 BaseValue 数值用于保证计算的结果是正确的。坦白地讲,我们并不确切知道 BaseValue 是如何计算出来的。幸运的是,我们不需要知道它是如何计算出来的,只要我们能够找出该值是什么就行,我们马上向您展示如何完成此任务。

以下是该公式在某个实际脚本中的样子:我们连接到 WMI 服务,从 Win32_PerfRawData_PerfDisk_LogicalDisk 类获取数据,获取 PercentFreeSpace 计数器的值,将其乘以 100,然后将所得的结果除以基础值 (11507)。

intBaseValue = 11507
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colDisks = objWMIService.ExecQuery _
    ("Select * from Win32_PerfRawData_PerfDisk_LogicalDisk Where Name = 'C:'")
For Each objDisk in colDisks
    dblActualFreeSpace = (100 * objDisk.PercentFreeSpace) / intBaseValue
    Wscript.Echo Int(dblActualFreeSpace)
Next

听起来好像有许多工作要做,但实际上没有那么复杂。我们假设 PercentFreeSpace 返回的值为 4145。在这种情况下,计算将如下所示:

(100 * 4145) / 11507
或者:
414500 / 11507 = 36
实际上,除以下一点外,这是非常简单的:如何知道 PercentFreeSpace 的基础值为 11507?我查阅了一下。只要您具有一个 PERF_RAW_FRACTION 性能计数器,就会还具有一个伴随的基础值性能计数器。要找到该计数器,只需打开 Wbemtest 进行查看即可:

 

图 2:在 Wbemtest 中找到基础值性能计数器

那么,我们接着如何获得 PercentFreeSpace_Base 的值呢?在 Wbemtest 中单击 Instances(实例)按钮,然后双击所得到的任何实例(单击任何一个都可以)。PercentFreeSpace_Base 的值就是您将在脚本中使用的基础值。

 

图 3:确定基础值性能计数器的值

这里只有一件事情需要注意:PercentFreeSpace_Base 的值会随着您的硬件、操作系统、时间以及其他内容的不同而不同。(实际上,它取决于您的逻辑磁盘的大小。)因此,您不能将其硬编码为某个数值(例如 11507,这只适用于特定大小的磁盘),而应当使用 PercentFreeSpace_Base 的检索值。请注意以下脚本中的粗体行。

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colDisks = objWMIService.ExecQuery _
    ("Select * from Win32_PerfRawData_PerfDisk_LogicalDisk Where Name = 'C:'")
For Each objDisk in colDisks
    intBaseValue = objDisk.PercentFreeSpace_Base
    dblActualFreeSpace = (100 * objDisk.PercentFreeSpace) / intBaseValue
    Wscript.Echo Int(dblActualFreeSpace)
Next

提示:如何知道脚本是否返回了正确的数据?这里有一种快捷方法可以仔细检查您的工作。启动性能监视器并加载您的脚本中正在运行的性能计数器。同时运行该脚本,观察并比较您的值与性能监视器的值。这将有助于您知道返回的数据是否正确。
PERF_COUNTER_COUNTER
仍然在看吗?好的。接下来,这个类型看上去可能有点难对付,但是不必惊慌。做一次深呼吸并保持冷静,您会发现根本不像您开始想象的那样糟。

PERF_COUNTER_COUNTER(与 Boutros Boutros-Ghali [英文] 没有关系)用于测量每秒发生的事件数(“事件”的定义视不同的计数器而定)。让我们暂时忘记性能监视(你们中的某些人可能在几页之前就已经忘记了),考虑一下将如何测量每秒发生的事情数。这实际非常简单。

计算出发生了多少事件。
计算出用了多长时间。
将所用的时间转换为秒。例如,如果所用的时间为 2 分钟,则需要乘以 60,将 2 分钟转换为 120 秒。
PERF_COUNTER_COUNTER 的基本思想与此相同:

获取两个测量值(CounterValue1 和 CounterValue2),然后计算出在这两个测量值之间发生了多少事情(CounterValue2 减去 CounterValue1)。
通过以下方式计算所用的时间:获取开始测量时的时间 (TimeValue1),然后从结束测量时的时间中减去该时间(TimeValue2 减去 Time

[wangjy17908]
添加时间:2010-03-11
版权所有(C)2005-2015