diff --git a/doc/100-General/10-Changelog.md b/doc/100-General/10-Changelog.md index 9be01dc..35f0bcf 100644 --- a/doc/100-General/10-Changelog.md +++ b/doc/100-General/10-Changelog.md @@ -11,6 +11,10 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic [Issues and PRs](https://github.com/Icinga/icinga-powershell-framework/milestone/28) +### Enhancements + +* [#679](https://github.com/Icinga/icinga-powershell-framework/pull/679) Adds a new data provider for fetching process information of Windows systems, while sorting all objects based on a process name and their process id + ## 1.11.2 (tbd) [Issues and PRs](https://github.com/Icinga/icinga-powershell-framework/milestone/30) diff --git a/lib/provider/assets/cpu/Get-IcingaProviderDataValuesCpu.psm1 b/lib/provider/assets/cpu/Get-IcingaProviderDataValuesCpu.psm1 index e61ebda..55c1773 100644 --- a/lib/provider/assets/cpu/Get-IcingaProviderDataValuesCpu.psm1 +++ b/lib/provider/assets/cpu/Get-IcingaProviderDataValuesCpu.psm1 @@ -1,6 +1,8 @@ function Get-IcingaProviderDataValuesCpu() { param ( + [array]$IncludeFilter = @(), + [array]$ExcludeFilter = @(), [switch]$IncludeDetails = $FALSE ); diff --git a/lib/provider/assets/hyperv/Get-IcingaProviderDataValuesHyperV.psm1 b/lib/provider/assets/hyperv/Get-IcingaProviderDataValuesHyperV.psm1 index 3149ae3..8514071 100644 --- a/lib/provider/assets/hyperv/Get-IcingaProviderDataValuesHyperV.psm1 +++ b/lib/provider/assets/hyperv/Get-IcingaProviderDataValuesHyperV.psm1 @@ -1,6 +1,8 @@ function Get-IcingaProviderDataValuesHyperV() { param ( + [array]$IncludeFilter = @(), + [array]$ExcludeFilter = @(), [switch]$IncludeDetails = $FALSE ); diff --git a/lib/provider/assets/process/Get-IcingaProviderDataValuesProcess.psm1 b/lib/provider/assets/process/Get-IcingaProviderDataValuesProcess.psm1 new file mode 100644 index 0000000..24deaab --- /dev/null +++ b/lib/provider/assets/process/Get-IcingaProviderDataValuesProcess.psm1 @@ -0,0 +1,154 @@ +function Get-IcingaProviderDataValuesProcess() +{ + param ( + [array]$IncludeFilter = @(), + [array]$ExcludeFilter = @(), + [switch]$IncludeDetails = $FALSE + ); + + # Fetch all required process information + $ProviderName = 'Process' + $ProcessData = New-IcingaProviderObject -Name $ProviderName; + [array]$ProcessInformation = Get-IcingaWindowsInformation Win32_Process; + [array]$ProcessPerfDataList = Get-IcingaWindowsInformation Win32_PerfFormattedData_PerfProc_Process; + [hashtable]$CPUProcessCache = @{ }; + [hashtable]$MEMProcessCache = @{ }; + + # Read our process data and build our metrics object + foreach ($entry in $ProcessInformation) { + [string]$ProcessName = $entry.Name.Replace('.exe', ''); + [int]$ProcessId = [int]$entry.ProcessId; + + if ((Test-IcingaArrayFilter -InputObject $ProcessName -Include $IncludeFilter -Exclude $ExcludeFilter) -eq $FALSE) { + continue; + } + + if ((Test-PSCustomObjectMember -PSObject $ProcessData.Metrics -Name $ProcessName) -eq $FALSE) { + $ProcessData.Metrics | Add-Member -MemberType NoteProperty -Name $ProcessName -Value (New-Object PSCustomObject); + $ProcessData.Metrics.$ProcessName | Add-Member -MemberType NoteProperty -Name 'List' -Value (New-Object PSCustomObject); + $ProcessData.Metrics.$ProcessName | Add-Member -MemberType NoteProperty -Name 'Total' -Value (New-Object PSCustomObject); + $ProcessData.Metrics.$ProcessName.Total | Add-Member -MemberType NoteProperty -Name 'PageFileUsage' -Value 0; + $ProcessData.Metrics.$ProcessName.Total | Add-Member -MemberType NoteProperty -Name 'ThreadCount' -Value 0; + $ProcessData.Metrics.$ProcessName.Total | Add-Member -MemberType NoteProperty -Name 'WorkingSetSize' -Value 0; + $ProcessData.Metrics.$ProcessName.Total | Add-Member -MemberType NoteProperty -Name 'ProcessCount' -Value 0; + $ProcessData.Metrics.$ProcessName.Total | Add-Member -MemberType NoteProperty -Name 'CpuUsage' -Value 0; + $ProcessData.Metrics.$ProcessName.Total | Add-Member -MemberType NoteProperty -Name 'MemoryUsage' -Value 0; + } + + # Detail for each single Process + $ProcessData.Metrics.$ProcessName.List | Add-Member -MemberType NoteProperty -Name $ProcessId -Value (New-Object PSCustomObject); + $ProcessData.Metrics.$ProcessName.List.$ProcessId | Add-Member -MemberType NoteProperty -Name 'Name' -Value $ProcessName; + $ProcessData.Metrics.$ProcessName.List.$ProcessId | Add-Member -MemberType NoteProperty -Name 'ProcessName' -Value ([string]$entry.Name); + $ProcessData.Metrics.$ProcessName.List.$ProcessId | Add-Member -MemberType NoteProperty -Name 'ProcessId' -Value $ProcessId; + $ProcessData.Metrics.$ProcessName.List.$ProcessId | Add-Member -MemberType NoteProperty -Name 'Priority' -Value ([int]$entry.Priority); + $ProcessData.Metrics.$ProcessName.List.$ProcessId | Add-Member -MemberType NoteProperty -Name 'PageFileUsage' -Value ([decimal]$entry.PageFileUsage); + $ProcessData.Metrics.$ProcessName.List.$ProcessId | Add-Member -MemberType NoteProperty -Name 'ThreadCount' -Value ([int]$entry.ThreadCount); + $ProcessData.Metrics.$ProcessName.List.$ProcessId | Add-Member -MemberType NoteProperty -Name 'KernelModeTime' -Value ([decimal]$entry.KernelModeTime); + $ProcessData.Metrics.$ProcessName.List.$ProcessId | Add-Member -MemberType NoteProperty -Name 'UserModeTime' -Value ([decimal]$entry.UserModeTime); + $ProcessData.Metrics.$ProcessName.List.$ProcessId | Add-Member -MemberType NoteProperty -Name 'WorkingSetSize' -Value ([decimal]$entry.WorkingSetSize); + $ProcessData.Metrics.$ProcessName.List.$ProcessId | Add-Member -MemberType NoteProperty -Name 'CommandLine' -Value ([string]$entry.CommandLine); + $ProcessData.Metrics.$ProcessName.List.$ProcessId | Add-Member -MemberType NoteProperty -Name 'CpuUsage' -Value 0; + $ProcessData.Metrics.$ProcessName.List.$ProcessId | Add-Member -MemberType NoteProperty -Name 'MemoryUsage' -Value 0; + + # Total data for all processes with a given name + $ProcessData.Metrics.$ProcessName.Total.PageFileUsage += ([decimal]$entry.PageFileUsage); + $ProcessData.Metrics.$ProcessName.Total.ThreadCount += ([int]$entry.ThreadCount); + $ProcessData.Metrics.$ProcessName.Total.WorkingSetSize += ([decimal]$entry.WorkingSetSize); + $ProcessData.Metrics.$ProcessName.Total.ProcessCount += 1; + } + + # Process all process performance metrics and add memory and cpu usage to the correct process id + foreach ($entry in $ProcessPerfDataList) { + [string]$ProcessName = $entry.Name; + [int]$ProcessId = [int]$entry.IDProcess; + + if ($ProcessName.Contains('#')) { + $ProcessName = $ProcessName.Substring(0, $ProcessName.IndexOf('#')); + } + + if ((Test-IcingaArrayFilter -InputObject $ProcessName -Include $IncludeFilter -Exclude $ExcludeFilter) -eq $FALSE) { + continue; + } + + if ((Test-PSCustomObjectMember -PSObject $ProcessData.Metrics -Name $ProcessName) -eq $FALSE) { + continue; + } + + # Add a cache for our Process Data with all CPU loads for every single Process Id + $CPUProcessCache.Add([string]::Format('{0}|{1}', $ProcessName, [string]$ProcessId), [int]$entry.PercentProcessorTime); + $MEMProcessCache.Add([string]::Format('{0}|{1}', $ProcessName, [string]$ProcessId), [decimal]$entry.WorkingSetPrivate); + + # Just in case a process id is not present, we should ensure to add it to prevent exceptions + if ((Test-PSCustomObjectMember -PSObject $ProcessData.Metrics.$ProcessName.List -Name $ProcessId) -eq $FALSE) { + $ProcessData.Metrics.$ProcessName.List | Add-Member -MemberType NoteProperty -Name $ProcessId -Value (New-Object PSCustomObject); + $ProcessData.Metrics.$ProcessName.List.$ProcessId | Add-Member -MemberType NoteProperty -Name 'CpuUsage' -Value 0; + $ProcessData.Metrics.$ProcessName.List.$ProcessId | Add-Member -MemberType NoteProperty -Name 'MemoryUsage' -Value 0; + } + + $ProcessData.Metrics.$ProcessName.List.$ProcessId.CpuUsage = [int]$entry.PercentProcessorTime; + $ProcessData.Metrics.$ProcessName.List.$ProcessId.MemoryUsage = [decimal]$entry.WorkingSetPrivate; + $ProcessData.Metrics.$ProcessName.Total.CpuUsage += [int]$entry.PercentProcessorTime; + $ProcessData.Metrics.$ProcessName.Total.MemoryUsage += [decimal]$entry.WorkingSetPrivate; + } + + # Generaet a "hot" object for our 10 most CPU and Memory consuming process objects + $ProcessData.Metadata | Add-Member -MemberType NoteProperty -Name 'Hot' -Value (New-Object PSCustomObject); + $ProcessData.Metadata.Hot | Add-Member -MemberType NoteProperty -Name 'Cpu' -Value (New-Object PSCustomObject); + $ProcessData.Metadata.Hot | Add-Member -MemberType NoteProperty -Name 'Memory' -Value (New-Object PSCustomObject); + + [array]$TopCPUUsage = $CPUProcessCache.GetEnumerator() | Sort-Object Value -Descending; + [array]$TopMEMUsage = $MEMProcessCache.GetEnumerator() | Sort-Object Value -Descending; + [int]$IterationIndex = 0; + + while ($IterationIndex -lt 10) { + if ($TopCPUUsage.Count -gt 0 -And (($TopCPUUsage.Count - 1) -ge $IterationIndex )) { + + if ($TopCPUUsage.Count -gt 1) { + [string]$CPUProcessName = $TopCPUUsage.Key[$IterationIndex].Split('|')[0]; + [int]$CPUProcessId = $TopCPUUsage.Key[$IterationIndex].Split('|')[1]; + [int]$CPUUsage = $TopCPUUsage.Value[$IterationIndex]; + } else { + [string]$CPUProcessName = $TopCPUUsage.Key.Split('|')[0]; + [int]$CPUProcessId = $TopCPUUsage.Key.Split('|')[1]; + [int]$CPUUsage = $TopCPUUsage.Value; + } + + if ($TopCPUUsage.Value[$IterationIndex] -gt 0) { + $ProcessData.Metadata.Hot.Cpu | Add-Member -MemberType NoteProperty -Name $CPUProcessId -Value (New-Object PSCustomObject); + $ProcessData.Metadata.Hot.Cpu.$CPUProcessId | Add-Member -MemberType NoteProperty -Name 'Name' -Value $CPUProcessName; + $ProcessData.Metadata.Hot.Cpu.$CPUProcessId | Add-Member -MemberType NoteProperty -Name 'ProcessId' -Value $CPUProcessId; + $ProcessData.Metadata.Hot.Cpu.$CPUProcessId | Add-Member -MemberType NoteProperty -Name 'CpuUsage' -Value $CPUUsage; + } + } + + if ($TopMEMUsage.Count -gt 0 -And (($TopMEMUsage.Count - 1) -ge $IterationIndex )) { + if ($TopMEMUsage.Count -gt 1) { + [string]$MEMProcessName = $TopMEMUsage.Key[$IterationIndex].Split('|')[0]; + [int]$MEPProcessId = $TopMEMUsage.Key[$IterationIndex].Split('|')[1]; + [decimal]$MemoryUsage = $TopMEMUsage.Value[$IterationIndex]; + } else { + [string]$MEMProcessName = $TopMEMUsage.Key.Split('|')[0]; + [int]$MEPProcessId = $TopMEMUsage.Key.Split('|')[1]; + [int]$MemoryUsage = $TopMEMUsage.Value; + } + + if ($TopMEMUsage.Value[$IterationIndex] -gt 0) { + $ProcessData.Metadata.Hot.Memory | Add-Member -MemberType NoteProperty -Name $MEPProcessId -Value (New-Object PSCustomObject); + $ProcessData.Metadata.Hot.Memory.$MEPProcessId | Add-Member -MemberType NoteProperty -Name 'Name' -Value $MEMProcessName; + $ProcessData.Metadata.Hot.Memory.$MEPProcessId | Add-Member -MemberType NoteProperty -Name 'ProcessId' -Value $MEPProcessId; + $ProcessData.Metadata.Hot.Memory.$MEPProcessId | Add-Member -MemberType NoteProperty -Name 'MemoryUsage' -Value $MemoryUsage; + } + } + + $IterationIndex += 1; + } + + $CPUProcessCache = $null; + $MEMProcessCache = $null; + $ProcessInformation = $null; + $ProcessPerfDataList = $null; + $TopCPUUsage = $null; + $TopMEMUsage = $null; + + return $ProcessData; +} diff --git a/lib/provider/core/Get-IcingaProviderData.psm1 b/lib/provider/core/Get-IcingaProviderData.psm1 index 1c3acf7..310717d 100644 --- a/lib/provider/core/Get-IcingaProviderData.psm1 +++ b/lib/provider/core/Get-IcingaProviderData.psm1 @@ -1,7 +1,10 @@ function Get-IcingaProviderData() { param ( - [array]$Name = '' + [array]$Name = '', + [array]$IncludeFilter = @(), + [array]$ExcludeFilter = @(), + [switch]$IncludeDetails = $FALSE ); [hashtable]$ProviderData = @{ }; @@ -24,9 +27,8 @@ function Get-IcingaProviderData() continue; } - $ProviderCmd = $ProviderDataList[0]; - - $ProviderContent = (& $ProviderCmd -IncludeDetails); + $ProviderCmd = $ProviderDataList[0]; + $ProviderContent = (& $ProviderCmd -IncludeDetails:$IncludeDetails -IncludeFilter $IncludeFilter -ExcludeFilter $ExcludeFilter); if ($ProviderData.ContainsKey($ProviderContent.Name) -eq $FALSE) { $ProviderData.Add($ProviderContent.Name, $ProviderContent);