实用工具特别推荐 使用多线程处理速度更快的 CPU 使用率报告

James Turner

内容

脚本执行准备
创建服务器列表
多线程处理
向上覆盖它

此版本的实用工具特别推荐略有不同,通常、 的它涉及到脚本而不是可执行文件,因此,column 是 dual-purpose: 不仅它确实很好可以生成有关组织中的 CPU 使用率的报告,它还充当一个 mini-tutorial 解释该脚本的工作方式以及如何使用它。

如果您是系统管理员,您可能熟悉尝试通过查询 LoadPercentage 属性 Win32_Processor 类的远程确定服务器的 CPU 使用率。 我最近编写了这样一个脚本来读取内容检索所有域服务器的列表。 该脚本满足一个立即需要扫描和时我们在我们的域遇到总的服务器运行缓慢期间报告的所有我的服务器。

时,在一个时这意味着需要获得完全在服务器的整个列表相当一段计算,只有一个服务器中脚本的明显限制未能但是。

为加快过程,我决定使用多线程处理,这导致明显的改进,而不是采用接近于 15 分钟以完成对超过 100 台服务器的扫描,在只需几分钟内完成新的过程。

该改进但是是成本的位的一个。 而不是使用一个简单的 Visual Basic 脚本,新过程需要两个脚本、 记录到单独的文本文件的各个服务器 CPU 结果、 一个数据文件夹用于保存这些的文件例程监视连续运行线程数,另一个例程监视所有线程和到 Microsoft Office Excel 电子表格中读取文本文件的所有进程的完成。

是否它需要额外的工作? 我认为,因为新过程有助于提高反应和可能在您的服务器上可能存在的关键问题的响应时间。

脚本执行准备

您可以下载在脚本在 代码下载我们的网站的部分. 图 1 中列出此进程,CPUloadpercentageMultiThread.vbs 的主脚本。 它运行一个脚本调用 CPUThread.vbs,单独脚本 (请参见 图 2 ),计算您的域中的每台服务器 CPULoadPercentage。

图 1 CPUloadpercentageMultiThread.vbs

'***********************************************************************************************
'*** CPUloadpercentageMultiThread.vbs - Jim Turner
'*** Report on CPU Load Percentage for Domain Servers
'***  Adjust variables at Call Out A
'*** Uses MultiThreaded approach to speed up process by Calling CPUThread.vbs
'*** Requires Excel
'***********************************************************************************************
On Error Resume Next
Const ADS_SCOPE_SUBTREE = 2
CONST ForReading = 1
CONST xlAscending = 1 : CONST xlDescending = 2 
Const xlHeader = 1 : Const xlNoHeader = 2

'Call Out A
'*** Set these variables
TotalActiveThreads=20
NumberOfSamples = 5
ThreadVBSPath = "C:\scripts\CPUrefresher\"
DataPath = "C:\scripts\CPUrefresher\DATA\"
'*** Set these Variables
'End Call Out A

strMessage = "Excel Spreadsheet will open when process completes"
strScriptName = "CPU LoadPercentage Script"
CreateObject("WScript.Shell").Popup strMessage,5,strScriptName,vbInformation

'*** Call Out B
DNC = GetObject("LDAP://RootDSE").Get("defaultNamingContext")
QueryStr = "SELECT cn FROM 'LDAP://" & DNC & "' where objectcategory='computer' and operatingSystem='*server*'"
'End Call Out B

Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection
objCommand.Properties("Page Size") = 1000
objCommand.Properties("Sort On") = "CN"
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
objCommand.CommandText = QueryStr
Set objRecordSet = objCommand.Execute
objRecordSet.MoveFirst

'Call Out C
Set oShell = CreateObject("WSCript.shell")
Do Until objRecordset.EOF
 Err.Clear
 strComputer = ""
 strComputer = objRecordSet.Fields("CN").Value
 cmd = ThreadVBSPath & "CPUThread.vbs " & strComputer & " " & NumberOfSamples & " " & DataPath

 ThreadAvailable = False
 Do Until ThreadAvailable
  Set Processes = GetObject("winmgmts:\\.\root\cimv2").ExecQuery("Select * from Win32_Process where name ='wscript.exe'")
  If Processes.count < (TotalActiveThreads + 1) Then
   ThreadAvailable = True
   oShell.Run cmd,1,False
  End If
  Set Processes = Nothing
 Loop
 objRecordset.MoveNext
Loop
'End Call Out C

'Call Out D
MarkTime = Now()
ThreadAvailable = False
Do Until ThreadAvailable
 Set Processes = GetObject("winmgmts:\\.\root\cimv2").ExecQuery("Select * from Win32_Process where name ='wscript.exe'")

'*** Wait until all wscript processes are done or 120 seconds, whichever occurs first
'*** possible that other unrelated scripts may be running
 If Processes.Count < 2 Or DateDiff("s",MarkTime,Now()) > 120 Then
  ThreadAvailable = True
 Else
  Set Processes = Nothing
 End If
Loop
'End Call Out D

Set XL = CreateObject("Excel.Application")
XL.WorkBooks.Add
XL.Cells(1,1).Value = "Server"
XL.Cells(1,2).Value = "Processor"
XL.Cells(1,3).Value = "Load Average"

Row = 2

strPath = "C:\scripts\CPUrefresher\DATA\"
Set FSO = CreateObject("Scripting.FileSystemObject")
Set objFolder = FSO.GetFolder(strPath)
For Each objFile In objFolder.Files
 aray = ""
 strFile = strPath & objFile.Name
 Set fsoRead = fso.OpenTextFile(strFile,ForReading,False)
 CPUinfo = fsoRead.ReadAll
 fsoRead.Close
 aray = Split(CPUinfo,VBCRLF)

'Call Out E
 Set UniqCPU = CreateObject("Scripting.Dictionary")
 Set CPUAverage = CreateObject("Scripting.Dictionary")
'End Call Out E

'Call Out F
 For Each arItem In aray

  UniqCPU.RemoveAll
  CPUAverage.RemoveAll

  If Trim(arItem) <> "" Then
   ArFields = Split(arItem," ")
   If Instr(ArFields(1),"CPU") <> 0 Then
    UniqCPU.Add ArFields(1),0
   End If
  End If
 Next
'End Call Out F

'Call Out G
 For Each arItem In aray
  If Trim(arItem) <> "" Then
   ArFields = Split(arItem," ")
   If IsNumeric(ArFields(2)) Then

    UniqCPU.Item(ArFields(1)) = clng(UniqCPU.Item(ArFields(1))) + Clng(ArFields(2))

   End If
  End If
 Next
'End Call Out G

'Call Out H
 For Each arItem In aray
  If Trim(arItem) <> "" Then

   If Not CPUAverage.Exists(ArFields(1)) Then
    CPUAverage.Add ArFields(1),ArFields(1)
    ArFields = Split(arItem," ")
    XL.Cells(Row,1).Value = ArFields(0)
    XL.Cells(Row,2).Value = ArFields(1)

    XL.Cells(Row,3).Value = Round(UniqCPU.Item(ArFields(1)) / NumberOfSamples)

    Row = Row + 1
   End If
  End If
 Next
'End Call Out H
Next

XL.Cells.Select
XL.Selection.Sort XL.Range("c1"),xlDescending,XL.Range("A1"),,xlAscending,XL.Range("B1"),xlAscending,XLHeader,1,False
XL.Cells.EntireColumn.AutoFit
XL.Rows("2:2").Select
XL.ActiveWindow.FreezePanes = True
XL.Range("A1").Select
XL.Visible = TRUE

图 2 CPUThread.vbs

On Error Resume Next
If WScript.Arguments.Count < 1 Then
 Wscript.Quit
Else
 strComputer = WScript.Arguments.Item(0)
 NumberOfSamples = Clng(WScript.Arguments.Item(1))
 Datapath = WScript.Arguments.Item(2)
End If

Set fso = CreateObject("Scripting.FileSystemObject")

txtfile= Datapath & strComputer & ".txt"
Set TextFile = fso.CreateTextFile(txtfile,True)

Set objWMIService = GetObject("winmgmts:" _
 & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
If Err.Number <> 0 Then
 TextFile.WriteLine strComputer & " " & "ConnectionProblem"
 Set objWMIService = nothing
 Err.Clear
Else
 Set OSItems = objWMIService.ExecQuery("Select * from Win32_OperatingSystem",,48)
 For Each OpSys in OSItems
  OS = OpSys.Caption
 Next
 Set OSItems = nothing
 If instr(OS,"Windows 2000") <> 0 Then
  For i = 1 to NumberOfSamples
   Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
   Set colItems = objWMIService.ExecQuery("Select * from Win32_Processor",,48)
   For Each objItem in colItems
    TextFile.WriteLine strComputer & " " & objItem.DeviceID & " " & objItem.LoadPercentage
   Next
  Next
 Else
  Set objWMIService = nothing
  Set RefreshingObject = CreateObject("WbemScripting.SWbemRefresher")
  Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
  If Err.Number <> 0 Then
   TextFile.WriteLine strComputer & " " &  "ConnectionProblem"
   Err.Clear
  Else
   Set ItemObjects = RefreshingObject.AddEnum(objWMIService,"Win32_Processor")
   RefreshingObject.Refresh
   For i = 1 to NumberOfSamples
    RefreshingObject.Refresh 
    For Each Sampling in ItemObjects.ObjectSet
     TextFile.WriteLine strComputer & " " & Sampling.DeviceID & " " & Sampling.LoadPercentage
    Next
   Next
  End If
 End If
End If
TextFile.Close

您可以运行主要的脚本之前,个需要调整的几个变量。 这些可以找到在主脚本从 A 调用:

TotalActiveThreads=20
NumberOfSamples = 3
ThreadVBSPath = "C:\scripts\  CPUrefresher\"
DataPath = "C:\scripts\CPUrefresher\DATA\"

TotalActiveThread 代表单独的脚本将允许在计算机上运行一次的数量。 您可以更改 TotalActiveThreads 与所需的任何数字。 请记住,但是,但设置此值更高意味着您将看到结果所有服务器更快,还将能使用更多的内存并在本地计算机上增加 CPU 处理负载。 我建议保持 15 和 30 之间的此变量值。

NumberOfSamples 表示您希望获得读取给定服务器上每个处理器的 CPU 负载百分比的时间数。 只需更改此变量的值,为您希望获得每个处理器的 CPU 读取内容的数量。 三到五个应提供准确的总体平均值。

ThreadVBSPath,如其名称的建议需要指向 CPUThread.vbs 脚本的文件夹位置。 还需要指向存储所生成 CPU 负载文本文件位置 DataPath 变量值。 应确保该文件夹为空或只包含文本文件由该脚本。 该脚本会尝试打开并读取此文件夹中的所有文件。

请注意您必须手动创建文件夹之前运行该脚本指定 ThreadVBSPath 和 DataPath 变量中。

创建服务器列表

而不是试图通过特定的 Active Directory OU hardcoding 获取服务器的集合,我选择了稍有不同的方法这样我就不必更改该代码应被更改 OU,重命名,或移动,或有应包含服务器对象的多个 OU。

在中主要的脚本出 B 调用,您会看到我使用非常通用查询生成我的收藏的 Active Directory 中的服务器:

QueryStr = "SELECT cn FROM 'LDAP://" & DNC & "' where
 objectcategory='computer' and operatingSystem='*server*'"

正如您所看到此查询的关键是只需查看的对象的操作系统包含单词"服务器"。 这有助于使脚本更通用,因此创建脚本和数据文件夹仅强制您最初需要进行的更改并将其初始化这些两个文件夹的变量。

多线程处理

设置我的 ADO Connection 对象后, 脚本循环的服务器在从 C 的调用返回的集合 此循环中, 我设置我将使用它来执行该命令语句在多线程处理,使用以下语句:

cmd = ThreadVBSPath & "CPUThread.vbs " & strComputer & " " &
 NumberOfSamples & " " & DataPath

此处是连续命令可能实际外观的示例:

C:\Scripts\CPUrefresher\CPUThread.vbs
  ServerOne 5
 C:\Scripts\CPUrefresher\Data\

您可以看到 CPUThread 脚本将执行带参数代表服务器,ServerOne,执行 5 和引用要在生成的文本文件应写入,C:\Scripts\CPUrefresher\Data\ 的样本数的名称。 包含该的 servername 变量 strComputer 是该命令字符串,实际更改为在迭代中 Server 集合循环的每个项目的唯一一条。

在设置 cmd 变量之后下面的代码段控制多线程处理部分脚本:

ThreadAvailable = False
 Do Until ThreadAvailable
  Set Processes = GetObject("winmgmts:\\.\root\cimv2").ExecQuery("Select * from Win32_Process where name ='wscript.exe'")
  If Processes.count < (TotalActiveThreads + 1) Then
   ThreadAvailable = True
   oShell.Run cmd,1,False
  End If
  Set Processes = Nothing
 Loop

我首先创建一个叫做 ThreadAvailable 的布尔标志变量,并将它设置为 False。 此布尔值中使用一个 Do Until 循环限制可以同时运行的 Wscript.exe 进程的数。 正如您很快就会看到,此限制与直接相关,TotalActiveThreads 变量的值且,只要活动 Wscript.exe 线程的总数是设置值下,Boolean 类型的值,ThreadAvailable 的设置为 True。

接下来的两个代码行确定线程是否可用。

Set Processes = GetObject("winmgmts:\\.\root\cimv2").
      ExecQuery("Select * from Win32_Process where name ='wscript.exe'")
  If Processes.count < (TotalActiveThreads + 1) Then

WMI 查询返回名为 Wscript.exe,进程的集合,并 process.count 指示多少这些进程正在运行。 如果该数字小于数设置为 TotalActiveThreads (外加一个到此主要脚本本身),然后在 ThreadAvailable 变量设置为 true 和多线程的脚本生成通过下面的语句:

oShell.Run cmd,1,False

此语句实质上正在运行 CPUThread 脚本 (具有三个参数中)。 为您重新调用 cmd 变量的工作分配查找如下:

cmd = ThreadVBSPath & "CPUThread.vbs " & strComputer & " " &
  NumberOfSamples & " " & DataPath 

数字 1 后面 cmd 指示生成的命令窗口将最小化状态运行。 False 参数表示不想等待完成,然后才移动到下一行代码命令。 它基本上神奇功能提供多线程处理功能的代码行。

当前正在运行的 Wscript.exe 进程数大于或等于 TotalActiveThreads (外加一个) 时,脚本保留内直到循环直到 Wscript.exe 进程数断开并成为小于 TotalActiveThreads (外加一个)。 显然,为在多线程脚本 wscript.exes 拉数 (CPUThread.vbs) 完成,并实质上打开另一个多线程的脚本将生成一个插槽。

然后,毕竟服务器已被枚举集合中项目的脚本进入等待状态从调用出 D。 此部分代码被为了等待所有生成的多线程脚本移动到最后一步收集数据,并创建报表之前完成设置。

等待状态代码是与循环只介绍,使用中,在于我只想等待直到 Wscript.exe 进程数少于两个或两分钟已通过该代码非常相似。 为添加的两分钟条件原因是可以有其他相关的脚本运行。 我知道我需要考虑这种可能性,因为我有多个本地计划的任务启动定期全天。 两分钟内应该是足够的时间允许剩余的生成的过程完成设置。 如果不是,只是增加数 120 更高的值。

当任一这些两个条件满足时脚本开始在其中打开由所有 CPUThread 脚本都创建的文件的下降,并将其读取到电子表格。 此部分的大部分是相当的基本内容打开文件并读取其但没有的复杂性跟踪的多个 CPU 负载合计和平均值,我想在按。

在从 E 的调用,我设置两个 Dictionary 对象 UniqCPU 和 CPUAverage。 UniqCPU 将跟踪的每个单个 CPU 和组合的采样总 CPU Loadpercentages 的 CPU 的使用。 此字典元素的关键部分 CPU 设备 ID (CPU0、 CPU1…) 并且词典元素的项目部分是在 cpuloadpercentage 样本的组合的合计。

在其他词典对象,CPUAverage,用于作为排序的一个标志。 我使用它来确定是否我已经已报告特定的 CPU 上。 我将说明的详细稍后。

要理解如何在使用放置 Dictionary 对象,您需要有的哪些数据由过程创建的文本文件之一类似的想法。 下面是一个示例:

SERVER3 CPU0 56
SERVER3 CPU1 94
SERVER3 CPU0 88
SERVER3 CPU1 57
SERVER3 CPU0 43
SERVER3 CPU1 54

此处,Server 3 是只是服务器的名称。 CPU0 是设备 ID 的第一个处理器,该服务器上。 56 代表第一个 CPULoadPercentage 示例所执行的返回值。 下一行代表第二个 CPU (设备 ID CPU1),并且该处理器第一个 CPULoadPercentage 示例 (94)。 第三行等等代表 CPU0 和其 CPULoadpercentage 的第二个的采样。 因此本示例,可以判断有两个处理器和为每个的三个 CPULoadpercentage 示例。 顺便说一下,每一行随后转换为数组使用容易引用 CPU 和 CPULoadpercentage 拆分函数。

要设置我的 UniqCPU 词典,以便只有一个项包含每个 CPU,我运行在退出创建此项,并开始将项值设置为零的 F 的调用代码。

如果上面的示例数据实际数据,我 UniqCPU 词典将具有两个的项,名为每个 CPU 的一个项,项目元素,每个将组合的 CPU loadpercentages 总共。 因此例如 UniqCPU 项 CPU0 将有 187 的项值。 在 loadpercentage 向每个项的过程可以查看在调用出 G。

此时,在代码,我准备写入到 Excel 的结果。 可以看到在调用出 H,我再次循环数据,并使用我的第二个词典 CPUAverage 计算平均值为每个 CPU。

fig03.gif

图 3 The Excel 报表显示 CPU 负载平均 (单击图像可查看大视图)

后正在读取的所有文本的文件数据到电子表格我只需执行一些 Excel 管理并排序该电子表格,以便首先列出"hottest CPU。

如您会想起此进程,其他片段将涉及 CPUThread 脚本。 很在此脚本中实际的 CPU 负载百分比被收集。 在 CPULoadPercentage 是一秒时间段内的特定的 CPU 负载的平均的百分比。

如前所述,主要的脚本会将三个参数传递给 CPUThread 脚本、 服务器名称、 样本,数和数据文件的位置。 以下三个参数告诉 CPUThread 脚本来执行 WMI 处理器的服务器查询,多少次刷新查询以适应样本数我想要以及最后,写入结果的位置。

仅 twist 是此脚本使用的处理器集信息刷新通过利用了 WbemScripting.SWbemRefresher 类方法,并从而无需 re-instantiate WMI Win32_Processor 类需要获取一个新的 CPU 每次加载阅读。

请注意我已添加一些简单的逻辑,这个脚本将为 Windows 2000 的住宿的不兼容与刷新类。 如果该操作系统是 Windows 2000,WIN32_Processor 类重复用于创建 NumberOfSamples 变量中指定的多个时间的 Processor 对象的新实例。

向上覆盖它

当所有已完成可以看到在 图 3 Excel 报表就会列出一的行中的每个 CPU 的平均值。 这样进行排序以 CPU 平均列,并快速对所有"热"的 CPU 负载进行查看。

James Turner 是计算机科学 Corporation 和脚本编写应用程序和工具的开发人员的 Active Directory 的域管理员。 他已开发 PC 和基于服务器的工具和应用程序几乎 10 年中使用 VBScript 和所他有还创作许多 HTA 应用程序、 VBScripts 和脚本的文章 Windows IT Pro 杂志的。 他专门研究 Active Directory 脚本和使用 Excel 作为其脚本的报告。