Tags

, , ,

Some time back I was managing a VMware vSphere environment that was spread across several regions with more than two thousand virtual machines. In a very dynamic virtualised environment such as this one the virtual machines are sized frequently due to varying needs. After some time the VMs are sometimes allocated to different projects or decommissioned or sometimes the sizing was for a short term. These VMs that now have reduced workloads are over provisioned and one of the main resource that is over provisioned this way is the number of vCPUs. To maintain an efficient infrastructure it is a good practice to right size virtual machines from time to time.

The requirement I had this time was to right size virtual machines’ vCPU configurations. In this situation I focused on collecting the vCPU usage statistics of the virtual machines that had more than 2 virtual processors for a period of 30 days. One of the challenges in here was to limit the statistics to the working hours only so that I have a more accurate report on the work hour workload on them. Although I have laid the criteria to generate the report easily there was no option in VMware vSphere management console to collect this kind of a customized data set. Time to get into PowerCLI – yay! 🙂

Finally I ended up in writing the following script. It allows the user to specify the number of days from the day of the script is run to search for statistics during the runtime and generate a user friendly HTML report using the statistics. The names of the virtual centers should be specified in the variable $VCs and if the minimum number of vCPUs that you need to check is different than 2 then the value can be changed in the integer variable $numCpu.

Please download the script from here as WordPress may have messed up the PowerShell syntax below.

# =============================================================================
# name		: get_vcpu_usage_stats.ps1
# author	:NimanthaWickremasinghe
# version	: 1.0
# purpose	: Retrieves the CPU usage statistics ofVMs for a given period.	
# requirements	:PowerCLI
# =============================================================================asnp VMware.VimAutomation.Core-ErrorActionSilentlyContinue

[double]$statHistory = Read-Host "Please enter the duration you want to check for statistics in number of days"-ErrorActionSilentlyContinue

if($?-and ($statHistory-gt 0)){
	$date = Get-Date
	$today = Get-Date-Formatd-M-yyy
	$rptPath = "C:\vm_cpu_stat_all_vms_$today.html"
	$VCs = "vCenter1","vCenter2","vCenter3"
	$sTimeWH = "08:00:00"
	$fTimeWH = "17:00:00"
	$sTimeNWH = "17:00:01"
	$sTimeNWH = "07:00:00"
	[int]$numCpu = 3
	
	$rptHeader+= ''
	$rptHeader+= ''
	$rptHeader+= ''
	$rptHeader+= ''
	$rptHeader+= "<h2><span style="color: purple; font-family: Helvetica;">Virtual Machine vCPU Usage </span></h2>"
	$rptHeader+= "<h3><span style="font-family: Helvetica;">Period: " + (Get-Date).AddDays(-$statHistory).tostring().split(",")[0] +" - "+ (Get-Date).tostring().split(",")[0] + "</span></h3>"

	foreach ($vc in $VCs){
		Connect-VIServer $vc

		$vms = Get-VM | ?{$_.NumCpu -ge $numCpu}
		
		$rptBody += "<span style="color: green; font-family: Helvetica;"><b><span style="text-decoration: underline;">" + $vc.toUpper() + "</span>: "+ $vms.count +" VMs</b></span>

"
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''

		foreach ($vm in $vms){
			$chkDate = -$statHistory
			
			$cpuStat = $vm | Get-Stat -ErrorAction SilentlyContinue -Start $date.AddDays(-$statHistory) -Finish $date -Stat cpu.usage.average | Measure-Object -Property Value -Average -Maximum -Minimum
			
			do{
				$sTimeStampWH = $date.AddDays($chkDate).tostring().split('')[0] +' '+ $sTimeWH
				$fTimeStampWH = $date.AddDays($chkDate).tostring().split('')[0] +' '+ $fTimeWH
				$sTimeStampNWH = $date.AddDays($chkDate).tostring().split('')[0] +' '+ $sTimeNWH
				$fTimeStampNWH = $date.AddDays($chkDate+1).tostring().split('')[0] +' '+ $sTimeNWH
				
				$cpuStatWH += ($vm | Get-Stat -ErrorAction SilentlyContinue -Start $sTimeStampWH -Finish $fTimeStampWH -Stat cpu.usage.average | Measure-Object -Property Value -Average).Average
				$cpuStatNWH += ($vm | Get-Stat -ErrorAction SilentlyContinue -Start $sTimeStampNWH -Finish $fTimeStampNWH -Stat cpu.usage.average | Measure-Object -Property Value -Average).Average
				
				$chkDate++
			} while ($chkDate -le 0)
			
			$os = ($vm | Get-View).Summary.Guest.GuestFullName
			if(!$os){
				$os = "<i>"+ ($vm | Get-View).Summary.Guest.ToolsStatus + "</i>"
			}
			
			if($cpuStat){
				if($cpuStat.Average -ge 1){$avgCpu = [Math]::Round($cpuStat.Average)}else{$avgCpu = "{0:N1}" -f $cpuStat.Average}
				if($cpuStat.Maximum -ge 1){$maxCpu = [Math]::Round($cpuStat.Maximum)}else{$maxCpu = "{0:N1}" -f $cpuStat.Maximum}
				if($cpuStat.Minimum -ge 1){$minCpu = [Math]::Round($cpuStat.Minimum)}else{$minCpu = "{0:N1}" -f $cpuStat.Minimum}
			}
			else{
				$avgCpu = "<i>No data</i>"
				$maxCpu = "<i>No data</i>"
				$minCpu = "<i>No data</i>"
			}
			
			if($cpuStatWH){
				$avgCpuWH = $cpuStatWH/$statHistory
			
				if($avgCpuWH -ge 1){
					$avgCpuWH = [Math]::Round($avgCpuWH)
				}
				else{
					$avgCpuWH = "{0:N1}" -f $avgCpuWH
				}
			}
			else{
				$avgCpuWH = "<i>No data</i>"
			}
			
			if($cpuStatNWH){
				$avgCpuNWH = $cpuStatNWH/$statHistory
			
				if($avgCpuNWH -ge 1){
					$avgCpuNWH = [Math]::Round($avgCpuNWH)
				}
				else{
					$avgCpuNWH = "{0:N1}" -f $avgCpuNWH
				}
			}
			else{
				$avgCpuNWH = "<i>No data</i>"
			}
			
			$rptBodyTop += ''
			
			Clear-VariablecpuStatWH,cpuStatNWH,cpuStat,maxCpu,minCpu,avgCpu,avgCpuWH,avgCpuNWH,chkDate
		}
		$rptBody += $rptBodyTop
		$rptBody += "<table border="1">
<tbody>
<tr>
<th><span style="color: midnightblue; font-family: Times;">Name</span></th>
<th><span style="color: midnightblue; font-family: Times;">PowerState</span></th>
<th><span style="color: midnightblue; font-family: Times;">NumCPU</span></th>
<th><span style="color: midnightblue; font-family: Times;">AvgCPU (%)</span></th>
<th><span style="color: midnightblue; font-family: Times;">MaxCPU (%)</span></th>
<th><span style="color: midnightblue; font-family: Times;">MinCPU (%)</span></th>
<th><span style="color: midnightblue; font-family: Times;">AvgCPU WH(08:00-17:00) (%)</span></th>
<th><span style="color: midnightblue; font-family: Times;">AvgCPU NWH(17:00-07:00)(%)</span></th>
<th><span style="color: midnightblue; font-family: Times;">OS</span></th>
</tr>
<tr>
<td><span style="font-family: Tahoma;">"+ $vm.Name.toLower() +"</span></td>
<td><span style="font-family: Tahoma;">"+ $vm.PowerState +"</span></td>
<td><span style="font-family: Tahoma;">"+ $vm.NumCpu +"</span></td>
<td><span style="font-family: Tahoma;">"+ $avgCpu +"</span></td>
<td><span style="font-family: Tahoma;">"+ $maxCpu +"</span></td>
<td><span style="font-family: Tahoma;">"+ $minCpu +"</span></td>
<td><span style="font-family: Tahoma;">"+ $avgCpuWH +"</span></td>
<td><span style="font-family: Tahoma;">"+ $avgCpuNWH +"</span></td>
<td><span style="font-family: Tahoma;">"+ $os +"</span></td>
</tr>
</tbody>
</table>"
		
		Disconnect-VIServer $vc -Confirm:$false
		Clear-Variable rptBodyTop
	}
	$rptBody += "<i> * No data </i> - Statistics are unavailable for the given period."
	$rptFooter += ''

	$rpt = $rptHeader + $rptBody + $rptFooter
	$rpt | Out-File $rptPath

	$execTime = ((Get-Date)-$date)
	Write-Host `n"Report generated in (h:m:s): $execTime" -ForegroundColor Yellow
	Write-Host Please find the report at $rptPath -ForegroundColor Green
} else{
	Write-Host `n"Invalid data. You have entered either a non-numeric value or value less than 1." -ForegroundColor Red
	Write-Host `n"Press any key to continue ...`n" -ForegroundColor Yellow
	$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

	exit
}

A HTML report similar to the following one will be generated at the end of the execution of the script.

vCPU Usage Statistics Report

vCPU Usage Statistics Report

Advertisements