Private/Kinds/Cpu.ps1
|
# The Cpu Kind: what the processor is doing when nobody is asking it to do anything. # # A machine that is busy at rest is busy for a reason - a scan, an update, an indexer, a # runaway process - and the Section names the culprit so second level does not have to # revisit the machine. $script:CpuSampleCount = 5 # The process snapshot is a rate over an interval rather than an instant, so it needs its # own settle regardless of the shared sampling wait. $script:CpuProcessSampleSeconds = 2 function Get-CpuData { [CmdletBinding()] [OutputType([psobject])] param([hashtable]$Parameters = @{}) $filter = "Name='_Total'" # The first read of a formatted-performance class is primed with zeroes; discard it. [void](Get-CimInstance Win32_PerfFormattedData_Counters_ProcessorInformation -Filter $filter -ErrorAction SilentlyContinue) # One step per reading plus the process snapshot's settle, which is the longest wait. $steps = $script:CpuSampleCount + 1 $samples = @(1..$script:CpuSampleCount | ForEach-Object { Write-CheckProgress -Status ((Get-Text 'Console.Progress.Sample') -f $_, $script:CpuSampleCount) -Step ($_ - 1) -Of $steps Wait-Sample Get-CimInstance Win32_PerfFormattedData_Counters_ProcessorInformation -Filter $filter -ErrorAction SilentlyContinue }) # The script divided this counter by a core count the System Check had stashed in # script scope, so reordering the two Checks silently changed the column. Asking the # runtime directly removes that coupling. On Windows PowerShell 5.1 a machine with # more than 64 logical processors reports only the current processor group; no # Target Machine in the estate is close to that. $logical = [Environment]::ProcessorCount [void](Get-CimInstance Win32_PerfFormattedData_PerfProc_Process -ErrorAction SilentlyContinue) $settled = Get-SampleSeconds -Seconds $script:CpuProcessSampleSeconds Write-CheckProgress -Status (Get-Text 'Console.Progress.Settle') -Step $script:CpuSampleCount -Of $steps Wait-Sample -Seconds $script:CpuProcessSampleSeconds $top = @(Get-CimInstance Win32_PerfFormattedData_PerfProc_Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -ne '_Total' -and $_.Name -ne 'Idle' } | Sort-Object PercentProcessorTime -Descending | Select-Object -First 12 | ForEach-Object { [pscustomobject]@{ Process = $_.Name 'CPU %' = [math]::Round($_.PercentProcessorTime / [math]::Max($logical, 1), 1) 'Private MB' = [math]::Round($_.WorkingSetPrivate / 1MB) 'IO KB/s' = [math]::Round($_.IODataBytesPersec / 1KB) } }) [pscustomobject]@{ PSTypeName = 'Gutcheck.Data.Cpu' ProcessorTimeSamples = @($samples | ForEach-Object { $_.PercentProcessorTime }) PerformanceLimitSamples = @($samples | ForEach-Object { $_.PercentPerformanceLimit }) TopProcessesByCpu = $top # How long the process snapshot actually settled for, so the Section can say so # rather than claim a window it did not use. ProcessSampleSeconds = $settled } } function ConvertTo-CpuFinding { [CmdletBinding()] [OutputType([psobject])] param( [AllowNull()]$Data, [hashtable]$Parameters = @{} ) New-CpuLoadFinding -Data $Data -Parameters $Parameters New-CpuPerformanceLimitFinding -Data $Data -Parameters $Parameters } function ConvertTo-CpuSection { [CmdletBinding()] [OutputType([psobject])] param([AllowNull()]$Data) $seconds = ConvertTo-Number (Get-DataProperty $Data 'ProcessSampleSeconds') if ($null -eq $seconds) { $seconds = $script:CpuProcessSampleSeconds } New-Section -Title ((Get-Text 'Title.Cpu.TopProcessesByCpu') -f $seconds) ` -Row (Get-DataProperty $Data 'TopProcessesByCpu') } function New-CpuLoadFinding { [CmdletBinding()] param([AllowNull()]$Data, [hashtable]$Parameters) $warn = Get-Parameter $Parameters 'CpuLoadAtRestWarnPercent' 30 $fail = Get-Parameter $Parameters 'CpuLoadAtRestFailPercent' 60 $average = Get-SampleAverage (Get-DataProperty $Data 'ProcessorTimeSamples') if ($null -eq $average) { return New-UnavailableFinding -Category CPU -Check (Get-Text 'Check.Cpu.CPULoadAtRest') ` -Hint (Get-Text 'Hint.Cpu.TheProcessorPerformanceCountersDid') } New-Finding -Category CPU -Check (Get-Text 'Check.Cpu.CPULoadAtRest') -Severity (Get-Severity $average $warn $fail) ` -Value ('{0:N0} %' -f $average) ` -Hint (Get-Text 'Hint.Cpu.HighLoadWhileIdleSee') } function New-CpuPerformanceLimitFinding { [CmdletBinding()] param([AllowNull()]$Data, [hashtable]$Parameters) # Graded downwards, and the script only ever warned here, so the FAIL threshold # defaults to a percentage no reading can fall below. $warnBelow = Get-Parameter $Parameters 'CpuPerformanceLimitWarnBelowPercent' 90 $failBelow = Get-Parameter $Parameters 'CpuPerformanceLimitFailBelowPercent' ([double]::MinValue) # Absent on processors that do not expose a performance limit. The script reported # nothing at all in that case rather than a gap, and so does this. $average = Get-SampleAverage (Get-DataProperty $Data 'PerformanceLimitSamples') if ($null -eq $average) { return } New-Finding -Category CPU -Check (Get-Text 'Check.Cpu.CPUPerformanceLimit') -Severity (Get-SeverityBelow $average $warnBelow $failBelow) ` -Value ('{0:N0} %' -f $average) ` -Hint (Get-Text 'Hint.Cpu.FirmwareOSIsCappingThe') } |