From 4051239d0f2c7d7372d8de955e36a53890ec5267 Mon Sep 17 00:00:00 2001 From: Lord Hepipud Date: Mon, 11 Aug 2025 17:37:10 +0200 Subject: [PATCH] Adds feature to update the cache for performance counter instances to keep track of system changes --- doc/100-General/10-Changelog.md | 1 + .../New-IcingaPerformanceCounter.psm1 | 43 ++++++++--- .../New-IcingaPerformanceCounterArray.psm1 | 13 ---- ...how-IcingaPerformanceCounterInstances.psm1 | 2 +- .../Update-IcingaPerformanceCounterCache.psm1 | 75 +++++++++++++++++++ 5 files changed, 109 insertions(+), 25 deletions(-) create mode 100644 lib/core/perfcounter/Update-IcingaPerformanceCounterCache.psm1 diff --git a/doc/100-General/10-Changelog.md b/doc/100-General/10-Changelog.md index c6edfedc..a95d3cb5 100644 --- a/doc/100-General/10-Changelog.md +++ b/doc/100-General/10-Changelog.md @@ -20,6 +20,7 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic ### Enhancements * [#838](https://github.com/Icinga/icinga-powershell-framework/pull/838) Enhances Icinga for Windows to never load and user PowerShell profiles +* [#11](https://github.com/Icinga/icinga-powershell-framework/pull/11) Adds feature to update the cache for performance counter instances to keep track of system changes ## 1.13.4 (tbd) diff --git a/lib/core/perfcounter/New-IcingaPerformanceCounter.psm1 b/lib/core/perfcounter/New-IcingaPerformanceCounter.psm1 index 6fbcb8a4..79efdc6d 100644 --- a/lib/core/perfcounter/New-IcingaPerformanceCounter.psm1 +++ b/lib/core/perfcounter/New-IcingaPerformanceCounter.psm1 @@ -26,6 +26,9 @@ if multiple counters are fetched during one call with this function if the sleep is done afterwards manually. A sleep is set to 500ms to ensure counter data is valid and contains an offset from previous/current values +.PARAMETER NoCache + Set this if no caching of the counter is intended. This will prevent from adding + single counters to the internal cache during update phase .INPUTS System.String .LINK @@ -36,7 +39,8 @@ function New-IcingaPerformanceCounter() { param( [string]$Counter = '', - [boolean]$SkipWait = $FALSE + [boolean]$SkipWait = $FALSE, + [switch]$NoCache = $FALSE ); # Simply use the counter name, like @@ -45,7 +49,7 @@ function New-IcingaPerformanceCounter() return (New-IcingaPerformanceCounterNullObject -FullName $Counter -ErrorMessage 'Failed to initialise counter, as no counter was specified.'); } - [array]$CounterArray = $Counter.Split('\'); + [array]$CounterArray = $Counter.Split('\'); [string]$UseCounterCategory = ''; [string]$UseCounterName = ''; [string]$UseCounterInstance = ''; @@ -72,6 +76,13 @@ function New-IcingaPerformanceCounter() # At last get the actual counter containing our values $UseCounterName = $CounterArray[2]; + if ($NoCache -eq $FALSE) { + # If we are not skipping the cache, we will update the cache + # with the current counter path. This will ensure that we + # have a valid cache for the counter and can return it later + Update-IcingaPerformanceCounterCache -Counter $Counter; + } + # Now as we know how the counter path is constructed and has been split into # the different values, we need to know how to handle the instances of the counter @@ -80,11 +91,13 @@ function New-IcingaPerformanceCounter() # which contains the parent name including counters for all instances that # have been found if ($UseCounterInstance -eq '*') { - # In case we already loaded the counters once, return the finished array - $CachedCounter = Get-IcingaPerformanceCounterCacheItem -Counter $Counter; + if ($NoCache -eq $FALSE) { + # In case we already loaded the counters once, return the finished array + $CachedCounter = Get-IcingaPerformanceCounterCacheItem -Counter $Counter; - if ($null -ne $CachedCounter) { - return (New-IcingaPerformanceCounterResult -FullName $Counter -PerformanceCounters $CachedCounter); + if ($null -ne $CachedCounter) { + return (New-IcingaPerformanceCounterResult -FullName $Counter -PerformanceCounters $CachedCounter); + } } # If we need to build the array, load all instances from the counters and @@ -116,7 +129,9 @@ function New-IcingaPerformanceCounter() # Add the parent counter including the array of Performance Counters to our # caching mechanism and return the New-IcingaPerformanceCounterResult object for usage # within the monitoring modules - Add-IcingaPerformanceCounterCache -Counter $Counter -Instances $AllCountersInstances; + if ($NoCache -eq $FALSE) { + Add-IcingaPerformanceCounterCache -Counter $Counter -Instances $AllCountersInstances; + } return (New-IcingaPerformanceCounterResult -FullName $Counter -PerformanceCounters $AllCountersInstances); } else { # This part will handle the counters without any instances as well as @@ -124,16 +139,22 @@ function New-IcingaPerformanceCounter() # In case we already have the counter within our cache, return the # cached informations - $CachedCounter = Get-IcingaPerformanceCounterCacheItem -Counter $Counter; + if ($NoCache -eq $FALSE) { + $CachedCounter = Get-IcingaPerformanceCounterCacheItem -Counter $Counter; - if ($null -ne $CachedCounter) { - return $CachedCounter; + if ($null -ne $CachedCounter) { + return $CachedCounter; + } } # If the cache is not present yet, create the Performance Counter object, # and add it to our cache $NewCounter = New-IcingaPerformanceCounterObject -FullName $Counter -Category $UseCounterCategory -Counter $UseCounterName -Instance $UseCounterInstance -SkipWait $SkipWait; - Add-IcingaPerformanceCounterCache -Counter $Counter -Instances $NewCounter; + if ($NoCache -eq $FALSE) { + Add-IcingaPerformanceCounterCache -Counter $Counter -Instances $NewCounter; + } else { + return $NewCounter; + } } # This function will always return non-instance counters or diff --git a/lib/core/perfcounter/New-IcingaPerformanceCounterArray.psm1 b/lib/core/perfcounter/New-IcingaPerformanceCounterArray.psm1 index 4ff70b89..d4cb00cc 100644 --- a/lib/core/perfcounter/New-IcingaPerformanceCounterArray.psm1 +++ b/lib/core/perfcounter/New-IcingaPerformanceCounterArray.psm1 @@ -40,13 +40,6 @@ function New-IcingaPerformanceCounterArray() # NumOfCounters * 500 milliseconds for the first runs. This will speed # up the general loading of counters and will not require some fancy # pre-caching / configuration handler - $CachedCounter = Get-IcingaPerformanceCounterCacheItem -Counter $Counter; - - # Remove this for now to ensure our CPU metrics will not be cached - # and represent correct values, not exceeding 200% and beyond - #if ($null -ne $CachedCounter) { - # $RequireSleep = $FALSE; - #} $obj = New-IcingaPerformanceCounter -Counter $counter -SkipWait $TRUE; if ($CounterResult.ContainsKey($obj.Name()) -eq $FALSE) { @@ -54,12 +47,6 @@ function New-IcingaPerformanceCounterArray() } } - # TODO: Add a cache for our Performance Counters to only fetch them once - # for each session to speed up the loading. This cold be something like - # this: - # - # $Global:Icinga.Private.PerformanceCounter.Cache += $CounterResult; - # Above we initialise ever single counter and we only require a sleep once # in case a new, yet unknown counter was added if ($RequireSleep) { diff --git a/lib/core/perfcounter/Show-IcingaPerformanceCounterInstances.psm1 b/lib/core/perfcounter/Show-IcingaPerformanceCounterInstances.psm1 index f633e41a..9b2da756 100644 --- a/lib/core/perfcounter/Show-IcingaPerformanceCounterInstances.psm1 +++ b/lib/core/perfcounter/Show-IcingaPerformanceCounterInstances.psm1 @@ -35,7 +35,7 @@ function Show-IcingaPerformanceCounterInstances() return; } - $PerfCounter = New-IcingaPerformanceCounter -Counter $Counter -SkipWait $TRUE; + $PerfCounter = New-IcingaPerformanceCounter -Counter $Counter -SkipWait $TRUE -NoCache; foreach ($entry in $PerfCounter.Counters) { $Instances.Add( diff --git a/lib/core/perfcounter/Update-IcingaPerformanceCounterCache.psm1 b/lib/core/perfcounter/Update-IcingaPerformanceCounterCache.psm1 new file mode 100644 index 00000000..e5d7940c --- /dev/null +++ b/lib/core/perfcounter/Update-IcingaPerformanceCounterCache.psm1 @@ -0,0 +1,75 @@ +<# +.SYNOPSIS + Updates the cached instances of a specified performance counter in the Icinga PowerShell Framework. + +.DESCRIPTION + The Update-IcingaPerformanceCounterCache function synchronizes the cached instances of a given performance counter. + It removes instances that no longer exist and adds new instances that are not yet cached. + This ensures that the cache accurately reflects the current state of the performance counter instances. + +.PARAMETER Counter + The name of the performance counter whose cache should be updated. + +.EXAMPLE + Update-IcingaPerformanceCounterCache -Counter "\Processor(_Total)\% Processor Time" + + Updates the cache for the specified performance counter. + +.NOTES + This function is intended for internal use within the Icinga PowerShell Framework. + It requires that the global cache variable and related functions are available. + +#> +function Update-IcingaPerformanceCounterCache() +{ + param ( + $Counter + ); + + if ([string]::IsNullOrEmpty($Counter)) { + return; + } + + if ($Global:Icinga.Private.PerformanceCounter.Cache.ContainsKey($Counter) -eq $FALSE) { + # If there is no cache entry for the provided counter, we don't need to do anything yet + return; + } + + # First we need to prepare some data by fetching the current instances of the counter + # and the cached instances. We will then compare them and update the cache accordingly + [array]$CounterInstances = Show-IcingaPerformanceCounterInstances -Counter $Counter; + [array]$CachedInstances = $Global:Icinga.Private.PerformanceCounter.Cache[$Counter]; + [array]$UpdatedInstances = @(); + [array]$CachedInstanceNames = $CachedInstances.FullName; + + # We will now iterate over the cached instances and check if they are still present in the current + # counter instances. If they are not, we will remove them from the cache. + # If they are present, we will keep them in the updated instances array. + for ($index = 0; $index -lt $CachedInstances.Count; $index++) { + $cachedInstance = $CachedInstances[$index]; + $instanceName = $cachedInstance.FullName; + + # If the instance is not in the current list, we remove it + if ($CounterInstances.Value -contains $instanceName) { + $UpdatedInstances += $cachedInstance; + } + } + + # Now we will iterate over the current counter instances and check if they are already cached. + # If they are not, we will add them to the updated instances array. + # This ensures that we only add new instances that are not already cached. + for ($index = 0; $index -lt $CounterInstances.Count; $index++) { + $instanceName = $CounterInstances[$index].Value; + + if ($CachedInstanceNames -notcontains $instanceName) { + # If the instance is not cached, we create a new performance counter object + # and add it to the updated instances array + $UpdatedInstances += (New-IcingaPerformanceCounter -Counter $instanceName -SkipWait $TRUE -NoCache); + } + } + + # Finally, we update the cache with the new instances + # This will ensure that the cache is up-to-date with the current state of the performance + # counter instances + $Global:Icinga.Private.PerformanceCounter.Cache[$Counter] = $UpdatedInstances; +}