From 295fa00b12f2a384b27dfdf134454561ae98bba8 Mon Sep 17 00:00:00 2001 From: Lord Hepipud Date: Mon, 19 Aug 2024 16:13:24 +0200 Subject: [PATCH] Reworks handling on how plugin thresholds are interpreted --- doc/100-General/10-Changelog.md | 7 +- .../Get-IcingaCheckSchedulerPerfData.psm1 | 10 +- .../Invoke-IcingaInternalServiceCall.psm1 | 2 +- .../New-IcingaEnvironmentVariable.psm1 | 6 +- .../tools/Convert-IcingaPluginThresholds.psm1 | 202 +++++- .../Convert-IcingaPluginValueToString.psm1 | 86 ++- lib/icinga/enums/Icinga_IcingaEnums.psm1 | 13 + .../Icinga_IcingaExceptionEnums.psm1 | 1 + .../Compare-IcingaPluginThresholds.psm1 | 600 ++++-------------- .../Compare-IcingaPluginValueToThreshold.psm1 | 190 ++++++ ...ert-IcingaPluginThresholdsFromPercent.psm1 | 92 +++ lib/icinga/plugin/New-IcingaCheck.psm1 | 126 ++-- .../plugin/New-IcingaCheckBaseObject.psm1 | 5 - lib/icinga/plugin/New-IcingaCheckPackage.psm1 | 27 - .../plugin/Write-IcingaPluginPerfData.psm1 | 84 +-- .../plugin/Write-IcingaPluginResult.psm1 | 8 +- 16 files changed, 789 insertions(+), 670 deletions(-) create mode 100644 lib/icinga/plugin/Compare-IcingaPluginValueToThreshold.psm1 create mode 100644 lib/icinga/plugin/Convert-IcingaPluginThresholdsFromPercent.psm1 diff --git a/doc/100-General/10-Changelog.md b/doc/100-General/10-Changelog.md index b1579f51..c16c408f 100644 --- a/doc/100-General/10-Changelog.md +++ b/doc/100-General/10-Changelog.md @@ -7,10 +7,14 @@ documentation before upgrading to a new release. Released closed milestones can be found on [GitHub](https://github.com/Icinga/icinga-powershell-framework/milestones?state=closed). -## 1.13.0 (tbd) +## 1.13.0 Beta-1 (2024-08-30) [Issues and PRs](https://github.com/Icinga/icinga-powershell-framework/milestone/32) +### Notes + +This beta release has reworked the entire handling on how thresholds and the checker core operate. For that reason, the `Beta-1` release will **not** include the `Metrics over Time` feature. The goal of this beta is to get an idea if the threshold handling is working as expected and evaluate the performance gains for the new check handling. The `Metrics over Time` feature will be re-implemented with an entire new background task and configurations in `Beta-2`. + ### Bugfixes * [#729](https://github.com/Icinga/icinga-powershell-framework/issues/729) Fixes `Update-Icinga` to print an error in case a component is not installed, instead of silently continue @@ -27,6 +31,7 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic * [#739](https://github.com/Icinga/icinga-powershell-framework/pull/739) Adds support to check the encoding of files to ensure we can properly load them and throw errors for unsupported encoding * [#740](https://github.com/Icinga/icinga-powershell-framework/pull/740) Adds new command `Invoke-IcingaForWindowsRESTApi` for easier API communication * [#742](https://github.com/Icinga/icinga-powershell-framework/pull/742) Adds support for the CPU provider to limit the CPU usage to 100% for each thread +* [#750](https://github.com/Icinga/icinga-powershell-framework/pull/750) Reworks the internal handling on how plugin thresholds are evaluated and the internal checker core, including on how performance metrics are generated ## 1.12.3 (2024-04-24) diff --git a/lib/core/framework/Get-IcingaCheckSchedulerPerfData.psm1 b/lib/core/framework/Get-IcingaCheckSchedulerPerfData.psm1 index a8507e9f..7dba40b6 100644 --- a/lib/core/framework/Get-IcingaCheckSchedulerPerfData.psm1 +++ b/lib/core/framework/Get-IcingaCheckSchedulerPerfData.psm1 @@ -10,15 +10,19 @@ Returns the last performance data output for executed plugins while the Framework is running as daemon .OUTPUTS - System.Object + System.String .LINK https://github.com/Icinga/icinga-powershell-framework #> function Get-IcingaCheckSchedulerPerfData() { - $PerfData = $Global:Icinga.Private.Scheduler.PerformanceData; - [array]$Global:Icinga.Private.Scheduler.PerformanceData = @(); + [string]$PerfData = $Global:Icinga.Private.Scheduler.PerformanceData; + [string]$Global:Icinga.Private.Scheduler.PerformanceData = ''; + + # Ensure we clear our PerfDataWriter cache and storage to have a clean base state for the next plugin execution + $Global:Icinga.Private.Scheduler.PerfDataWriter.Cache.Clear() | Out-Null; + $Global:Icinga.Private.Scheduler.PerfDataWriter.Storage.Clear() | Out-Null; return $PerfData; } diff --git a/lib/core/framework/Invoke-IcingaInternalServiceCall.psm1 b/lib/core/framework/Invoke-IcingaInternalServiceCall.psm1 index 3d99ebcf..8ace3dee 100644 --- a/lib/core/framework/Invoke-IcingaInternalServiceCall.psm1 +++ b/lib/core/framework/Invoke-IcingaInternalServiceCall.psm1 @@ -88,7 +88,7 @@ function Invoke-IcingaInternalServiceCall() $IcingaCR = ($IcingaResult.$Command.checkresult.Replace("`r`n", "`n")); if ($IcingaResult.$Command.perfdata.Count -ne 0) { - $IcingaCR = [string]::Format('{0}{1}| {2}', $IcingaCR, "`r`n", ([string]::Join(' ', $IcingaResult.$Command.perfdata))); + $IcingaCR = [string]::Format('{0}{1}| {2}', $IcingaCR, "`r`n", ([string]::Join('', $IcingaResult.$Command.perfdata))); } if ($NoExit) { diff --git a/lib/core/framework/New-IcingaEnvironmentVariable.psm1 b/lib/core/framework/New-IcingaEnvironmentVariable.psm1 index 80ad5cb6..fe174b4d 100644 --- a/lib/core/framework/New-IcingaEnvironmentVariable.psm1 +++ b/lib/core/framework/New-IcingaEnvironmentVariable.psm1 @@ -40,9 +40,13 @@ function New-IcingaEnvironmentVariable() 'CheckData' = @{ }; 'ThresholdCache' = @{ }; 'CheckResults' = @(); - 'PerformanceData' = @(); + 'PerformanceData' = ''; 'PluginException' = $null; 'ExitCode' = $null; + 'PerfDataWriter' = @{ + 'Cache' = @{}; + 'Storage' = (New-Object System.Text.StringBuilder); + } } ); diff --git a/lib/core/tools/Convert-IcingaPluginThresholds.psm1 b/lib/core/tools/Convert-IcingaPluginThresholds.psm1 index e7124084..76fc7556 100644 --- a/lib/core/tools/Convert-IcingaPluginThresholds.psm1 +++ b/lib/core/tools/Convert-IcingaPluginThresholds.psm1 @@ -14,6 +14,13 @@ to the lowest base of the unit. It does support the Icinga plugin language, like ~:30, @10:40, 15:30, ... + You can also provide date time values in the format of "yyyy/MM/dd HH:mm:ss" + and use Icinga for Windows plugin thresholds in combination. You have to escape + the ':' inside the date time value with a '`' to ensure the correct conversion. + + Example: + 2024/08/19 12`:42`:00 + The conversion does currently support the following units: Size: B, KB, MB, GB, TB, PT, KiB, MiB, GiB, TiB, PiB @@ -26,43 +33,90 @@ Name Value ---- ----- - Value 1728000 + EndRange Unit s + StartRange + Threshold 1728000 + Mode 0 + Raw 20d + IsDateTime False + Value 1728000 .EXAMPLE PS>Convert-IcingaPluginThresholds -Threshold '5GB'; Name Value ---- ----- - Value 5000000000 + EndRange Unit B + StartRange + Threshold 5000000000 + Mode 0 + Raw 5GB + IsDateTime False + Value 5000000000 .EXAMPLE PS>Convert-IcingaPluginThresholds -Threshold '10MB:20MB'; Name Value ---- ----- - Value 10000000:20000000 + EndRange 20000000 Unit B + StartRange 10000000 + Threshold 10000000:20000000 + Mode 3 + Raw 10MB:20MB + IsDateTime False + Value .EXAMPLE PS>Convert-IcingaPluginThresholds -Threshold '10m:1h'; Name Value ---- ----- - Value 600:3600 + EndRange 3600 Unit s + StartRange 600 + Threshold 600:3600 + Mode 3 + Raw 10m:1h + Value .EXAMPLE PS>Convert-IcingaPluginThresholds -Threshold '@10m:1h'; Name Value ---- ----- - Value @600:3600 + EndRange 3600 Unit s + StartRange 600 + Threshold @600:3600 + Mode 4 + Raw @10m:1h + IsDateTime False + Value .EXAMPLE - Convert-IcingaPluginThresholds -Threshold '~:1M'; + PS>Convert-IcingaPluginThresholds -Threshold '~:1M'; Name Value ---- ----- - Value ~:2592000 + EndRange + Unit s + StartRange + Threshold ~:2592000 + Mode 2 + Raw ~:1M + IsDateTime False + Value 2592000 +.EXAMPLE + PS>Convert-IcingaPluginThresholds -Threshold '@2024/08/19 12`:42`:00:2024/08/19 12`:42`:00'; + Name Value + ---- ----- + EndRange 133685377200000000 Unit s + StartRange 133685377200000000 + Threshold @133685377200000000:133685377200000000 + Mode 4 + Raw @2024/08/19 12`:42`:00:2024/08/19 12`:42`:00 + IsDateTime True + Value .INPUTS System.String .OUTPUTS @@ -78,35 +132,80 @@ function Convert-IcingaPluginThresholds() ); [hashtable]$RetValue = @{ - 'Unit' = ''; - 'Value' = $null; + 'Raw' = $Threshold; + 'Unit' = ''; + 'Threshold' = $null; + 'Value' = $null; + 'StartRange' = $null; + 'EndRange' = $null; + 'Mode' = $IcingaEnums.IcingaThresholdMethod.Default; + 'IsDateTime' = $FALSE; }; - if ($null -eq $Threshold) { + if ([string]::IsNullOrEmpty($Threshold)) { return $RetValue; } # Always ensure we are using correct digits - $Threshold = $Threshold.Replace(',', '.'); - - [array]$Content = @(); + $Threshold = $Threshold.Replace(',', '.'); + [array]$Content = @(); if ($Threshold.Contains(':')) { # If we have more than one ':' inside our string, lets check if this is a date time value # In case it is convert it properly to a FileTime we can work with later on - if ([Regex]::Matches($Threshold, ":").Count -gt 1) { + if ([Regex]::Matches($Threshold, '`:').Count -gt 1) { + [bool]$HasTilde = $FALSE; + [bool]$HasAt = $FALSE; + + if ($Threshold.Contains('@')) { + $HasAt = $TRUE; + } elseif ($Threshold.Contains('~')) { + $HasTilde = $TRUE; + } + + $Threshold = $Threshold.Replace('`:', '!').Replace('~', '').Replace('@', ''); + [array]$DatimeValueArray = $Threshold.Split(':'); + try { - $DateTimeValue = [DateTime]::ParseExact($Threshold, 'yyyy\/MM\/dd HH:mm:ss', $null); - $RetValue.Value = $DateTimeValue.ToFileTime(); - $RetValue.Unit = 's'; + [array]$DateTimeValue = @(); + if ([string]::IsNullOrEmpty($DatimeValueArray[0]) -eq $FALSE) { + [array]$DateTimeValue += ([DateTime]::ParseExact($DatimeValueArray[0].Replace('!', ':'), 'yyyy\/MM\/dd HH:mm:ss', $null)).ToFileTime(); + } + if ([string]::IsNullOrEmpty($DatimeValueArray[1]) -eq $FALSE) { + [array]$DateTimeValue += ([DateTime]::ParseExact($DatimeValueArray[1].Replace('!', ':'), 'yyyy\/MM\/dd HH:mm:ss', $null)).ToFileTime(); + } + + if ($DateTimeValue.Count -gt 1) { + $Threshold = [string]::Join(':', $DateTimeValue); + $RetValue.Mode = $IcingaEnums.IcingaThresholdMethod.Between; + + if ($HasAt) { + $Threshold = [string]::Format('@{0}', $Threshold); + } + } elseif ($DatimeValueArray.Count -gt 1) { + if ($HasTilde) { + $Threshold = [string]::Format('~:{0}', $DateTimeValue[0]); + } else { + $Threshold = [string]::Format('{0}:', $DateTimeValue[0]); + } + } else { + $Threshold = $DateTimeValue[0]; + } + $RetValue.Unit = 's'; + $RetValue.IsDateTime = $TRUE; } catch { - $RetValue.Value = $Threshold; + $RetValue.Threshold = $Threshold.Replace('!', '`:'); + Exit-IcingaThrowException -CustomMessage ([string]::Format('Could not convert the provided threshold value {0} to a valid Icinga for Windows range', $RetValue.Raw)) -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Inputs.InvalidThresholdValue -Force; + return $RetValue; } - - return $RetValue; + } else { + $RetValue.Mode = $IcingaEnums.IcingaThresholdMethod.Between; } $Content = $Threshold.Split(':'); + if ($Content.Count -eq 2 -And ([string]::IsNullOrEmpty($Content[1]))) { + $RetValue.Mode = $IcingaEnums.IcingaThresholdMethod.Lower; + } } else { $Content += $Threshold; } @@ -123,10 +222,12 @@ function Convert-IcingaPluginThresholds() if ($ThresholdValue.Contains('~')) { $ThresholdValue = $ThresholdValue.Replace('~', ''); - $HasTilde = $TRUE; + $HasTilde = $TRUE; + $RetValue.Mode = $IcingaEnums.IcingaThresholdMethod.Greater; } elseif ($ThresholdValue.Contains('@')) { - $HasAt = $TRUE; + $HasAt = $TRUE; $ThresholdValue = $ThresholdValue.Replace('@', ''); + $RetValue.Mode = $IcingaEnums.IcingaThresholdMethod.Outside; } if ($ThresholdValue[0] -eq '-' -And $ThresholdValue.Length -ge 1) { @@ -201,16 +302,67 @@ function Convert-IcingaPluginThresholds() [string]$Value = [string]::Join(':', $ConvertedValue); + switch ($RetValue.Mode) { + $IcingaEnums.IcingaThresholdMethod.Default { + $RetValue.Value = $ConvertedValue[0]; + + if ([string]::IsNullOrEmpty($RetValue.Value)) { + Exit-IcingaThrowException -CustomMessage ([string]::Format('Could not convert the provided threshold value {0} to a valid Icinga for Windows range', $RetValue.Raw)) -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Inputs.InvalidThresholdValue -Force; + return $RetValue; + } + break; + }; + $IcingaEnums.IcingaThresholdMethod.Lower { + $RetValue.Value = $ConvertedValue[0]; + + if ([string]::IsNullOrEmpty($RetValue.Value)) { + Exit-IcingaThrowException -CustomMessage ([string]::Format('Could not convert the provided threshold value {0} to a valid Icinga for Windows range', $RetValue.Raw)) -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Inputs.InvalidThresholdValue -Force; + return $RetValue; + } + break; + }; + $IcingaEnums.IcingaThresholdMethod.Greater { + $RetValue.Value = $ConvertedValue[1]; + + if ([string]::IsNullOrEmpty($RetValue.Value)) { + Exit-IcingaThrowException -CustomMessage ([string]::Format('Could not convert the provided threshold value {0} to a valid Icinga for Windows range', $RetValue.Raw)) -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Inputs.InvalidThresholdValue -Force; + return $RetValue; + } + break; + }; + $IcingaEnums.IcingaThresholdMethod.Between { + $RetValue.StartRange = [decimal]$ConvertedValue[0]; + $RetValue.EndRange = [decimal]$ConvertedValue[1]; + + if ([string]::IsNullOrEmpty($RetValue.StartRange) -Or [string]::IsNullOrEmpty($RetValue.EndRange)) { + Exit-IcingaThrowException -CustomMessage ([string]::Format('Could not convert the provided threshold value {0} to a valid Icinga for Windows range', $RetValue.Raw)) -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Inputs.InvalidThresholdValue -Force; + return $RetValue; + } + break; + }; + $IcingaEnums.IcingaThresholdMethod.Outside { + $RetValue.StartRange = [decimal]($ConvertedValue[0].Replace('@', '')); + $RetValue.EndRange = [decimal]$ConvertedValue[1]; + + if ([string]::IsNullOrEmpty($RetValue.StartRange) -Or [string]::IsNullOrEmpty($RetValue.EndRange)) { + Exit-IcingaThrowException -CustomMessage ([string]::Format('Could not convert the provided threshold value {0} to a valid Icinga for Windows range', ($RetValue.Raw))) -ExceptionType 'Input' -ExceptionThrown $IcingaExceptions.Inputs.InvalidThresholdValue -Force; + return $RetValue; + } + break; + }; + } + if ([string]::IsNullOrEmpty($Value) -eq $FALSE -And $Value.Contains(':') -eq $FALSE) { if ((Test-Numeric $Value)) { - $RetValue.Value = [decimal]$Value; + $RetValue.Threshold = [decimal]$Value; + $RetValue.Value = [decimal]$Value; return $RetValue; } } # Always ensure we are using correct digits - $Value = ([string]$Value).Replace(',', '.'); - $RetValue.Value = $Value; + $Value = ([string]$Value).Replace(',', '.'); + $RetValue.Threshold = $Value; return $RetValue; } diff --git a/lib/core/tools/Convert-IcingaPluginValueToString.psm1 b/lib/core/tools/Convert-IcingaPluginValueToString.psm1 index 63d49ecd..b9543eaa 100644 --- a/lib/core/tools/Convert-IcingaPluginValueToString.psm1 +++ b/lib/core/tools/Convert-IcingaPluginValueToString.psm1 @@ -1,12 +1,57 @@ +<# +.SYNOPSIS + Converts an Icinga plugin value to a human-readable string. + +.DESCRIPTION + The Convert-IcingaPluginValueToString function is used to convert an Icinga plugin value to a human-readable string. It supports various units and can handle percentage values. + +.PARAMETER Value + The value to be converted. + +.PARAMETER BaseValue + The base value used for percentage calculations. + +.PARAMETER Unit + The unit of the value. + +.PARAMETER OriginalUnit + The original unit of the value. + +.PARAMETER UsePercent + Specifies whether to treat the value as a percentage. + +.PARAMETER IsThreshold + Specifies whether the value is a threshold. + +.OUTPUTS + System.String + Returns the converted value as a human-readable string. + +.EXAMPLE + Convert-IcingaPluginValueToString -Value 1024 -Unit 'KiB' + Converts the value 1024 with the unit 'KiB' to a human-readable string. + +.EXAMPLE + Convert-IcingaPluginValueToString -Value 50 -BaseValue 100 -UsePercent + Converts the value 50 as a percentage of the base value 100 to a human-readable string. + +.NOTES + This function is part of the Icinga PowerShell Framework module. +#> function Convert-IcingaPluginValueToString() { param ( - $Value, + $Value = $null, + $BaseValue = $null, [string]$Unit = '', - [string]$OriginalUnit = '' + [string]$OriginalUnit = '', + [switch]$UsePercent = $FALSE, + [switch]$IsThreshold = $FALSE ); - $AdjustedValue = $Value; + $AdjustedValue = $Value; + $PercentValue = $null; + $HumanReadableValue = $null; if ([string]::IsNullOrEmpty($OriginalUnit)) { $OriginalUnit = $Unit; @@ -18,25 +63,46 @@ function Convert-IcingaPluginValueToString() $AdjustedValue = $Value; } - if ($Unit -eq '%' -Or [string]::IsNullOrEmpty($Unit)) { + if ($UsePercent -And ($null -eq $BaseValue -Or $BaseValue -eq 0)) { return ([string]::Format('{0}{1}', ([string]$AdjustedValue).Replace(',', '.'), $Unit)); + } elseif ($UsePercent) { + $Unit = $OriginalUnit; + if ($IsThreshold) { + $PercentValue = [math]::Round($Value, 2); + $AdjustedValue = [math]::Round(($BaseValue / 100) * $Value, 2); + } else { + $PercentValue = [math]::Round(($Value / $BaseValue) * 100, 2); + } } switch ($OriginalUnit) { { ($_ -eq "Kbit") -or ($_ -eq "Mbit") -or ($_ -eq "Gbit") -or ($_ -eq "Tbit") -or ($_ -eq "Pbit") -or ($_ -eq "Ebit") -or ($_ -eq "Zbit") -or ($_ -eq "Ybit") } { - $TransferSpeed = Get-IcingaNetworkInterfaceUnits -Value $Value -Unit $Unit; - return ([string]::Format('{0}{1}', $TransferSpeed.LinkSpeed, $TransferSpeed.Unit)).Replace(',', '.'); + $TransferSpeed = Get-IcingaNetworkInterfaceUnits -Value $AdjustedValue -Unit $Unit; + $HumanReadableValue = ([string]::Format('{0}{1}', $TransferSpeed.LinkSpeed, $TransferSpeed.Unit)).Replace(',', '.'); + break; }; { ($_ -eq "B") -or ($_ -eq "KiB") -or ($_ -eq "MiB") -or ($_ -eq "GiB") -or ($_ -eq "TiB") -or ($_ -eq "PiB") -or ($_ -eq "EiB") -or ($_ -eq "ZiB") -or ($_ -eq "YiB") } { - return (ConvertTo-BytesNextUnit -Value $Value -Unit $Unit -Units @('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB')).Replace(',', '.'); + $HumanReadableValue = (ConvertTo-BytesNextUnit -Value $AdjustedValue -Unit $Unit -Units @('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB')).Replace(',', '.'); + break; }; { ($_ -eq "KB") -or ($_ -eq "MB") -or ($_ -eq "GB") -or ($_ -eq "TB") -or ($_ -eq "PB") -or ($_ -eq "EB") -or ($_ -eq "ZB") -or ($_ -eq "YB") } { - return (ConvertTo-BytesNextUnit -Value $Value -Unit $Unit -Units @('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB')).Replace(',', '.'); + $HumanReadableValue = (ConvertTo-BytesNextUnit -Value $AdjustedValue -Unit $Unit -Units @('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB')).Replace(',', '.'); + break; }; 's' { - return (ConvertFrom-TimeSpan -Seconds $AdjustedValue).Replace(',', '.') + $HumanReadableValue = (ConvertFrom-TimeSpan -Seconds $AdjustedValue).Replace(',', '.'); + break; }; } - return ([string]::Format('{0}{1}', ([string]$AdjustedValue).Replace(',', '.'), $Unit)); + if ($null -eq $HumanReadableValue) { + $HumanReadableValue = ([string]::Format('{0}{1}', ([string]$AdjustedValue).Replace(',', '.'), $Unit)); + } + + # In case the value provided is a percentage value, we need to adjust the output so it doesn't make sense to add the percentage value again for this case + if ($UsePercent -And $Unit -ne '%') { + $HumanReadableValue = [string]::Format('{0} ({1}%)', $HumanReadableValue, $PercentValue); + } + + return $HumanReadableValue; } diff --git a/lib/icinga/enums/Icinga_IcingaEnums.psm1 b/lib/icinga/enums/Icinga_IcingaEnums.psm1 index 44a1a1bf..ac9071d4 100644 --- a/lib/icinga/enums/Icinga_IcingaEnums.psm1 +++ b/lib/icinga/enums/Icinga_IcingaEnums.psm1 @@ -25,6 +25,18 @@ 3 = 'Magenta'; }; +[hashtable]$IcingaThresholdMethod = @{ + Default = 0; # 20 + Lower = 1; # 20: + LowerEqual = 2; + Greater = 3; # ~:20 + GreaterEqual = 4; + Between = 5; # 30:40 + Outside = 6; # @20:30 + Matches = 7; + NotMatches = 8; +}; + [hashtable]$IcingaMeasurementUnits = @{ 's' = 'seconds'; 'ms' = 'milliseconds'; @@ -79,6 +91,7 @@ if ($null -eq $IcingaEnums) { [hashtable]$IcingaEnums = @{ IcingaExitCode = $IcingaExitCode; IcingaExitCodeText = $IcingaExitCodeText; + IcingaThresholdMethod = $IcingaThresholdMethod; IcingaExitCodeColor = $IcingaExitCodeColor; IcingaMeasurementUnits = $IcingaMeasurementUnits; #services diff --git a/lib/icinga/exception/Icinga_IcingaExceptionEnums.psm1 b/lib/icinga/exception/Icinga_IcingaExceptionEnums.psm1 index 9687e34a..00f00bcd 100644 --- a/lib/icinga/exception/Icinga_IcingaExceptionEnums.psm1 +++ b/lib/icinga/exception/Icinga_IcingaExceptionEnums.psm1 @@ -33,6 +33,7 @@ MSSQLCredentialHandling = 'The connection to MSSQL was not possible because your login credential was not correct.'; MSSQLCommandMissing = 'Failed to build a SQL query'; RegexError = 'A request was not handled properly because a provided regex could not be interpreted. Please validate your regex and try again. In case you are trying to access a ressource containing [], you will have to escape each symbol by using `. Example: myservice`[`]'; + InvalidThresholdValue = 'A treshold value provided for the plugin was invalid and did not follow the Icinga for Windows threshold guidelines. Available formatting examples are: "20", "20:", "~:20", "20:30", "@20:30".'; }; [hashtable]$Configuration = @{ diff --git a/lib/icinga/plugin/Compare-IcingaPluginThresholds.psm1 b/lib/icinga/plugin/Compare-IcingaPluginThresholds.psm1 index 2d5758f8..234e3b58 100644 --- a/lib/icinga/plugin/Compare-IcingaPluginThresholds.psm1 +++ b/lib/icinga/plugin/Compare-IcingaPluginThresholds.psm1 @@ -1,508 +1,168 @@ -function Compare-IcingaPluginThresholds() -{ - param ( - [string]$Threshold = $null, - $InputValue = $null, - $BaseValue = $null, - [switch]$Matches = $FALSE, - [switch]$NotMatches = $FALSE, - [switch]$DateTime = $FALSE, - [string]$Unit = '', - $ThresholdCache = $null, - [string]$CheckName = '', - [string]$PerfDataLabel = '', - [hashtable]$Translation = @{ }, - $Minium = $null, - $Maximum = $null, - [switch]$IsBetween = $FALSE, - [switch]$IsLowerEqual = $FALSE, - [switch]$IsGreaterEqual = $FALSE, - [string]$TimeInterval = $null - ); +<# +.SYNOPSIS + Compares a value against specified thresholds and returns the result. - # Fix possible numeric value comparison issues - $TestInput = Test-IcingaDecimal $InputValue; - $BaseInput = Test-IcingaDecimal $BaseValue; +.DESCRIPTION + The Compare-IcingaPluginThresholds function compares a given value against specified thresholds and returns an object containing the comparison result. It supports various comparison methods such as Matches, NotMatches, Between, LowerEqual, and GreaterEqual. If an error occurs during the comparison, the function returns an object with the error details. - if ($TestInput.Decimal) { - [decimal]$InputValue = [decimal]$TestInput.Value; - } - if ($BaseInput.Decimal) { - [decimal]$BaseValue = [decimal]$BaseInput.Value; - } +.PARAMETER Threshold + Specifies the threshold value or range to compare against. This can be a single value or a range specified in the format "min:max". If not provided, the function assumes no threshold. - $IcingaThresholds = New-Object -TypeName PSObject; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'Value' -Value $InputValue; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'BaseValue' -Value $BaseValue; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'RawValue' -Value $InputValue; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'Unit' -Value $Unit; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'OriginalUnit' -Value $Unit; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'PerfUnit' -Value $Unit; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'IcingaThreshold' -Value $Threshold; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'RawThreshold' -Value $Threshold; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'CompareValue' -Value $null; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'MinRangeValue' -Value $null; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'MaxRangeValue' -Value $null; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'PercentValue' -Value ''; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'TimeSpan' -Value ''; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'TimeSpanOutput' -Value ''; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'InRange' -Value $TRUE; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'Message' -Value ''; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'Range' -Value ''; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'FullMessage' -Value ( - [string]::Format('{0}', (ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value (Convert-IcingaPluginValueToString -Unit $Unit -Value $InputValue))) - ); - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'HeaderValue' -Value $IcingaThresholds.FullMessage; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'ErrorMessage' -Value ''; - $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'HasError' -Value $FALSE; +.PARAMETER InputValue + Specifies the value to compare against the threshold. - # In case we are using % values, we should set the BaseValue always to 100 - if ($Unit -eq '%' -And $null -eq $BaseValue) { - $BaseValue = 100; - } +.PARAMETER BaseValue + Specifies the base value for percentage calculations. If the unit is set to '%', the base value is used to calculate the percentage. If not provided, the function assumes no base value. - if ([string]::IsNullOrEmpty($TimeInterval) -eq $FALSE -And $null -ne $ThresholdCache) { - $TimeSeconds = ConvertTo-Seconds $TimeInterval; - $IntervalLabelName = (Format-IcingaPerfDataLabel -PerfData $CheckName); - $IntervalMultiLabelName = (Format-IcingaPerfDataLabel -PerfData $CheckName -MultiOutput); +.PARAMETER Matches + Indicates that the comparison should use the Matches method. This method checks if the input value matches the threshold. - if ([string]::IsNullOrEmpty($PerfDataLabel) -eq $FALSE) { - $IntervalLabelName = $PerfDataLabel; - $IntervalMultiLabelName = $PerfDataLabel; - } +.PARAMETER NotMatches + Indicates that the comparison should use the NotMatches method. This method checks if the input value does not match the threshold. - $MinuteInterval = [math]::round(([TimeSpan]::FromSeconds($TimeSeconds)).TotalMinutes, 0); - $CheckPerfDataLabel = [string]::Format('{0}_{1}', $IntervalLabelName, $MinuteInterval); - $MultiPerfDataLabel = [string]::Format('::{0}::Interval{1}', $IntervalMultiLabelName, $TimeSeconds); - [bool]$FoundInterval = $FALSE; +.PARAMETER DateTime + Specifies whether the input value is a DateTime object. If set to $true, the input value is treated as a DateTime object. - if ($null -ne $ThresholdCache.$CheckPerfDataLabel) { - $InputValue = $ThresholdCache.$CheckPerfDataLabel; - $InputValue = [math]::round([decimal]$InputValue, 6); - $IcingaThresholds.TimeSpanOutput = $MinuteInterval; - $IcingaThresholds.TimeSpan = $MinuteInterval; - $FoundInterval = $TRUE; - } - if ($null -ne $ThresholdCache.$MultiPerfDataLabel) { - $InputValue = $ThresholdCache.$MultiPerfDataLabel; - $InputValue = [math]::round([decimal]$InputValue, 6); - $IcingaThresholds.TimeSpanOutput = $MinuteInterval; - $IcingaThresholds.TimeSpan = $TimeSeconds; - $FoundInterval = $TRUE; - } - if ($FoundInterval -eq $FALSE) { - $IcingaThresholds.HasError = $TRUE; - $IcingaThresholds.ErrorMessage = [string]::Format( - 'The provided time interval "{0}" which translates to "{1}m" in your "-ThresholdInterval" argument does not exist', - $TimeInterval, - $MinuteInterval - ); +.PARAMETER Unit + Specifies the unit of measurement for the values. This is used for percentage calculations. If not provided, the function assumes no unit. - return $IcingaThresholds; - } - } <#else { - # The symbol splitting our threshold from the time index value - # Examples: - # @20:40#15m - # ~:40#15m - # 40#15m - $TimeIndexSeparator = '#'; +.PARAMETER ThresholdCache + Specifies a cache object to store threshold values for reuse. - # In case we found a ~ not starting at the beginning, we should load the - # time index values created by our background daemon - # Allows us to specify something like "40:50#15" - if ($Threshold.Contains($TimeIndexSeparator) -And $null -ne $ThresholdCache) { - [int]$LastIndex = $Threshold.LastIndexOf($TimeIndexSeparator); - if ($LastIndex -ne 0) { - $TmpValue = $Threshold; - $Threshold = $TmpValue.Substring(0, $LastIndex); - $TimeIndex = $TmpValue.Substring($LastIndex + 1, $TmpValue.Length - $LastIndex - 1); - $TimeSeconds = ConvertTo-Seconds $TimeIndex; - $MinuteInterval = [math]::round(([TimeSpan]::FromSeconds($TimeSeconds)).TotalMinutes, 0); +.PARAMETER CheckName + Specifies the name of the check being performed. - $CheckPerfDataLabel = [string]::Format('{0}_{1}', (Format-IcingaPerfDataLabel $CheckName), $MinuteInterval); +.PARAMETER PerfDataLabel + Specifies the label for the performance data. - if ($null -ne $ThresholdCache.$CheckPerfDataLabel) { - $InputValue = $ThresholdCache.$CheckPerfDataLabel; - $InputValue = [math]::round([decimal]$InputValue, 6); - $IcingaThresholds.TimeSpan = $MinuteInterval; - } else { - $IcingaThresholds.HasError = $TRUE; - $IcingaThresholds.ErrorMessage = [string]::Format( - 'The provided time interval "{0}{1}" which translates to "{2}m" in your "-ThresholdInterval" argument does not exist', - $TimeIndexSeparator, - $TimeIndex, - $MinuteInterval - ); - } - } - } - }#> +.PARAMETER Translation + Specifies a hashtable for translating threshold values. - [bool]$UseDynamicPercentage = $FALSE; - [hashtable]$ConvertedThreshold = Convert-IcingaPluginThresholds -Threshold $Threshold; - $Minimum = (Convert-IcingaPluginThresholds -Threshold $Minimum).Value; - $Maximum = (Convert-IcingaPluginThresholds -Threshold $Maximum).Value; - [string]$ThresholdValue = $ConvertedThreshold.Value; - $IcingaThresholds.Unit = $ConvertedThreshold.Unit; - $IcingaThresholds.IcingaThreshold = $ThresholdValue; - $TempValue = (Convert-IcingaPluginThresholds -Threshold ([string]::Format('{0}{1}', $InputValue, $Unit))); - $InputValue = $TempValue.Value; - $TmpUnit = $TempValue.Unit; - $TestInput = Test-IcingaDecimal $InputValue; +.PARAMETER Minium + Specifies the minimum threshold value for comparison. This is used when the IsBetween method is used. - if ($TestInput.Decimal) { - [decimal]$InputValue = [decimal]$TestInput.Value; - } +.PARAMETER Maximum + Specifies the maximum threshold value for comparison. This is used when the IsBetween method is used. - $IcingaThresholds.RawValue = $InputValue; - $TempValue = (Convert-IcingaPluginThresholds -Threshold ([string]::Format('{0}{1}', $BaseValue, $Unit))); - $BaseValue = $TempValue.Value; - $Unit = $TmpUnit; - $IcingaThresholds.PerfUnit = $Unit; - $IcingaThresholds.BaseValue = $BaseValue; +.PARAMETER IsBetween + Indicates that the comparison should use the IsBetween method. This method checks if the input value is between the minimum and maximum thresholds. - if ([string]::IsNullOrEmpty($IcingaThresholds.Unit)) { - $IcingaThresholds.Unit = $Unit; - } +.PARAMETER IsLowerEqual + Indicates that the comparison should use the IsLowerEqual method. This method checks if the input value is lower than or equal to the threshold. - # Calculate % value from base value of set - if ([string]::IsNullOrEmpty($BaseValue) -eq $FALSE -And $BaseValue -ne 0 -And $IcingaThresholds.Unit -eq '%') { - $InputValue = $InputValue / $BaseValue * 100; - $UseDynamicPercentage = $TRUE; - } elseif ([string]::IsNullOrEmpty($BaseValue) -eq $TRUE -And $IcingaThresholds.Unit -eq '%') { - $IcingaThresholds.HasError = $TRUE; - $IcingaThresholds.ErrorMessage = 'This argument does not support the % unit'; +.PARAMETER IsGreaterEqual + Indicates that the comparison should use the IsGreaterEqual method. This method checks if the input value is greater than or equal to the threshold. - return $IcingaThresholds; - } +.PARAMETER TimeInterval + Specifies the time interval for the comparison. This is used when the input value is a DateTime object. - # Always override our InputValue, case we might have change it - $IcingaThresholds.Value = $InputValue; +.OUTPUTS + The function returns an object containing the comparison result. The object has the following properties: + - Value: The input value. + - Unit: The unit of measurement. + - Message: The result message of the comparison. + - IsOK: Indicates whether the comparison result is OK. + - HasError: Indicates whether an error occurred during the comparison. + - Threshold: The threshold value or range used for comparison. + - Minimum: The minimum threshold value used for comparison. + - Maximum: The maximum threshold value used for comparison. - # If we simply provide a numeric number, we always check Value > Threshold or Value < 0 - if ($Matches) { - # Checks if the InputValue Matches the Threshold - if ($InputValue -Like $ThresholdValue) { - $IcingaThresholds.InRange = $FALSE; - $IcingaThresholds.Message = 'is matching threshold'; - $IcingaThresholds.Range = [string]::Format( - '{0}', - (ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value (Convert-IcingaPluginValueToString -Unit $IcingaThresholds.Unit -Value $ThresholdValue -OriginalUnit $IcingaThresholds.OriginalUnit)) - ); - } - } elseif ($NotMatches) { - # Checks if the InputValue not Matches the Threshold - if ($InputValue -NotLike $ThresholdValue) { - $IcingaThresholds.InRange = $FALSE; - $IcingaThresholds.Message = 'is not matching threshold'; - $IcingaThresholds.Range = [string]::Format( - '{0}', - (ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value (Convert-IcingaPluginValueToString -Unit $IcingaThresholds.Unit -Value $ThresholdValue -OriginalUnit $IcingaThresholds.OriginalUnit)) - ); - } - } elseif ($DateTime) { - # Checks if the InputValue Is Inside our time value +.EXAMPLE + $threshold = "10:20" + $inputValue = 15 + $baseValue = 100 + $result = Compare-IcingaPluginThresholds -Threshold $threshold -InputValue $inputValue -BaseValue $baseValue + $result.IsOK + # Returns $true - try { - $DateTimeValue = 0; - [decimal]$TimeThreshold = 0; - $CurrentDate = $global:Icinga.CurrentDate; - $IcingaThresholds.Unit = ''; - - if ([string]::IsNullOrEmpty($InputValue) -eq $FALSE) { - $DateTimeValue = [DateTime]::FromFileTime($InputValue); - $IcingaThresholds.Value = $DateTimeValue.ToString('yyyy\/MM\/dd HH:mm:ss'); - } - - if ([string]::IsNullOrEmpty($ThresholdValue) -eq $FALSE) { - $TimeThreshold = (ConvertTo-Seconds -Value $Threshold); - $CurrentDate = $CurrentDate.AddSeconds($TimeThreshold); - $IcingaThresholds.IcingaThreshold = $CurrentDate.ToFileTimeUtc(); - } - - if ([string]::IsNullOrEmpty($ThresholdValue) -eq $FALSE -And ($DateTimeValue -eq 0 -Or $DateTimeValue -lt $CurrentDate)) { - $IcingaThresholds.InRange = $FALSE; - $IcingaThresholds.Message = 'is lower than'; - $IcingaThresholds.Range = [string]::Format( - '{0} ({1}{2})', - ((Get-Date).ToString('yyyy\/MM\/dd HH:mm:ss')), - ( $( if ($TimeThreshold -ge 0) { '+'; } else { ''; } )), - $Threshold - ); - } - } catch { - $IcingaThresholds.ErrorMessage = [string]::Format( - 'Invalid date time specified. Your InputValue "{0}" seems not be a valid date time or your provided Threshold "{1}" cannot be converted to seconds. Exception: {2}', - $InputValue, - $ThresholdValue, - $_.Exception.Message - ); - $IcingaThresholds.HasError = $TRUE; - - return $IcingaThresholds; - } - } elseif ($IsBetween) { - if ($InputValue -gt $Minium -And $InputValue -lt $Maximum) { - $IcingaThresholds.InRange = $FALSE; - $IcingaThresholds.Message = 'is inside range'; - $IcingaThresholds.Range = [string]::Format( - '{0} and {1}', - (ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value (Convert-IcingaPluginValueToString -Unit $IcingaThresholds.Unit -Value $Minium -OriginalUnit $IcingaThresholds.OriginalUnit)), - (ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value (Convert-IcingaPluginValueToString -Unit $IcingaThresholds.Unit -Value $Maximum -OriginalUnit $IcingaThresholds.OriginalUnit)) - ); - } +.NOTES + This function is part of the Icinga PowerShell Framework module. +#> +function Compare-IcingaPluginThresholds() +{ + param ( + [string]$Threshold = $null, + $InputValue = $null, + $BaseValue = $null, + [switch]$Matches = $FALSE, + [switch]$NotMatches = $FALSE, + [switch]$DateTime = $FALSE, + [string]$Unit = '', + $ThresholdCache = $null, + [string]$CheckName = '', + [string]$PerfDataLabel = '', + [hashtable]$Translation = @{ }, + $Minium = $null, + $Maximum = $null, + [switch]$IsBetween = $FALSE, + [switch]$IsLowerEqual = $FALSE, + [switch]$IsGreaterEqual = $FALSE, + [string]$TimeInterval = $null + ); - if ($IcingaThresholds.Unit -eq '%') { - $IcingaThresholds.RawThreshold = [string]::Format( - '{0}% ({2}) {1}% ({3})', - (ConvertFrom-Percent -Value $BaseValue -Percent $Minium), - (ConvertFrom-Percent -Value $BaseValue -Percent $Maximum), - (Convert-IcingaPluginValueToString -Unit $Unit -Value $Minium -OriginalUnit $IcingaThresholds.OriginalUnit), - (Convert-IcingaPluginValueToString -Unit $Unit -Value $Maximum -OriginalUnit $IcingaThresholds.OriginalUnit) - ); - $IcingaThresholds.PercentValue = [string]::Format( - '@{0}:{1}', - (ConvertFrom-Percent -Value $BaseValue -Percent $Minium), - (ConvertFrom-Percent -Value $BaseValue -Percent $Maximum) - ); - } - } elseif ($IsLowerEqual) { - if ($InputValue -le $ThresholdValue) { - $IcingaThresholds.InRange = $FALSE; - $IcingaThresholds.Message = 'is lower equal than threshold'; - $IcingaThresholds.Range = [string]::Format( - '{0}', - (ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value (Convert-IcingaPluginValueToString -Unit $IcingaThresholds.Unit -Value $ThresholdValue -OriginalUnit $IcingaThresholds.OriginalUnit)) - ); - } + try { + # Fix possible numeric value comparison issues + $TestInput = Test-IcingaDecimal $InputValue; + $BaseInput = Test-IcingaDecimal $BaseValue; - if ($IcingaThresholds.Unit -eq '%') { - $IcingaThresholds.RawThreshold = [string]::Format( - '{0}% ({1})', - (ConvertFrom-Percent -Value $BaseValue -Percent $ThresholdValue), - (Convert-IcingaPluginValueToString -Unit $Unit -Value $ThresholdValue -OriginalUnit $IcingaThresholds.OriginalUnit) - ); - $IcingaThresholds.PercentValue = [string]::Format( - '{0}:', - (ConvertFrom-Percent -Value $BaseValue -Percent $ThresholdValue) - ); + if ($TestInput.Decimal) { + [decimal]$InputValue = [decimal]$TestInput.Value; } - } elseif ($IsGreaterEqual) { - if ($InputValue -ge $ThresholdValue) { - $IcingaThresholds.InRange = $FALSE; - $IcingaThresholds.Message = 'is greater equal than threshold'; - $IcingaThresholds.Range = [string]::Format( - '{0}', - (ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value (Convert-IcingaPluginValueToString -Unit $IcingaThresholds.Unit -Value $ThresholdValue -OriginalUnit $IcingaThresholds.OriginalUnit)) - ); + if ($BaseInput.Decimal) { + [decimal]$BaseValue = [decimal]$BaseInput.Value; } - if ($IcingaThresholds.Unit -eq '%') { - $IcingaThresholds.RawThreshold = [string]::Format( - '{0}% ({1})', - (ConvertFrom-Percent -Value $BaseValue -Percent $ThresholdValue), - (Convert-IcingaPluginValueToString -Unit $Unit -Value $ThresholdValue -OriginalUnit $IcingaThresholds.OriginalUnit) - ); + $IcingaThresholds = New-Object -TypeName PSObject; + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'Value' -Value $InputValue; + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'Unit' -Value $Unit; + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'Message' -Value ''; + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'IsOK' -Value $FALSE; + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'HasError' -Value $FALSE; + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'Threshold' -Value (Convert-IcingaPluginThresholds -Threshold $Threshold); + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'Minimum' -Value (Convert-IcingaPluginThresholds -Threshold $Minium); + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'Maximum' -Value (Convert-IcingaPluginThresholds -Threshold $Maximum); - $IcingaThresholds.PercentValue = [string]::Format( - '~:{0}', - (ConvertFrom-Percent -Value $BaseValue -Percent $ThresholdValue) - ); + # In case we are using % values, we should set the BaseValue always to 100 + if ($Unit -eq '%' -And $null -eq $BaseValue) { + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'BaseValue' -Value 100; + } else { + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'BaseValue' -Value $BaseValue; } - } else { - if ((Test-Numeric $ThresholdValue)) { - if ($InputValue -gt $ThresholdValue -Or $InputValue -lt 0) { - $IcingaThresholds.InRange = $FALSE; - $IcingaThresholds.Message = 'is greater than threshold'; - $IcingaThresholds.Range = [string]::Format('{0}', (Convert-IcingaPluginValueToString -Unit $Unit -Value $ThresholdValue -OriginalUnit $IcingaThresholds.OriginalUnit)); - } - - $IcingaThresholds.CompareValue = [decimal]$ThresholdValue; - if ($IcingaThresholds.Unit -eq '%') { - $IcingaThresholds.RawThreshold = [string]::Format('{0}% ({1})', $ThresholdValue, (Convert-IcingaPluginValueToString -Unit $Unit -Value (ConvertFrom-Percent -Value $BaseValue -Percent $ThresholdValue) -OriginalUnit $IcingaThresholds.OriginalUnit)); + $CheckResult = $null; - $IcingaThresholds.PercentValue = [string]::Format( - '{0}', - (ConvertFrom-Percent -Value $BaseValue -Percent $ThresholdValue) - ); - } + if ($Matches) { + $CheckResult = Compare-IcingaPluginValueToThreshold -Value $InputValue -BaseValue $IcingaThresholds.BaseValue -Threshold $IcingaThresholds.Threshold -Unit $Unit -Translation $Translation -OverrideMode $IcingaEnums.IcingaThresholdMethod.Matches; + } elseif ($NotMatches) { + $CheckResult = Compare-IcingaPluginValueToThreshold -Value $InputValue -BaseValue $IcingaThresholds.BaseValue -Threshold $IcingaThresholds.Threshold -Unit $Unit -Translation $Translation -OverrideMode $IcingaEnums.IcingaThresholdMethod.NotMatches; + } elseif ($IsBetween) { + $CheckResult = Compare-IcingaPluginValueToThreshold -Value $InputValue -BaseValue $IcingaThresholds.BaseValue -Threshold $IcingaThresholds.Threshold -Unit $Unit -Translation $Translation -OverrideMode $IcingaEnums.IcingaThresholdMethod.Between; + } elseif ($IsLowerEqual) { + $CheckResult = Compare-IcingaPluginValueToThreshold -Value $InputValue -BaseValue $IcingaThresholds.BaseValue -Threshold $IcingaThresholds.Threshold -Unit $Unit -Translation $Translation -OverrideMode $IcingaEnums.IcingaThresholdMethod.LowerEqual; + } elseif ($IsGreaterEqual) { + $CheckResult = Compare-IcingaPluginValueToThreshold -Value $InputValue -BaseValue $IcingaThresholds.BaseValue -Threshold $IcingaThresholds.Threshold -Unit $Unit -Translation $Translation -OverrideMode $IcingaEnums.IcingaThresholdMethod.GreaterEqual; } else { - # Transform our provided thresholds to split everything into single objects - [array]$thresholds = $ThresholdValue.Split(':'); - [string]$rangeMin = $thresholds[0]; - [string]$rangeMax = $thresholds[1]; - [bool]$IsNegating = $rangeMin.Contains('@'); - [string]$rangeMin = $rangeMin.Replace('@', ''); - - if ((Test-Numeric ($rangeMin.Replace('@', '').Replace('~', '')))) { - $IcingaThresholds.MinRangeValue = [decimal]($rangeMin.Replace('@', '').Replace('~', '')); - [decimal]$rangeMin = [decimal]$rangeMin; - } - if ((Test-Numeric $rangeMax)) { - $IcingaThresholds.MaxRangeValue = [decimal]$rangeMax; - [decimal]$rangeMax = [decimal]$rangeMax; - } - - if ($IsNegating -eq $FALSE -And (Test-Numeric $rangeMin) -And (Test-Numeric $rangeMax)) { - # Handles: 30:40 - # Error on: < 30 or > 40 - # Ok on: between {30 .. 40} - - if ($InputValue -lt $rangeMin -Or $InputValue -gt $rangeMax) { - $IcingaThresholds.InRange = $FALSE; - $IcingaThresholds.Message = 'is outside range'; - $IcingaThresholds.Range = [string]::Format( - '{0} and {1}', - (ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value (Convert-IcingaPluginValueToString -Unit $IcingaThresholds.Unit -Value $rangeMin -OriginalUnit $IcingaThresholds.OriginalUnit)), - (ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value (Convert-IcingaPluginValueToString -Unit $IcingaThresholds.Unit -Value $rangeMax -OriginalUnit $IcingaThresholds.OriginalUnit)) - ); - } - - if ($IcingaThresholds.Unit -eq '%') { - $IcingaThresholds.RawThreshold = [string]::Format( - '{0}% ({2}) and {1}% ({3})', - $rangeMin, - $rangeMax, - (Convert-IcingaPluginValueToString -Unit $Unit -Value (ConvertFrom-Percent -Value $BaseValue -Percent $rangeMin -OriginalUnit $IcingaThresholds.OriginalUnit)), - (Convert-IcingaPluginValueToString -Unit $Unit -Value (ConvertFrom-Percent -Value $BaseValue -Percent $rangeMax -OriginalUnit $IcingaThresholds.OriginalUnit)) - ); - - $IcingaThresholds.PercentValue = [string]::Format( - '{0}:{1}', - (ConvertFrom-Percent -Value $BaseValue -Percent $rangeMin), - (ConvertFrom-Percent -Value $BaseValue -Percent $rangeMax) - ); - } - } elseif ((Test-Numeric $rangeMin) -And [string]::IsNullOrEmpty($rangeMax) -eq $TRUE) { - # Handles: 20: - # Error on: 20: - # Ok on: between 20 .. ∞ - - if ($InputValue -lt $rangeMin) { - $IcingaThresholds.InRange = $FALSE; - $IcingaThresholds.Message = 'is lower than threshold'; - $IcingaThresholds.Range = [string]::Format( - '{0}', - (ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value (Convert-IcingaPluginValueToString -Unit $IcingaThresholds.Unit -Value $rangeMin -OriginalUnit $IcingaThresholds.OriginalUnit)) - ); - } - - if ($IcingaThresholds.Unit -eq '%') { - $IcingaThresholds.RawThreshold = [string]::Format( - '{0}% ({1})', - $rangeMin, - (Convert-IcingaPluginValueToString -Unit $Unit -Value (ConvertFrom-Percent -Value $BaseValue -Percent $rangeMin -OriginalUnit $IcingaThresholds.OriginalUnit)) - ); - - $IcingaThresholds.PercentValue = [string]::Format( - '{0}:', - (ConvertFrom-Percent -Value $BaseValue -Percent $rangeMin) - ); - } - } elseif ($rangeMin -eq '~' -And (Test-Numeric $rangeMax)) { - # Handles: ~:20 - # Error on: > 20 - # Ok on: between -∞ .. 20 - - if ($InputValue -gt $rangeMax) { - $IcingaThresholds.InRange = $FALSE; - $IcingaThresholds.Message = 'is greater than threshold'; - $IcingaThresholds.Range = [string]::Format( - '{0}', - (ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value (Convert-IcingaPluginValueToString -Unit $IcingaThresholds.Unit -Value $rangeMax -OriginalUnit $IcingaThresholds.OriginalUnit)) - ); - } - - if ($IcingaThresholds.Unit -eq '%') { - $IcingaThresholds.RawThreshold = [string]::Format( - '{0}% ({1})', - $rangeMax, - (Convert-IcingaPluginValueToString -Unit $Unit -Value (ConvertFrom-Percent -Value $BaseValue -Percent $rangeMax -OriginalUnit $IcingaThresholds.OriginalUnit)) - ); - - $IcingaThresholds.PercentValue = [string]::Format( - '~:{0}', - (ConvertFrom-Percent -Value $BaseValue -Percent $rangeMax) - ); - } - } elseif ($IsNegating -And (Test-Numeric $rangeMin) -And (Test-Numeric $rangeMax)) { - # Handles: @30:40 - # Error on: ≥ 30 and ≤ 40 - # Ok on: -∞ .. 29 and 41 .. ∞ - - if ($InputValue -ge $rangeMin -And $InputValue -le $rangeMax) { - $IcingaThresholds.InRange = $FALSE; - $IcingaThresholds.Message = 'is inside range'; - $IcingaThresholds.Range = [string]::Format( - '{0} and {1}', - (ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value (Convert-IcingaPluginValueToString -Unit $IcingaThresholds.Unit -Value $rangeMin -OriginalUnit $IcingaThresholds.OriginalUnit)), - (ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value (Convert-IcingaPluginValueToString -Unit $IcingaThresholds.Unit -Value $rangeMax -OriginalUnit $IcingaThresholds.OriginalUnit)) - ); - } - - if ($IcingaThresholds.Unit -eq '%') { - $IcingaThresholds.RawThreshold = [string]::Format( - '{0}% ({2}) {1}% ({3})', - $rangeMin, - $rangeMax, - (Convert-IcingaPluginValueToString -Unit $Unit -Value (ConvertFrom-Percent -Value $BaseValue -Percent $rangeMin -OriginalUnit $IcingaThresholds.OriginalUnit)), - (Convert-IcingaPluginValueToString -Unit $Unit -Value (ConvertFrom-Percent -Value $BaseValue -Percent $rangeMax -OriginalUnit $IcingaThresholds.OriginalUnit)) - ); - - $IcingaThresholds.PercentValue = [string]::Format( - '@{0}:{1}', - (ConvertFrom-Percent -Value $BaseValue -Percent $rangeMin), - (ConvertFrom-Percent -Value $BaseValue -Percent $rangeMax) - ); - } - } else { - if ([string]::IsNullOrEmpty($Threshold) -eq $FALSE) { - # Unhandled - $IcingaThresholds.ErrorMessage = [string]::Format( - 'Invalid range specified for threshold: InputValue "{0}" and Threshold {1}', - $InputValue, - $Threshold - ); - $IcingaThresholds.HasError = $TRUE; - - return $IcingaThresholds; - } - } + $CheckResult = Compare-IcingaPluginValueToThreshold -Value $InputValue -BaseValue $IcingaThresholds.BaseValue -Threshold $IcingaThresholds.Threshold -Unit $Unit -Translation $Translation; } - } - - $PluginOutputMessage = New-Object -TypeName 'System.Text.StringBuilder'; - - [string]$PluginCurrentValue = [string]::Format( - '{0}', - (ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value (Convert-IcingaPluginValueToString -Unit $IcingaThresholds.Unit -Value $IcingaThresholds.Value -OriginalUnit $IcingaThresholds.OriginalUnit)) - ); - - [string]$PluginThresholdValue = $IcingaThresholds.Range; - if ($UseDynamicPercentage -And $Unit -ne '%') { - $IcingaThresholds.IcingaThreshold = $IcingaThresholds.PercentValue; - $PluginCurrentValue = [string]::Format('{0}% ({1})', ([string]([math]::Round($IcingaThresholds.Value, 2))).Replace(',', '.'), (Convert-IcingaPluginValueToString -Unit $Unit -Value $IcingaThresholds.RawValue -OriginalUnit $IcingaThresholds.OriginalUnit)); - $PluginThresholdValue = $IcingaThresholds.RawThreshold; - } + $IcingaThresholds.Message = $CheckResult.Message; + $IcingaThresholds.IsOK = $CheckResult.IsOK; + $IcingaThresholds.HasError = $CheckResult.HasError; - $IcingaThresholds.HeaderValue = $PluginCurrentValue; - $PluginOutputMessage.Append($PluginCurrentValue) | Out-Null; - - if ([string]::IsNullOrEmpty($IcingaThresholds.Message) -eq $FALSE) { - $PluginOutputMessage.Append(' ') | Out-Null; - $PluginOutputMessage.Append($IcingaThresholds.Message.Replace(',', '.')) | Out-Null; + return $IcingaThresholds; + } catch { + $IcingaThresholds = New-Object -TypeName PSObject; + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'Value' -Value $InputValue; + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'Unit' -Value $Unit; + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'Message' -Value $_.Exception.Message; + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'IsOK' -Value $FALSE; + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'HasError' -Value $TRUE; + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'Threshold' -Value $Threshold; + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'Minimum' -Value $Minium; + $IcingaThresholds | Add-Member -MemberType NoteProperty -Name 'Maximum' -Value $Maximum; - if ([string]::IsNullOrEmpty($PluginThresholdValue) -eq $FALSE) { - $PluginOutputMessage.Append(' ') | Out-Null; - $PluginOutputMessage.Append(([string]$PluginThresholdValue).Replace(',', '.')) | Out-Null; - } + return $IcingaThresholds; } - # Lets build our full message for adding on the value - $IcingaThresholds.FullMessage = $PluginOutputMessage.ToString(); - - return $IcingaThresholds; + return $null; } diff --git a/lib/icinga/plugin/Compare-IcingaPluginValueToThreshold.psm1 b/lib/icinga/plugin/Compare-IcingaPluginValueToThreshold.psm1 new file mode 100644 index 00000000..0f29de70 --- /dev/null +++ b/lib/icinga/plugin/Compare-IcingaPluginValueToThreshold.psm1 @@ -0,0 +1,190 @@ +<# +.SYNOPSIS + Compares a value to a threshold and returns a result. + +.DESCRIPTION + The Compare-IcingaPluginValueToThreshold function compares a value to a threshold and returns a result indicating whether the value meets the threshold criteria. It supports various threshold methods such as default, lower, lower equal, greater, greater equal, between, outside, matches, and not matches. The function also handles percentage values and provides human-readable output. + +.PARAMETER Value + The value to compare against the threshold. + +.PARAMETER BaseValue + The base value used for percentage calculations. + +.PARAMETER Unit + The unit of measurement for the value. + +.PARAMETER Translation + The translation table for converting values to human-readable format. + +.PARAMETER Threshold + The threshold object containing the threshold criteria. + +.PARAMETER OverrideMode + The override mode for the threshold. + +.OUTPUTS + A hashtable containing the following properties: + - Message: The result message indicating whether the value meets the threshold criteria. + - IsOk: A boolean value indicating whether the value meets the threshold criteria. + - HasError: A boolean value indicating whether an error occurred during the comparison. + +.EXAMPLE + $threshold = @{ + EndRange = $null; + Unit = 'B'; + StartRange = $null; + Threshold = '30000000MB'; + Mode = 0; + Raw = '30MB'; + IsDateTime = $FALSE; + Value = 30000000; + } + Compare-IcingaPluginValueToThreshold -Value 450000000 -Unit 'B' -Threshold $threshold + + This example compares the value 15 to the threshold criteria specified in the $threshold object. The function returns a hashtable with the result message, IsOk, and HasError properties. + +.NOTES + This function is part of the Icinga PowerShell Framework module. + +.LINK + https://github.com/icinga/icinga-powershell-framework + +#> +function Compare-IcingaPluginValueToThreshold() +{ + param ( + $Value = $null, + $BaseValue = $null, + $Unit = $null, + $Translation = $null, + $Threshold = $null, + $OverrideMode = $null + ); + + [hashtable]$RetValue = @{ + 'Message' = ''; + 'IsOk' = $FALSE; + 'HasError' = $FALSE; + } + $HumanReadableValue = $Value; + $PercentValue = $null; + $TranslatedValue = $Value; + [bool]$UsePercent = $FALSE; + + # Otherwise just convert the values to human readble values + $HumanReadableValue = ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value $HumanReadableValue; + $HumanReadableValue = Convert-IcingaPluginValueToString -Value $HumanReadableValue -Unit $Unit; + + if ($null -eq $Value -Or $null -eq $Threshold -Or [string]::IsNullOrEmpty($Threshold.Raw)) { + $RetValue.Message = $HumanReadableValue; + $RetValue.IsOk = $TRUE; + + return $RetValue; + } + + if (Test-Numeric $Value) { + [decimal]$Value = $Value; + } + + if ($null -eq $OverrideMode) { + $OverrideMode = $Threshold.Mode; + } + + if ($Threshold.Unit -eq '%' -And $null -eq $BaseValue) { + $RetValue.Message = 'This plugin threshold does not support percentage units'; + $RetValue.HasError = $TRUE; + + return $RetValue; + } + + # In case we have a percentage value, we need to adjust the value + if ($Threshold.Unit -eq '%' -And $null -ne $BaseValue -And $BaseValue -ne 0) { + $UsePercent = $TRUE; + $HumanReadableValue = Convert-IcingaPluginValueToString -Value $Value -BaseValue $BaseValue -Unit $Threshold.Unit -OriginalUnit $Unit -UsePercent:$UsePercent; + $Value = [math]::Round(($Value / $BaseValue) * 100, 2); + # Ensure that we properly set the threshold range or metrics we defined from percent to acutal values based on the BaseValue + # Otherwise performance metrics will not be properly reported, causing later issues for visualiation tools like Grafana + $Threshold = Convert-IcingaPluginThresholdsFromPercent -BaseValue $BaseValue -Threshold $Threshold; + } else { + $TranslatedValue = ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value $Value; + } + + switch ($OverrideMode) { + $IcingaEnums.IcingaThresholdMethod.Default { + if ($Value -lt 0 -Or $Value -gt $Threshold.Value) { + if ($Value -lt 0) { + $RetValue.Message = [string]::Format('Value {0} is lower than 0', $HumanReadableValue); + return $RetValue; + } + + if ($Value -gt $Threshold.Value) { + $RetValue.Message = [string]::Format('Value {0} is greater than threshold {1}', $HumanReadableValue, (Convert-IcingaPluginValueToString -Value $Threshold.Value -BaseValue $BaseValue -Unit $Threshold.Unit -OriginalUnit $Unit -UsePercent:$UsePercent -IsThreshold)); + return $RetValue; + } + } + break; + }; + $IcingaEnums.IcingaThresholdMethod.Lower { + if ($Value -lt $Threshold.Value) { + $RetValue.Message = [string]::Format('Value {0} is lower than threshold {1}', $HumanReadableValue, (Convert-IcingaPluginValueToString -Value $Threshold.Value -BaseValue $BaseValue -Unit $Threshold.Unit -OriginalUnit $Unit -UsePercent:$UsePercent -IsThreshold)); + return $RetValue; + } + break; + }; + $IcingaEnums.IcingaThresholdMethod.LowerEqual { + if ($Value -le $Threshold.Value) { + $RetValue.Message = [string]::Format('Value {0} is lower or equal than threshold {1}', $HumanReadableValue, (Convert-IcingaPluginValueToString -Value $Threshold.Value -BaseValue $BaseValue -Unit $Threshold.Unit -OriginalUnit $Unit -UsePercent:$UsePercent -IsThreshold)); + return $RetValue; + } + break; + }; + $IcingaEnums.IcingaThresholdMethod.Greater { + if ($Value -gt $Threshold.Value) { + $RetValue.Message = [string]::Format('Value {0} is greater than threshold {1}', $HumanReadableValue, (Convert-IcingaPluginValueToString -Value $Threshold.Value -BaseValue $BaseValue -Unit $Threshold.Unit -OriginalUnit $Unit -UsePercent:$UsePercent -IsThreshold)); + return $RetValue; + } + break; + }; + $IcingaEnums.IcingaThresholdMethod.GreaterEqual { + if ($Value -gt $Threshold.Value) { + $RetValue.Message = [string]::Format('Value {0} is greater or equal than threshold {1}', $HumanReadableValue, (Convert-IcingaPluginValueToString -Value $Threshold.Value -BaseValue $BaseValue -Unit $Threshold.Unit -OriginalUnit $Unit -UsePercent:$UsePercent -IsThreshold)); + return $RetValue; + } + break; + }; + $IcingaEnums.IcingaThresholdMethod.Between { + if ($Value -lt $Threshold.StartRange -Or $Value -gt $Threshold.EndRange) { + $RetValue.Message = [string]::Format('Value {0} is not between thresholds <{1} or >{2}', $HumanReadableValue, (Convert-IcingaPluginValueToString -Value $Threshold.StartRange -BaseValue $BaseValue -Unit $Threshold.Unit -OriginalUnit $Unit -UsePercent:$UsePercent -IsThreshold), (Convert-IcingaPluginValueToString -Value $Threshold.EndRange -BaseValue $BaseValue -Unit $Threshold.Unit -OriginalUnit $Unit -UsePercent:$UsePercent -IsThreshold)); + return $RetValue; + } + break; + }; + $IcingaEnums.IcingaThresholdMethod.Outside { + if ($Value -ge $Threshold.StartRange -And $Value -le $Threshold.EndRange) { + $RetValue.Message = [string]::Format('Value {0} is between thresholds >={1} and <={2}', $HumanReadableValue, (Convert-IcingaPluginValueToString -Value $Threshold.StartRange -BaseValue $BaseValue -Unit $Threshold.Unit -OriginalUnit $Unit -UsePercent:$UsePercent -IsThreshold), (Convert-IcingaPluginValueToString -Value $Threshold.EndRange -BaseValue $BaseValue -Unit $Threshold.Unit -OriginalUnit $Unit -UsePercent:$UsePercent -IsThreshold)); + return $RetValue; + } + break; + }; + $IcingaEnums.IcingaThresholdMethod.Matches { + if ($Value -Like $Threshold.Value ) { + $RetValue.Message = [string]::Format('Value {0} is matching threshold {1}', $TranslatedValue, (ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value $Threshold.Value)); + return $RetValue; + } + break; + }; + $IcingaEnums.IcingaThresholdMethod.NotMatches { + if ($Value -NotLike $Threshold.Value ) { + $RetValue.Message = [string]::Format('Value {0} is not matching threshold {1}', $TranslatedValue, (ConvertTo-IcingaPluginOutputTranslation -Translation $Translation -Value $Threshold.Value)); + return $RetValue; + } + break; + }; + } + + $RetValue.Message = $HumanReadableValue; + $RetValue.IsOk = $TRUE; + + return $RetValue; +} diff --git a/lib/icinga/plugin/Convert-IcingaPluginThresholdsFromPercent.psm1 b/lib/icinga/plugin/Convert-IcingaPluginThresholdsFromPercent.psm1 new file mode 100644 index 00000000..df4eef74 --- /dev/null +++ b/lib/icinga/plugin/Convert-IcingaPluginThresholdsFromPercent.psm1 @@ -0,0 +1,92 @@ +<# +.SYNOPSIS + Converts Icinga plugin thresholds from percentage to actual values. + +.DESCRIPTION + The Convert-IcingaPluginThresholdsFromPercent function takes a base value and a threshold object and converts the threshold values from percentage to actual values. It supports various threshold modes such as Default, Lower, LowerEqual, Greater, GreaterEqual, Between, Outside, Matches, and NotMatches. + +.PARAMETER BaseValue + The base value used for calculating the threshold values. + +.PARAMETER Threshold + The threshold object containing the threshold mode and value. + +.OUTPUTS + The modified threshold object with the converted threshold values. + +.EXAMPLE + $baseValue = 274112400000 + $threshold = @{ + EndRange = 90; + Unit = '%'; + StartRange = 30; + Threshold = '@30:90'; + Mode = 6; + Raw = '@30%:90%'; + IsDateTime = $FALSE; + Value = $null; + } + + Convert-IcingaPluginThresholdsFromPercent -BaseValue $baseValue -Threshold $threshold + + This example converts the threshold value of 50% to the actual value based on the provided base value of 100. + +.NOTES + +#> +function Convert-IcingaPluginThresholdsFromPercent() +{ + param ( + $BaseValue = $null, + $Threshold = $null + ); + + if ($null -eq $Threshold -Or $null -eq $BaseValue -Or $BaseValue -eq 0) { + return $Threshold; + } + + switch ($Threshold.Mode) { + $IcingaEnums.IcingaThresholdMethod.Default { + $Threshold.Threshold = [math]::Round($BaseValue / 100 * $Threshold.Value, 0); + break; + }; + $IcingaEnums.IcingaThresholdMethod.Lower { + $Threshold.Threshold = [math]::Round($BaseValue / 100 * $Threshold.Value, 0); + break; + }; + $IcingaEnums.IcingaThresholdMethod.LowerEqual { + $Threshold.Threshold = [math]::Round($BaseValue / 100 * $Threshold.Value, 0); + break; + }; + $IcingaEnums.IcingaThresholdMethod.Greater { + $Threshold.Threshold = [math]::Round($BaseValue / 100 * $Threshold.Value, 0); + break; + }; + $IcingaEnums.IcingaThresholdMethod.GreaterEqual { + $Threshold.Threshold = [math]::Round($BaseValue / 100 * $Threshold.Value, 0); + break; + }; + $IcingaEnums.IcingaThresholdMethod.Between { + $StartRange = [math]::Round($BaseValue / 100 * $Threshold.StartRange, 0); + $EndRange = [math]::Round($BaseValue / 100 * $Threshold.EndRange, 0); + + $Threshold.Threshold = [string]::Format('{0}:{1}', $StartRange, $EndRange); + break; + }; + $IcingaEnums.IcingaThresholdMethod.Outside { + $StartRange = [math]::Round($BaseValue / 100 * $Threshold.StartRange, 0); + $EndRange = [math]::Round($BaseValue / 100 * $Threshold.EndRange, 0); + + $Threshold.Threshold = [string]::Format('@{0}:{1}', $StartRange, $EndRange); + break; + }; + $IcingaEnums.IcingaThresholdMethod.Matches { + break; + }; + $IcingaEnums.IcingaThresholdMethod.NotMatches { + break; + }; + } + + return $Threshold; +} diff --git a/lib/icinga/plugin/New-IcingaCheck.psm1 b/lib/icinga/plugin/New-IcingaCheck.psm1 index 9a2a475a..53c24c82 100644 --- a/lib/icinga/plugin/New-IcingaCheck.psm1 +++ b/lib/icinga/plugin/New-IcingaCheck.psm1 @@ -119,19 +119,19 @@ function New-IcingaCheck() $PluginThresholds = ''; $TimeSpan = ''; - $PluginThresholds = $this.__ThresholdObject.FullMessage; + $PluginThresholds = $this.__ThresholdObject.Message; if ([string]::IsNullOrEmpty($PluginOutput) -eq $FALSE) { $PluginThresholds = $PluginOutput; } - if ($null -ne $this.__ThresholdObject -And [string]::IsNullOrEmpty($this.__ThresholdObject.TimeSpan) -eq $FALSE) { + <#if ($null -ne $this.__ThresholdObject -And [string]::IsNullOrEmpty($this.__ThresholdObject.TimeSpan) -eq $FALSE) { $TimeSpan = [string]::Format( '{0}({1}m avg.)', (&{ if ([string]::IsNullOrEmpty($PluginThresholds)) { return ''; } else { return ' ' } }), $this.__ThresholdObject.TimeSpanOutput ); - } + }#> [bool]$AddColon = $TRUE; @@ -173,10 +173,10 @@ function New-IcingaCheck() $TimeSpan = $TimeSpanLabel.Replace($Label, '').Replace('_', '').Replace('::Interval', '').Replace('::', ''); if ($null -ne $this.__WarningValue -And [string]::IsNullOrEmpty($this.__WarningValue.TimeSpan) -eq $FALSE -And $this.__WarningValue.TimeSpan -eq $TimeSpan) { - $TimeSpans.Warning = $this.__WarningValue.IcingaThreshold; + $TimeSpans.Warning = $this.__WarningValue.Threshold.Threshold; } if ($null -ne $this.__CriticalValue -And [string]::IsNullOrEmpty($this.__CriticalValue.TimeSpan) -eq $FALSE -And $this.__CriticalValue.TimeSpan -eq $TimeSpan) { - $TimeSpans.Critical = $this.__CriticalValue.IcingaThreshold; + $TimeSpans.Critical = $this.__CriticalValue.Threshold.Threshold; } $TimeSpans.Interval = $TimeSpan; @@ -198,17 +198,17 @@ function New-IcingaCheck() [string]$LabelName = (Format-IcingaPerfDataLabel -PerfData $this.Name); [string]$MultiLabelName = (Format-IcingaPerfDataLabel -PerfData $this.Name -MultiOutput); - $value = ConvertTo-Integer -Value $this.__ThresholdObject.RawValue -NullAsEmpty; + $value = ConvertTo-Integer -Value $this.__ThresholdObject.Value -NullAsEmpty; $warning = ''; $critical = ''; # Set our threshold to nothing if we use time spans, as it would cause performance metrics to # contain warning/critical values for everything, which is not correct if ([string]::IsNullOrEmpty($this.__WarningValue.TimeSpan)) { - $warning = ConvertTo-Integer -Value $this.__WarningValue.IcingaThreshold -NullAsEmpty; + $warning = ConvertTo-Integer -Value $this.__WarningValue.Threshold.Threshold -NullAsEmpty; } if ([string]::IsNullOrEmpty($this.__CriticalValue.TimeSpan)) { - $critical = ConvertTo-Integer -Value $this.__CriticalValue.IcingaThreshold -NullAsEmpty; + $critical = ConvertTo-Integer -Value $this.__CriticalValue.Threshold.Threshold -NullAsEmpty; } if ([string]::IsNullOrEmpty($this.LabelName) -eq $FALSE) { @@ -226,7 +226,7 @@ function New-IcingaCheck() } if ([string]::IsNullOrEmpty($this.Maximum) -eq $FALSE -And (Test-Numeric $this.Maximum) -And (Test-Numeric $this.Value) -And $this.Value -gt $this.Maximum) { - $this.Maximum = $this.__ThresholdObject.RawValue; + $this.Maximum = $this.__ThresholdObject.Value; } } @@ -236,21 +236,37 @@ function New-IcingaCheck() $PerfDataTemplate = $this.MetricTemplate; } - $this.__CheckPerfData = @{ - 'index' = $this.MetricIndex; - 'name' = $this.MetricName; - 'template' = $PerfDataTemplate; - 'label' = $LabelName; - 'multilabel' = $MultiLabelName; - 'perfdata' = ''; - 'unit' = $this.__ThresholdObject.PerfUnit; - 'value' = (Format-IcingaPerfDataValue $value); - 'warning' = (Format-IcingaPerfDataValue $warning); - 'critical' = (Format-IcingaPerfDataValue $critical); - 'minimum' = (Format-IcingaPerfDataValue $this.Minimum); - 'maximum' = (Format-IcingaPerfDataValue $this.Maximum); - 'package' = $FALSE; - }; + [string]$PerfDataName = [string]::Format( + '{0}::ifw_{1}::{2}', + $this.MetricIndex, + $PerfDataTemplate.ToLower(), + $this.MetricName + ); + + # Ensure we only add a label with identical name once + if ($Global:Icinga.Private.Scheduler.PerfDataWriter.Cache.ContainsKey($PerfDataName) -eq $FALSE) { + $Global:Icinga.Private.Scheduler.PerfDataWriter.Cache.Add($PerfDataName, $TRUE); + } else { + return; + } + + [string]$PerfDataLabel = [string]::Format( + '{0}={1}{2};{3};{4};{5};{6}', + $PerfDataName, + (Format-IcingaPerfDataValue $value), + $this.__ThresholdObject.PerfUnit, + (Format-IcingaPerfDataValue $warning), + (Format-IcingaPerfDataValue $critical), + (Format-IcingaPerfDataValue $this.Minimum), + (Format-IcingaPerfDataValue $this.Maximum) + ); + + # Add a space before adding another metric + if ($Global:Icinga.Private.Scheduler.PerfDataWriter.Storage.Length -ne 0) { + $Global:Icinga.Private.Scheduler.PerfDataWriter.Storage.Append(' ') | Out-Null; + } + + $Global:Icinga.Private.Scheduler.PerfDataWriter.Storage.Append($PerfDataLabel.ToLower()) | Out-Null; } $IcingaCheck | Add-Member -MemberType ScriptMethod -Name '__ValidateObject' -Value { @@ -417,7 +433,7 @@ function New-IcingaCheck() if ($ThresholdObject.HasError) { $this.SetUnknown() | Out-Null; $this.__ThresholdObject = $ThresholdObject; - $this.__SetCheckOutput($this.__ThresholdObject.ErrorMessage); + $this.__SetCheckOutput($this.__ThresholdObject.Message); $this.__LockState(); return; } @@ -431,7 +447,7 @@ function New-IcingaCheck() $this.__ThresholdObject = $ThresholdObject; } - if ($ThresholdObject.InRange -eq $FALSE) { + if ($ThresholdObject.IsOk -eq $FALSE) { if ($this.__CheckState -lt $State) { $this.__CheckState = $State; $this.__ThresholdObject = $ThresholdObject; @@ -951,35 +967,53 @@ function New-IcingaCheck() [bool]$OutOfRange = $FALSE; - #Handles 20 - if ($null -ne $this.__WarningValue.CompareValue -And $null -ne $this.__CriticalValue.CompareValue) { - if ($this.__WarningValue.CompareValue -gt $this.__CriticalValue.CompareValue) { - $OutOfRange = $TRUE; + # Both thresholds use the mode + if ($this.__WarningValue.Threshold.Mode -eq $this.__CriticalValue.Threshold.Mode) { + + #Handles 20 + if ($this.__WarningValue.Threshold.Mode -eq $IcingaEnums.IcingaThresholdMethod.Default -And $null -ne $this.__WarningValue.Threshold.Value -And $null -ne $this.__CriticalValue.Threshold.Value) { + if ($this.__WarningValue.Threshold.Value -gt $this.__CriticalValue.Threshold.Value) { + $OutOfRange = $TRUE; + } } - } - # Handles: @30:40 and 30:40 - # Never throw an "error" here, as these ranges can be dynamic - if ($null -ne $this.__WarningValue.MinRangeValue -And $null -ne $this.__CriticalValue.MinRangeValue -And $null -ne $this.__WarningValue.MaxRangeValue -And $null -ne $this.__CriticalValue.MaxRangeValue) { - return; - } + # Handles: 30:40 + if ($this.__WarningValue.Threshold.Mode -eq $IcingaEnums.IcingaThresholdMethod.Between) { + if ($this.__WarningValue.Threshold.StartRange -lt $this.__CriticalValue.Threshold.StartRange) { + $OutOfRange = $TRUE; + } + if ($this.__WarningValue.Threshold.EndRange -gt $this.__CriticalValue.Threshold.EndRange) { + $OutOfRange = $TRUE; + } + # Handles: @30:40 + } elseif ($this.__WarningValue.Threshold.Mode -eq $IcingaEnums.IcingaThresholdMethod.Outside) { + if ($this.__WarningValue.Threshold.StartRange -ge $this.__CriticalValue.Threshold.StartRange) { + $OutOfRange = $TRUE; + } + if ($this.__WarningValue.Threshold.EndRange -le $this.__CriticalValue.Threshold.EndRange) { + $OutOfRange = $TRUE; + } + } - # Handles: 20: - if ($null -ne $this.__WarningValue.MinRangeValue -And $null -ne $this.__CriticalValue.MinRangeValue -And $null -eq $this.__WarningValue.MaxRangeValue -And $null -eq $this.__CriticalValue.MaxRangeValue) { - if ($this.__WarningValue.MinRangeValue -lt $this.__CriticalValue.MinRangeValue) { - $OutOfRange = $TRUE; + # Handles: 20: + if ($this.__WarningValue.Threshold.Mode -eq $IcingaEnums.IcingaThresholdMethod.Lower -And $null -ne $this.__WarningValue.Threshold.Value -And $null -ne $this.__CriticalValue.Threshold.Value) { + if ($this.__WarningValue.Threshold.Value -lt $this.__CriticalValue.Threshold.Value) { + $OutOfRange = $TRUE; + } } - } - # Handles: ~:20 - if ($null -eq $this.__WarningValue.MinRangeValue -And $null -eq $this.__CriticalValue.MinRangeValue -And $null -ne $this.__WarningValue.MaxRangeValue -And $null -ne $this.__CriticalValue.MaxRangeValue) { - if ($this.__WarningValue.MaxRangeValue -gt $this.__CriticalValue.MaxRangeValue) { - $OutOfRange = $TRUE; + # Handles: ~:20 + if ($this.__WarningValue.Threshold.Mode -eq $IcingaEnums.IcingaThresholdMethod.Greater -And $null -ne $this.__WarningValue.Threshold.Value -And $null -ne $this.__CriticalValue.Threshold.Value) { + if ($this.__WarningValue.Threshold.Value -gt $this.__CriticalValue.Threshold.Value) { + $OutOfRange = $TRUE; + } } + } else { + # Todo: Implement handling for mixed modes } if ($OutOfRange) { - $this.SetUnknown([string]::Format('Warning threshold range "{0}" is greater than Critical threshold range "{1}"', $this.__WarningValue.RawThreshold, $this.__CriticalValue.RawThreshold), $TRUE) | Out-Null; + $this.SetUnknown([string]::Format('Warning threshold range "{0}" is greater than Critical threshold range "{1}"', $this.__WarningValue.Threshold.Threshold, $this.__CriticalValue.Threshold.Threshold), $TRUE) | Out-Null; } } diff --git a/lib/icinga/plugin/New-IcingaCheckBaseObject.psm1 b/lib/icinga/plugin/New-IcingaCheckBaseObject.psm1 index ad854a7b..ca8f3005 100644 --- a/lib/icinga/plugin/New-IcingaCheckBaseObject.psm1 +++ b/lib/icinga/plugin/New-IcingaCheckBaseObject.psm1 @@ -4,7 +4,6 @@ function New-IcingaCheckBaseObject() $IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name 'Name' -Value ''; $IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name 'Verbose' -Value 0; - $IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__CheckPerfData' -Value @{ }; $IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__Hidden' -Value $FALSE; $IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__SkipSummary' -Value $FALSE; $IcingaCheckBaseObject | Add-Member -MemberType NoteProperty -Name '__Parent' -Value $IcingaCheckBaseObject; @@ -183,10 +182,6 @@ function New-IcingaCheckBaseObject() return $TRUE; } - $IcingaCheckBaseObject | Add-Member -MemberType ScriptMethod -Name '__GetPerformanceData' -Value { - return $this.__CheckPerfData; - } - $IcingaCheckBaseObject | Add-Member -MemberType ScriptMethod -Name '__ValidateThresholdInput' -Value { # Shared function } diff --git a/lib/icinga/plugin/New-IcingaCheckPackage.psm1 b/lib/icinga/plugin/New-IcingaCheckPackage.psm1 index 9e5e8df6..7ed03228 100644 --- a/lib/icinga/plugin/New-IcingaCheckPackage.psm1 +++ b/lib/icinga/plugin/New-IcingaCheckPackage.psm1 @@ -333,33 +333,6 @@ function New-IcingaCheckPackage() return ''; } - # Override default behaviour from shared function - $IcingaCheckPackage | Add-Member -MemberType ScriptMethod -Force -Name '__GetPerformanceData' -Value { - [string]$perfData = ''; - [hashtable]$CollectedPerfData = @{ }; - - # At first lets collect all perf data, but ensure we only add possible label duplication only once - foreach ($check in $this.__Checks) { - $data = $check.__GetPerformanceData(); - - if ($null -eq $data -Or $null -eq $data.label) { - continue; - } - - if ($CollectedPerfData.ContainsKey($data.label)) { - continue; - } - - $CollectedPerfData.Add($data.label, $data); - } - - return @{ - 'label' = $this.Name; - 'perfdata' = $CollectedPerfData; - 'package' = $TRUE; - } - } - # __GetTimeSpanThreshold(0, 'Core_30_20', 'Core_30') $IcingaCheckPackage | Add-Member -MemberType ScriptMethod -Force -Name '__GetTimeSpanThreshold' -Value { param ($TimeSpanLabel, $Label, $MultiOutput); diff --git a/lib/icinga/plugin/Write-IcingaPluginPerfData.psm1 b/lib/icinga/plugin/Write-IcingaPluginPerfData.psm1 index 26ce2749..c2f2f017 100644 --- a/lib/icinga/plugin/Write-IcingaPluginPerfData.psm1 +++ b/lib/icinga/plugin/Write-IcingaPluginPerfData.psm1 @@ -1,84 +1,14 @@ function Write-IcingaPluginPerfData() { - param ( - $IcingaCheck = $null - ); - - if ($null -eq $IcingaCheck) { - return; - } - - $PerformanceData = $IcingaCheck.__GetPerformanceData(); - $CheckCommand = $IcingaCheck.__GetCheckCommand(); - - if ($PerformanceData.package -eq $FALSE) { - $PerformanceData = @{ - $PerformanceData.label = $PerformanceData; - } - } else { - $PerformanceData = $PerformanceData.perfdata; - } - - if ([string]::IsNullOrEmpty($CheckCommand) -eq $FALSE -And $Global:Icinga.Private.Scheduler.ThresholdCache.ContainsKey($CheckCommand)) { - $CheckResultCache = $Global:Icinga.Private.Scheduler.ThresholdCache[$CheckCommand]; - } else { - $CheckResultCache = New-Object PSCustomObject; - } - - Get-IcingaPluginPerfDataContent -PerfData $PerformanceData -CheckResultCache $CheckResultCache -IcingaCheck $IcingaCheck; - if ($Global:Icinga.Protected.RunAsDaemon -eq $FALSE -And $Global:Icinga.Protected.JEAContext -eq $FALSE) { - if ($Global:Icinga.Private.Scheduler.PerformanceData.Count -ne 0) { - Write-IcingaConsolePlain ([string]::Format('| {0}', ([string]::Join(' ', $Global:Icinga.Private.Scheduler.PerformanceData)))); + if ($Global:Icinga.Private.Scheduler.PerfDataWriter.Storage.Length -ne 0) { + Write-IcingaConsolePlain ([string]::Format('| {0}', ($Global:Icinga.Private.Scheduler.PerfDataWriter.Storage.ToString()))); } - } -} - -function Get-IcingaPluginPerfDataContent() -{ - param ( - $PerfData, - $CheckResultCache, - $IcingaCheck = $null - ); - - [hashtable]$CompiledPerfData = @{ }; - - foreach ($package in $PerfData.Keys) { - $data = $PerfData[$package]; - if ($data.package) { - (Get-IcingaPluginPerfDataContent -PerfData $data.perfdata -CheckResultCache $CheckResultCache -IcingaCheck $IcingaCheck); - } else { - $CompiledPerfData = New-IcingaPerformanceDataEntry -PerfDataObject $data -PerfData $CompiledPerfData; - - foreach ($checkresult in $CheckResultCache.PSobject.Properties) { - - $SearchPattern = [string]::Format('{0}_', $data.label); - $SearchPatternMulti = [string]::Format('::{0}::Interval', $data.multilabel); - $SearchEntry = $checkresult.Name; - - if ($SearchEntry -like "$SearchPatternMulti*") { - $TimeSpan = $IcingaCheck.__GetTimeSpanThreshold($SearchEntry, $data.multilabel, $TRUE); - $CompiledPerfData = New-IcingaPerformanceDataEntry -PerfDataObject $data -Label $SearchEntry -Value $checkresult.Value -Warning $TimeSpan.Warning -Critical $TimeSpan.Critical -Interval $TimeSpan.Interval -PerfData $CompiledPerfData; - } - } - } - } - - foreach ($entry in $CompiledPerfData.Keys) { - $entryValue = $CompiledPerfData[$entry]; - [string]$PerfDataLabel = $entryValue.Index; - - if ($entryValue.Values.Count -ne 0) { - [string]$PerfDataLabel = [string]::Format('{0} {1}', $entryValue.Index, ([string]::Join(' ', ($entryValue.Values | Sort-Object)))); - } - - [array]$global:Icinga.Private.Scheduler.PerformanceData += $PerfDataLabel; + } else { + $Global:Icinga.Private.Scheduler.PerformanceData = $Global:Icinga.Private.Scheduler.PerfDataWriter.Storage.ToString(); } - [array]$global:Icinga.Private.Scheduler.PerformanceData = $Global:Icinga.Private.Scheduler.PerformanceData | Sort-Object @{ - expression = { $_.Substring(1, $_.IndexOf('::') - 1) -As [int] } - }; + # Ensure we clear our cache after writing the data + $Global:Icinga.Private.Scheduler.PerfDataWriter.Cache.Clear() | Out-Null; + $Global:Icinga.Private.Scheduler.PerfDataWriter.Storage.Clear() | Out-Null; } - -Export-ModuleMember -Function @( 'Write-IcingaPluginPerfData' ); diff --git a/lib/icinga/plugin/Write-IcingaPluginResult.psm1 b/lib/icinga/plugin/Write-IcingaPluginResult.psm1 index b39835b1..55331ed5 100644 --- a/lib/icinga/plugin/Write-IcingaPluginResult.psm1 +++ b/lib/icinga/plugin/Write-IcingaPluginResult.psm1 @@ -1,14 +1,14 @@ function Write-IcingaPluginResult() { param ( - [string]$PluginOutput = '', - [array]$PluginPerfData = @() + [string]$PluginOutput = '', + [string]$PluginPerfData = '' ); [string]$CheckResult = $PluginOutput; - if ($PluginPerfData.Count -ne 0) { - $CheckResult = [string]::Format('{0}{1}| {2}', $CheckResult, "`r`n", ([string]::Join(' ', $PluginPerfData))); + if ([string]::IsNullOrEmpty($PluginPerfData) -eq $FALSE) { + $CheckResult = [string]::Format('{0}{1}| {2}', $CheckResult, "`r`n", $PluginPerfData); } Write-IcingaConsolePlain $CheckResult;