Link

PowerCLI 性能优化

目录

  1. 参考资料
  2. 概要
  3. 异步执行命令
  4. 优化代码
    1. 查看瓶颈在哪里
    2. PowerCLI 代码性能优化
    3. 赋值远比执行一条命令快捷
  5. 多线程
    1. 多线程使用前提条件
    2. PSJobs :Powershell 的多线程任务
    3. get-job 获取 job 状态
      1. start-job 开始任务
      2. receive-job 获取任务输出
      3. 参数传递
      4. 任务执行完毕后的操作
      5. 输出优化
  6. 应用于 PowerCLI

参考资料

此文章大部分内容取材于 https://adamtheautomator.com/powershell-multithreading/

概要

在用 PowerCLI 处理批量任务时会非常慢(例如批量创建虚拟机,批量获取主机信息),主要原因在于 Poershell 是顺序执行脚本的,因此可以通过代码改造让命令可以并行(或者后台)执行将能极大加快运行速度。

异步执行命令

PowerCLI 的某些操作可以使用异步执行,即 PowerCLI 脚本只进行任务的下发,执行交给 vCenter 去执行。

这样的操作有:

虚拟机批量开关机、修改虚拟机配置等等。

示例:

Set-VM -vm $vmGuest -MemoryMB "81920"  -Confirm:$false -RunAsync
#调整虚拟机的内存大小,异步执行

对于某些操作,例如批量收集主机的信息,显然难以用异步,因为需要收集且处理信息,此时可以优化代码,以及将任务拆分,多线程执行。

优化代码

查看瓶颈在哪里

有时候性能差是程序逻辑设计的问题,或者循环的问题,需要尽早完成这部分优化。

可以通过 Measure-Command{ 命令内容 }来查看命令的运行时间。

一个通用的优化方式如下:假如你要先收集大量的数据,应当尽早对数据进行过滤,以缩减这部分数据的大小,再进行其他二次过滤。或者按照原作者的话,将过滤往代码的左边移动。

例如:

想要通过 get-process 获取 Dock 这个进程的 CPU 信息,可以通过以下两组代码实现:

Get-Process -Name 'Dock' | Where-Object {$_.CPU -ne $null} | Select-Object CPU
Get-Process | Where-Object {$_.ProcessName -eq 'Dock'} | Select-Object CPU | Where-Object {$_.CPU -ne $null}

性能对比如下:

image-20200108234328565

可以看到前者运行速度比后者要快将近一倍,如果是执行大量的任务,这一点点差距累计起来的差距是很大的。

PowerCLI 代码性能优化

笔者对 PowerCLI 的脚本进行了类似测试发现随着写法不同,代码执行速度差异很大,如下例:获取主机所在集群信息:

Get-VMHost $vmHost | Get-cluster
Get-Cluster -vmhost $vmHost

image-20200108155257164

又如对 ESXi 本地盘数量的统计:

虽然 esxcli 看似步骤更多,代码更多,但使用 esxcli 的效率明显高于 get-scsilun 的效率

$ESXCLI = Get-EsxCli -VMHost $VMHost 
$luncount=$ESXCLI.storage.core.device.list() | select DeviceType | where DeviceType -eq "Direct-Access"
write-host "Count is" $luncount.Count -ForegroundColor Green
$luncount=get-vmhost $VMhost| Get-ScsiLun -LunType disk 
write-host "Count is" $luncount.Count -ForegroundColor Green

image-20200108153834661

又如获取主机 BIOS 信息,加了过滤和不加过滤也有差异:

 $biosHost = Get-VMHost $vmHost | Get-View -Property hardware
 Write-Host $biosHost.Hardware.BiosInfo.biosversion
 $biosHost = Get-VMHost $vmHost | Get-View
Write-Host $biosHost.Hardware.BiosInfo.biosversion

image-20200108170952755

遇到大量代码时,需要反复测试不同写法带来的速度差异,选择最优的。

赋值远比执行一条命令快捷

另外测试发现,所有赋值的操作速度都低于 1ms,所以这类操作并不影响性能。具体到代码优化上,应该尽可能减少与 vCenter 的交互。例如获取主机所在集群:

Get-VMHost $vmHost | Get-cluster
#或者
Get-Cluster -vmhost $vmHost
$info.cluster=  $vmhost.Parent.name

直接的赋值远比执行一条命令快。三条命令执行速度分别如下:

image-20200108172754830

通过上述的方法逐行对一个硬件信息统计脚本进行了优化。优化前需要 15 分钟,稍微优化一下就降到了 8 分钟。

image-20200108171324284

image-20200108172556655

多线程

多线程使用前提条件

如果要使用 powershell 的多线程,前提条件有:

  • Powershell 3.0 以上版本
  • 宿主机的内存和CPU足够多
  • 确保程序可以多线程运行

注意:在进行多线程改造前,需要确保多线程运行时,多个线程间不会冲突。例如多个进程同时写入文件便会冲突,但是同时读取一个文件就没问题。

PSJobs :Powershell 的多线程任务

实现 powershell 多线程的最快方法是用 PSJobs,此 cmdlet 内置于 Microsoft.PowerShell.Core 中,可以使用下列命令查看此 cmdlet 下的命令:

Get-Command *-Job

image-20200108233915873

PSJobs 共有8种状态,最常用的如下:

  • Completed :Job 已经完成,可以获取 job 输出的信息,或者 job 可以被安全移除。
  • Running:Job 正在运行,无法停止(除非强制停止),无法获取输出信息。
  • Blocked:Job 正在运行,但是系统提示要求输入信息才能继续。
  • Failed:执行 Job 时出错。

get-job 获取 job 状态

通过 get-job 命令可以查看 Job 的运行情况。

下列命令用于创建一个任务并查看任务的状态:

PS51> Start-Job -Scriptblock {Start-Sleep 5}
PS51> Get-Job

image-20200108174049536

image-20200108174101294

上例中,如果任务还在运行,则 State 显示 Running,如果运行完毕,State 为 Completed。

另外在运行完后 HasMoreData 值为 False,表示此 Job 没有输出。

下图是其他状态的截图,使用的命令见 Command 列。

image-20200108174450985

start-job 开始任务

使用 Start-Job 可以创建一个新的任务,如果使用 measure-command 查看任务执行时间,可以看到远远小于命令真实的执行时间。

image-20200108174651101

receive-job 获取任务输出

$Job = Start-Job -ScriptBlock {Write-Output 'Hello World'}
Receive-Job $Job

image-20200108175112897

参数传递

默认 start-job 生产的任务无法使用全局定义的变量,需要 ArgumentList 进行变量的传递。

Start-Job -Scriptblock {param ($Text) Write-Output $Text} -ArgumentList "Hello world!"

任务执行完毕后的操作

在做一些批量收集的时候,我们通过 start-job 来创建了大量的 Job,但这只是下发了任务,我们还需要在任务执行完毕后读取并处理数据,在此我使用 for 循环定期检查 Job 是不是已经完成,只有等待完成后才批量读取数据,具体操作如下:

 $tasklist=@(
 "task1"
 "task2"
 "task3"
 )
 
 $jobs=@()
  Foreach ($task in $tasklist) {
	write-host "Starting Job..."
  $jobs += start-job -ScriptBlock {
  param ($taskname)
  	sleep 5
 		Write-Output "Task Finished : "$taskname
		} -ArgumentList $task
}

while($jobs.state -contains "Running")
  {write-host "still running!" -ForegroundColor Yellow
  sleep 1
  }  
 
write-host "Finished!" -ForegroundColor Green

receive-job $jobs
  

输出优化

上面提到可以在 job 内使用 Write-Output 输出信息 ,job 外使用receive-job 来接受信息,

在实际测试时发现 Write-Output 对于一个比较大的变量效率非常低(例如我尝试输出 get-vmhost 的结果,几十分钟没有反应),低到怀疑人生,因此最佳方法是在 Job 内完成该有的过滤。

例如:

Measure-Command{
  write-host "Starting Job..."
  $job = start-job -ScriptBlock {
$debuginfo=Connect-VIServer -Server "192.168.1.1" -User '[email protected]' -Password 'VMware1!' -WarningAction Ignore

  $vmHost =get-vmhost "esx06.vsphere.local"
Write-Output $vmHost
Disconnect-VIServer -Server "192.168.1.1" -Confirm:$false
}

while($job.state -contains "Running")
  {write-host "still running!" -ForegroundColor Yellow
  sleep 1
  }  
write-host "Finished!" -ForegroundColor Green
receive-job $job

}
Measure-Command{
  write-host "Starting Job..."
  $job = start-job -ScriptBlock {
$debuginfo=Connect-VIServer -Server "192.168.1.1" -User '[email protected]' -Password 'VMware1!' -WarningAction Ignore

  $vmHost =get-vmhost "esx06.vsphere.local"| select name,Version
Write-Output $vmHost
Disconnect-VIServer -Server "192.168.1.1" -Confirm:$false
}

while($job.state -contains "Running")
  {write-host "still running!" -ForegroundColor Yellow
  sleep 1
  }  
write-host "Finished!" -ForegroundColor Green
receive-job $job

}

后面的只是在输出时增加了 select name,Version ,执行时间只有 7 秒,而前面的程序几十分钟了还没反应。

image-20200108224033412

应用于 PowerCLI

在应用于 PowerCLI 时,发现每个 job 都必须单独连接 vCenter,好在这个操作无需交互,为了减少无用的输出,可以在命令前加上 $debuginfo=

$debuginfo=Set-PowerCLIConfiguration -Scope User -ParticipateInCEIP $false -Confirm:$false
$debuginfo=Connect-VIServer -Server "192.168.1.1" -User '[email protected]' -Password 'VMware1!' -WarningAction Ignore

一个最终优化的主机、BIOS、CPU、内存、硬盘信息收集脚本如下:

Measure-Command{
  
  $esxilocationlist=@(
Cluster-01
Cluster-02
Cluster-03
Cluster-04
Cluster-05
  )


  $jobs=@()
  Foreach ($esxilocation in $esxilocationlist) {
    write-host "starting job on "$esxilocation -ForegroundColor Green
   
    $jobs += start-job -ScriptBlock {
    param ($esxilocation2) 

   $debuginfo=Connect-VIServer -Server "192.168.1.1" -User '[email protected]' -Password 'VMware1!' -WarningAction Ignore
   
    $vmHosts =get-vmhost -location $esxilocation2
    
    $outputInfo = @()
    Foreach ($vmHost in $vmHosts) {
      $biosHost = Get-VMHost $vmHost | Get-View  -Property hardware
      $mgmtip=Get-VMHostNetworkAdapter -VMHost $VMHost -VMKernel | where {$_.ManagementTrafficEnabled -eq $True }
      $ESXCLI = Get-EsxCli -VMHost $VMHost -WarningAction Ignore
      $luncount=$ESXCLI.storage.core.device.list() | select DeviceType | where DeviceType -eq "Direct-Access"
      $sninfo=$ESXCLI.hardware.platform.get() 
      
      $Info = "" | Select Cluster, VMHost,MGMT_IP , Manufacturer, Model,BIOS,SN, ESXiVersion,Build, CPU,CPUnum,Mem,Diskcount
      $info.cluster=  $vmhost.Parent.name
      $Info.VMHost = $vmHost.Name
      $Info.MGMT_IP = $mgmtip.ip
      $Info.Manufacturer = $vmHost.Manufacturer
      $Info.Model = $vmHost.model
      $Info.BIOS=  $biosHost.Hardware.BiosInfo.BiosVersion
      $Info.SN=$sninfo.SerialNumber
      $Info.ESXiVersion = $vmHost.Version
      $Info.Build = $vmHost.Build
      $Info.Cpu = $vmHost.ProcessorType
      $Info.Cpunum = $vmHost.NumCpu
      $Info.Mem= $vmHost.MemoryTotalGB
      $Info.Diskcount=$luncount.Count
      $outputInfo += $Info
    }
    
    Disconnect-VIServer -Server "192.168.1.1" -Confirm:$false
    
 
    Write-Output  $outputInfo
 
  } -ArgumentList $esxilocation
 
 sleep 2
 #创建下一个进程前休息一下
 
 }
 
#检查所有任务是否均已完成
while($jobs.state -contains "Running")
  {write-host "still running!" -ForegroundColor Yellow
  sleep 1
  }

write-host "Finished!" -ForegroundColor Green

#收集所有job的输出
$allinfo=receive-job $jobs
$allinfo = $AllInfo | Select Cluster, VMHost,MGMT_IP , Manufacturer, Model,BIOS,SN, ESXiVersion,Build, CPU,CPUnum,Mem,Diskcount
     
$AllInfo 
$AllInfo |Export-Csv -NoTypeInformation BIOS-info.csv
  
# Export to HTML
$css  = "table{ Margin: 0px 0px 0px 4px; Border: 1px solid rgb(200, 200, 200); Font-Family: Tahoma; Font-Size: 8pt; Background-Color: rgb(252, 252, 252); }"
$css += "tr:hover td { Background-Color: #6495ED; Color: rgb(255, 255, 255);}"
$css += "tr:nth-child(even) { Background-Color: rgb(242, 242, 242); }"
Set-Content -Value $css -Path BIOS-info.css
$AllInfo | ConvertTo-Html -CSSUri "BIOS-info.css" | Set-Content "BIOS-info.html"
}


多线程跑起来了,电脑也跟着卡了,不过整体来说比较值得。

image-20200108222144976

最终使用多线程优化的执行速度不到 3 分钟,又远远超过之前的 8 分钟:

image-20200108222245413

运行结果如下:

image-20200108230055613