Skip to content

Commit ec65665

Browse files
authored
Merge pull request #206 from Icinga:fix/background_service_checks_not_working
Fix: Background service check daemon data pool separation and memory leak Improves the background daemon by separating each single configured check into an own data pool, preventing data of leaking from one thread to another which might cause a memory leak in long term with plenty of background checks defined. This might also reduce CPU impact because lesser data has to be processed.
2 parents 0a57bce + b9eca3a commit ec65665

9 files changed

+171
-36
lines changed

doc/31-Changelog.md

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic
1919
* [#203](https://github.com/Icinga/icinga-powershell-framework/pull/203) Removes experimental state of the Icinga PowerShell Framework code caching and adds docs on how to use the feature
2020
* [#205](https://github.com/Icinga/icinga-powershell-framework/pull/205) Ensure Icinga for Windows configuration file is opened as read-only for every single task besides actually modifying configuration content
2121

22+
### Bugfixes
23+
24+
* [#206](https://github.com/Icinga/icinga-powershell-framework/pull/206) Fixes background service check daemon for collecting metrics over time which will no longer share data between configured checks which might cause higher CPU load and a possible memory leak
25+
2226
## 1.3.1 (2021-02-04)
2327

2428
[Issue and PRs](https://github.com/Icinga/icinga-powershell-framework/milestone/12?closed=1)

lib/core/cache/Get-IcingaCacheData.psm1

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ function Get-IcingaCacheData()
3434

3535
$CacheFile = Join-Path -Path (Join-Path -Path (Join-Path -Path (Get-IcingaCacheDir) -ChildPath $Space) -ChildPath $CacheStore) -ChildPath ([string]::Format('{0}.json', $KeyName));
3636
[string]$Content = '';
37-
$cacheData = @{};
37+
$cacheData = @{ };
3838

3939
if ((Test-Path $CacheFile) -eq $FALSE) {
4040
return $null;
4141
}
4242

43-
$Content = Get-Content -Path $CacheFile;
43+
$Content = Read-IcingaFileContent -File $CacheFile;
4444

4545
if ([string]::IsNullOrEmpty($Content)) {
4646
return $null;

lib/core/cache/Set-IcingaCacheData.psm1

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@
2727

2828
function Set-IcingaCacheData()
2929
{
30-
param(
30+
param (
3131
[string]$Space,
3232
[string]$CacheStore,
3333
[string]$KeyName,
3434
$Value
3535
);
3636

3737
$CacheFile = Join-Path -Path (Join-Path -Path (Join-Path -Path (Get-IcingaCacheDir) -ChildPath $Space) -ChildPath $CacheStore) -ChildPath ([string]::Format('{0}.json', $KeyName));
38-
$cacheData = @{};
38+
$cacheData = @{ };
3939

4040
if ((Test-Path $CacheFile)) {
4141
$cacheData = Get-IcingaCacheData -Space $Space -CacheStore $CacheStore;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<#
2+
.SYNOPSIS
3+
Clear all cached values for all check commands executed by this thread.
4+
This is mandatory as we might run into a memory leak otherwise!
5+
.DESCRIPTION
6+
Clear all cached values for all check commands executed by this thread.
7+
This is mandatory as we might run into a memory leak otherwise!
8+
.FUNCTIONALITY
9+
Clear all cached values for all check commands executed by this thread.
10+
This is mandatory as we might run into a memory leak otherwise!
11+
.OUTPUTS
12+
System.Object
13+
.LINK
14+
https://github.com/Icinga/icinga-powershell-framework
15+
#>
16+
17+
function Clear-IcingaCheckSchedulerCheckData()
18+
{
19+
if ($null -eq $global:Icinga) {
20+
return;
21+
}
22+
23+
if ($global:Icinga.ContainsKey('CheckData') -eq $FALSE) {
24+
return;
25+
}
26+
27+
$global:Icinga.CheckData.Clear();
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<#
2+
.SYNOPSIS
3+
Clears the entire check scheduler cache environment and frees memory as
4+
well as cleaning the stack
5+
.DESCRIPTION
6+
Clears the entire check scheduler cache environment and frees memory as
7+
well as cleaning the stack
8+
.FUNCTIONALITY
9+
Clears the entire check scheduler cache environment and frees memory as
10+
well as cleaning the stack
11+
.OUTPUTS
12+
System.Object
13+
.LINK
14+
https://github.com/Icinga/icinga-powershell-framework
15+
#>
16+
17+
function Clear-IcingaCheckSchedulerEnvironment()
18+
{
19+
if ($null -eq $global:Icinga) {
20+
return;
21+
}
22+
23+
Get-IcingaCheckSchedulerPluginOutput | Out-Null;
24+
Get-IcingaCheckSchedulerPerfData | Out-Null;
25+
Clear-IcingaCheckSchedulerCheckData;
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<#
2+
.SYNOPSIS
3+
Fetch the raw output values for a check command for each single object
4+
processed by New-IcingaCheck
5+
.DESCRIPTION
6+
Fetch the raw output values for a check command for each single object
7+
processed by New-IcingaCheck
8+
.FUNCTIONALITY
9+
Fetch the raw output values for a check command for each single object
10+
processed by New-IcingaCheck
11+
.OUTPUTS
12+
System.Object
13+
.LINK
14+
https://github.com/Icinga/icinga-powershell-framework
15+
#>
16+
17+
function Get-IcingaCheckSchedulerCheckData()
18+
{
19+
if ($null -eq $global:Icinga) {
20+
return $null;
21+
}
22+
23+
if ($global:Icinga.ContainsKey('CheckData') -eq $FALSE) {
24+
return @{ };
25+
}
26+
27+
return $global:Icinga.CheckData;
28+
}
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,60 @@
1+
<#
2+
.SYNOPSIS
3+
Create a new environment in which we can store check results, performance data
4+
and values over time or executed plugins.
5+
6+
Usage:
7+
8+
Access the string plugin output by calling `Get-IcingaCheckSchedulerPluginOutput`
9+
Access possible performance data with `Get-IcingaCheckSchedulerPerfData`
10+
11+
If you execute check plugins, ensure you read both of these functions to fetch the
12+
result of the plugin call and to clear the stack and memory of the check data.
13+
14+
If you do not require the output, you can write them to Null
15+
16+
Get-IcingaCheckSchedulerPluginOutput | Out-Null;
17+
Get-IcingaCheckSchedulerPerfData | Out-Null;
18+
19+
IMPORTANT:
20+
In addition each value for each object created with `New-IcingaCheck` is stored
21+
with a timestamp for the check command inside a hashtable. If you do not require
22+
these data, you MUST call `Clear-IcingaCheckSchedulerCheckData` to free memory
23+
and clear data from the stack!
24+
25+
If you are finished with all data processing and do not require anything within
26+
memory anyway, you can safely call `Clear-IcingaCheckSchedulerEnvironment` to
27+
do the same thing in one call.
28+
.DESCRIPTION
29+
Fetch the raw output values for a check command for each single object
30+
processed by New-IcingaCheck
31+
.FUNCTIONALITY
32+
Fetch the raw output values for a check command for each single object
33+
processed by New-IcingaCheck
34+
.OUTPUTS
35+
System.Object
36+
.LINK
37+
https://github.com/Icinga/icinga-powershell-framework
38+
#>
39+
140
function New-IcingaCheckSchedulerEnvironment()
241
{
342
# Legacy code
4-
$IcingaDaemonData.IcingaThreadContent.Add('Scheduler', @{ });
43+
if ($IcingaDaemonData.IcingaThreadContent.ContainsKey('Scheduler') -eq $FALSE) {
44+
$IcingaDaemonData.IcingaThreadContent.Add('Scheduler', @{ });
45+
}
546

647
if ($null -eq $global:Icinga) {
7-
$global:Icinga = @{};
48+
$global:Icinga = @{ };
849
}
950

10-
$global:Icinga.Add('CheckResults', @());
11-
$global:Icinga.Add('PerfData', @());
51+
if ($global:Icinga.ContainsKey('CheckResults') -eq $FALSE) {
52+
$global:Icinga.Add('CheckResults', @());
53+
}
54+
if ($global:Icinga.ContainsKey('PerfData') -eq $FALSE) {
55+
$global:Icinga.Add('PerfData', @());
56+
}
57+
if ($global:Icinga.ContainsKey('CheckData') -eq $FALSE) {
58+
$global:Icinga.Add('CheckData', @{ });
59+
}
1260
}

lib/daemons/ServiceCheckDaemon/Start-IcingaServiceCheckDaemon.psm1

+22-18
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ function Start-IcingaServiceCheckDaemon()
55

66
Use-Icinga -LibOnly -Daemon;
77

8-
$IcingaDaemonData.BackgroundDaemon.Add('ServiceCheckScheduler', [hashtable]::Synchronized(@{}));
98
$IcingaDaemonData.IcingaThreadPool.Add('ServiceCheckPool', (New-IcingaThreadPool -MaxInstances (Get-IcingaConfigTreeCount -Path 'BackgroundDaemon.RegisteredServices')));
109

1110
while ($TRUE) {
@@ -45,11 +44,16 @@ function Start-IcingaServiceCheckTask()
4544
Use-Icinga -LibOnly -Daemon;
4645
$PassedTime = 0;
4746
$SortedResult = $null;
48-
$OldData = @{};
49-
$PerfCache = @{};
50-
$AverageCalc = @{};
47+
$OldData = @{ };
48+
$PerfCache = @{ };
49+
$AverageCalc = @{ };
5150
[int]$MaxTime = 0;
5251

52+
# Initialise some global variables we use to actually store check result data from
53+
# plugins properly. This is doable from each thread instance as this part isn't
54+
# shared between daemons
55+
New-IcingaCheckSchedulerEnvironment;
56+
5357
foreach ($index in $TimeIndexes) {
5458
# Only allow numeric index values
5559
if ((Test-Numeric $index) -eq $FALSE) {
@@ -73,22 +77,22 @@ function Start-IcingaServiceCheckTask()
7377

7478
[int]$MaxTimeInSeconds = $MaxTime * 60;
7579

76-
if (-Not ($IcingaDaemonData.BackgroundDaemon.ServiceCheckScheduler.ContainsKey($CheckCommand))) {
77-
$IcingaDaemonData.BackgroundDaemon.ServiceCheckScheduler.Add($CheckCommand, [hashtable]::Synchronized(@{}));
78-
$IcingaDaemonData.BackgroundDaemon.ServiceCheckScheduler[$CheckCommand].Add('results', [hashtable]::Synchronized(@{}));
79-
$IcingaDaemonData.BackgroundDaemon.ServiceCheckScheduler[$CheckCommand].Add('average', [hashtable]::Synchronized(@{}));
80+
if (-Not ($global:Icinga.CheckData.ContainsKey($CheckCommand))) {
81+
$global:Icinga.CheckData.Add($CheckCommand, @{ });
82+
$global:Icinga.CheckData[$CheckCommand].Add('results', @{ });
83+
$global:Icinga.CheckData[$CheckCommand].Add('average', @{ });
8084
}
8185

8286
$LoadedCacheData = Get-IcingaCacheData -Space 'sc_daemon' -CacheStore 'checkresult_store' -KeyName $CheckCommand;
8387

8488
if ($null -ne $LoadedCacheData) {
8589
foreach ($entry in $LoadedCacheData.PSObject.Properties) {
86-
$IcingaDaemonData.BackgroundDaemon.ServiceCheckScheduler[$CheckCommand]['results'].Add(
90+
$global:Icinga.CheckData[$CheckCommand]['results'].Add(
8791
$entry.name,
88-
[hashtable]::Synchronized(@{})
92+
@{ }
8993
);
9094
foreach ($item in $entry.Value.PSObject.Properties) {
91-
$IcingaDaemonData.BackgroundDaemon.ServiceCheckScheduler[$CheckCommand]['results'][$entry.name].Add(
95+
$global:Icinga.CheckData[$CheckCommand]['results'][$entry.name].Add(
9296
$item.Name,
9397
$item.Value
9498
);
@@ -106,11 +110,11 @@ function Start-IcingaServiceCheckTask()
106110

107111
$UnixTime = Get-IcingaUnixTime;
108112

109-
foreach ($result in $IcingaDaemonData.BackgroundDaemon.ServiceCheckScheduler[$CheckCommand]['results'].Keys) {
113+
foreach ($result in $global:Icinga.CheckData[$CheckCommand]['results'].Keys) {
110114
[string]$HashIndex = $result;
111-
$SortedResult = $IcingaDaemonData.BackgroundDaemon.ServiceCheckScheduler[$CheckCommand]['results'][$HashIndex].GetEnumerator() | Sort-Object name -Descending;
112-
Add-IcingaHashtableItem -Hashtable $OldData -Key $HashIndex -Value @{} | Out-Null;
113-
Add-IcingaHashtableItem -Hashtable $PerfCache -Key $HashIndex -Value @{} | Out-Null;
115+
$SortedResult = $global:Icinga.CheckData[$CheckCommand]['results'][$HashIndex].GetEnumerator() | Sort-Object name -Descending;
116+
Add-IcingaHashtableItem -Hashtable $OldData -Key $HashIndex -Value @{ } | Out-Null;
117+
Add-IcingaHashtableItem -Hashtable $PerfCache -Key $HashIndex -Value @{ } | Out-Null;
114118

115119
foreach ($timeEntry in $SortedResult) {
116120
foreach ($calc in $AverageCalc.Keys) {
@@ -133,7 +137,7 @@ function Start-IcingaServiceCheckTask()
133137
);
134138

135139
Add-IcingaHashtableItem `
136-
-Hashtable $IcingaDaemonData.BackgroundDaemon.ServiceCheckScheduler[$CheckCommand]['average'] `
140+
-Hashtable $global:Icinga.CheckData[$CheckCommand]['average'] `
137141
-Key $MetricName -Value $AverageValue -Override | Out-Null;
138142

139143
$AverageCalc[$calc].Sum = 0;
@@ -144,11 +148,11 @@ function Start-IcingaServiceCheckTask()
144148
# Flush data we no longer require in our cache to free memory
145149
foreach ($entry in $OldData.Keys) {
146150
foreach ($key in $OldData[$entry].Keys) {
147-
Remove-IcingaHashtableItem -Hashtable $IcingaDaemonData.BackgroundDaemon.ServiceCheckScheduler[$CheckCommand]['results'][$entry] -Key $key.Name
151+
Remove-IcingaHashtableItem -Hashtable $global:Icinga.CheckData[$CheckCommand]['results'][$entry] -Key $key.Name;
148152
}
149153
}
150154

151-
Set-IcingaCacheData -Space 'sc_daemon' -CacheStore 'checkresult' -KeyName $CheckCommand -Value $IcingaDaemonData.BackgroundDaemon.ServiceCheckScheduler[$CheckCommand]['average'];
155+
Set-IcingaCacheData -Space 'sc_daemon' -CacheStore 'checkresult' -KeyName $CheckCommand -Value $global:Icinga.CheckData[$CheckCommand]['average'];
152156
# Write collected metrics to disk in case we reload the daemon. We will load them back into the module after reload then
153157
Set-IcingaCacheData -Space 'sc_daemon' -CacheStore 'checkresult_store' -KeyName $CheckCommand -Value $PerfCache;
154158
} catch {

lib/icinga/plugin/New-IcingaCheck.psm1

+7-10
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,19 @@ function New-IcingaCheck()
4949
return;
5050
}
5151

52-
if ($global:IcingaDaemonData.ContainsKey('BackgroundDaemon') -eq $FALSE) {
52+
if ($null -eq $global:Icinga -Or $global:Icinga.ContainsKey('CheckData') -eq $FALSE) {
5353
return;
5454
}
5555

56-
if ($global:IcingaDaemonData.BackgroundDaemon.ContainsKey('ServiceCheckScheduler') -eq $FALSE) {
57-
return;
58-
}
59-
60-
if ($global:IcingaDaemonData.BackgroundDaemon.ServiceCheckScheduler.ContainsKey($this.checkcommand)) {
61-
if ($global:IcingaDaemonData.BackgroundDaemon.ServiceCheckScheduler[$this.checkcommand]['results'].ContainsKey($this.name) -eq $FALSE) {
62-
$global:IcingaDaemonData.BackgroundDaemon.ServiceCheckScheduler[$this.checkcommand]['results'].Add(
56+
if ($global:Icinga.CheckData.ContainsKey($this.checkcommand)) {
57+
if ($global:Icinga.CheckData[$this.checkcommand]['results'].ContainsKey($this.name) -eq $FALSE) {
58+
$global:Icinga.CheckData[$this.checkcommand]['results'].Add(
6359
$this.name,
64-
[hashtable]::Synchronized(@{})
60+
@{ }
6561
);
6662
}
67-
$global:IcingaDaemonData.BackgroundDaemon.ServiceCheckScheduler[$this.checkcommand]['results'][$this.name].Add(
63+
64+
$global:Icinga.CheckData[$this.checkcommand]['results'][$this.name].Add(
6865
(Get-IcingaUnixTime),
6966
$this.value
7067
);

0 commit comments

Comments
 (0)