diff --git a/doc/100-General/10-Changelog.md b/doc/100-General/10-Changelog.md index 304b99c..b9ec3de 100644 --- a/doc/100-General/10-Changelog.md +++ b/doc/100-General/10-Changelog.md @@ -24,6 +24,7 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic ### Enhancements * [#40](https://github.com/Icinga/icinga-powershell-framework/issues/40) Adds support to set service recovery for the Icinga Agent and Icinga for Windows service, to restart them in case of a crash or error +* [#485](https://github.com/Icinga/icinga-powershell-framework/issues/485) Adds new style for performance data labels, to use the multi output format, allowing for better filtering and visualisation with InfluxDB and Grafana * [#525](https://github.com/Icinga/icinga-powershell-framework/pull/525) Adds new developer mode for `icinga` command and improved cache handling, to ensure within `-DeveloperMode` and inside a VS Code environment, the framework cache file is never overwritten, while still all functions are loaded and imported. * [#531](https://github.com/Icinga/icinga-powershell-framework/pull/531) Adds `Test-IcingaStateFile` and `Repair-IcingaStateFile`, which is integrated into `Test-IcingaAgent`, to ensure the Icinga Agent state file is healthy and not corrupt, causing the Icinga Agent to fail on start * [#534](https://github.com/Icinga/icinga-powershell-framework/pull/534) Improves Icinga and Director configuration generator, by wrapping PowerShell arrays inside `@()` instead of simply writing them comma separated diff --git a/lib/core/framework/Get-IcingaCheckSchedulerPerfData.psm1 b/lib/core/framework/Get-IcingaCheckSchedulerPerfData.psm1 index c522df1..a8507e9 100644 --- a/lib/core/framework/Get-IcingaCheckSchedulerPerfData.psm1 +++ b/lib/core/framework/Get-IcingaCheckSchedulerPerfData.psm1 @@ -17,8 +17,8 @@ function Get-IcingaCheckSchedulerPerfData() { - $PerfData = $Global:Icinga.Private.Scheduler.PerformanceData; - $Global:Icinga.Private.Scheduler.PerformanceData = @(); + $PerfData = $Global:Icinga.Private.Scheduler.PerformanceData; + [array]$Global:Icinga.Private.Scheduler.PerformanceData = @(); return $PerfData; } diff --git a/lib/core/framework/Get-IcingaCheckSchedulerPluginOutput.psm1 b/lib/core/framework/Get-IcingaCheckSchedulerPluginOutput.psm1 index 0d7945f..ef73ec0 100644 --- a/lib/core/framework/Get-IcingaCheckSchedulerPluginOutput.psm1 +++ b/lib/core/framework/Get-IcingaCheckSchedulerPluginOutput.psm1 @@ -17,8 +17,12 @@ function Get-IcingaCheckSchedulerPluginOutput() { - $CheckResult = [string]::Join("`r`n", $Global:Icinga.Private.Scheduler.CheckResults); - $Global:Icinga.Private.Scheduler.CheckResults = @(); + if ($Global:Icinga.Private.Scheduler.CheckResults.Count -eq 0) { + return @(); + } + + $CheckResult = [string]::Join("`r`n", $Global:Icinga.Private.Scheduler.CheckResults); + [array]$Global:Icinga.Private.Scheduler.CheckResults = @(); return $CheckResult; } diff --git a/lib/core/framework/Invoke-IcingaForWindowsMigration.psm1 b/lib/core/framework/Invoke-IcingaForWindowsMigration.psm1 index 13abbbf..d9749ee 100644 --- a/lib/core/framework/Invoke-IcingaForWindowsMigration.psm1 +++ b/lib/core/framework/Invoke-IcingaForWindowsMigration.psm1 @@ -39,4 +39,41 @@ function Invoke-IcingaForWindowsMigration() Restart-IcingaService -Service 'icingapowershell'; } } + + if (Test-IcingaForWindowsMigration -MigrationVersion (New-IcingaVersionObject -Version '1.10.0')) { + Write-IcingaConsoleNotice 'Applying pending migrations required for Icinga for Windows v1.10.0'; + + $ServiceStatus = (Get-Service 'icingapowershell' -ErrorAction SilentlyContinue).Status; + + if ($ServiceStatus -eq 'Running') { + Stop-IcingaWindowsService; + } + + # Convert the time intervals for the background daemon services from the previous index handling + # 1, 3, 5, 15 as example to 1m, 3m, 5m, 15m + $BackgroundServices = Get-IcingaPowerShellConfig -Path 'BackgroundDaemon.RegisteredServices'; + [hashtable]$Output = @{ }; + + foreach ($service in $BackgroundServices.PSObject.Properties) { + [array]$ConvertedTimeIndex = @(); + + foreach ($interval in $service.Value.TimeIndexes) { + if (Test-Numeric $interval) { + $ConvertedTimeIndex += [string]::Format('{0}m', $interval); + } else { + $ConvertedTimeIndex = $interval; + } + } + + $service.Value.TimeIndexes = $ConvertedTimeIndex; + } + + Set-IcingaPowerShellConfig -Path 'BackgroundDaemon.RegisteredServices' -Value $BackgroundServices; + + Set-IcingaForWindowsMigration -MigrationVersion (New-IcingaVersionObject -Version '1.10.0'); + + if ($ServiceStatus -eq 'Running') { + Restart-IcingaWindowsService -Service 'icingapowershell'; + } + } } diff --git a/lib/core/framework/Invoke-IcingaInternalServiceCall.psm1 b/lib/core/framework/Invoke-IcingaInternalServiceCall.psm1 index f83edbc..de6423e 100644 --- a/lib/core/framework/Invoke-IcingaInternalServiceCall.psm1 +++ b/lib/core/framework/Invoke-IcingaInternalServiceCall.psm1 @@ -76,10 +76,7 @@ function Invoke-IcingaInternalServiceCall() $IcingaCR = ($IcingaResult.$Command.checkresult.Replace("`r`n", "`n")); if ($IcingaResult.$Command.perfdata.Count -ne 0) { - $IcingaCR += ' | '; - foreach ($perfdata in $IcingaResult.$Command.perfdata) { - $IcingaCR += $perfdata; - } + $IcingaCR = [string]::Format('{0}{1}| {2}', $IcingaCR, "`r`n", ([string]::Join(' ', $IcingaResult.$Command.perfdata))); } if ($NoExit) { diff --git a/lib/core/perfcounter/Get-IcingaPerformanceCounterDetails.psm1 b/lib/core/perfcounter/Get-IcingaPerformanceCounterDetails.psm1 new file mode 100644 index 0000000..bb5fb45 --- /dev/null +++ b/lib/core/perfcounter/Get-IcingaPerformanceCounterDetails.psm1 @@ -0,0 +1,45 @@ +function Get-IcingaPerformanceCounterDetails() +{ + param ( + [string]$Counter = $null + ); + + [hashtable]$RetValue = @{ + 'RawCounter' = $Counter; + 'HasValue' = $TRUE; + 'HasInstance' = $FALSE; + 'Category' = ''; + 'Instance' = ''; + 'Counter' = ''; + 'CounterInstance' = ''; + } + + if ([string]::IsNullOrEmpty($Counter)) { + $RetValue.HasValue = $FALSE; + + return $RetValue; + } + + [array]$CounterElements = $Counter.Split('\'); + [string]$Instance = ''; + [string]$Category = ''; + [bool]$HasInstance = $FALSE; + + if ($CounterElements[1].Contains('(') -And $CounterElements[1].Contains(')')) { + $HasInstance = $TRUE; + [int]$StartIndex = $CounterElements[1].IndexOf('(') + 1; + [int]$EndIndex = $CounterElements[1].Length - $StartIndex - 1; + $Instance = $CounterElements[1].Substring($StartIndex, $EndIndex); + $RetValue.HasInstance = $HasInstance; + $Category = $CounterElements[1].Substring(0, $CounterElements[1].IndexOf('(')); + $RetValue.CounterInstance = [string]::Format('{0}_{1}', $Instance, $CounterElements[2]); + } else { + $Category = $CounterElements[1]; + } + + $RetValue.Category = $Category; + $RetValue.Instance = $Instance; + $RetValue.Counter = $CounterElements[2]; + + return $RetValue; +} diff --git a/lib/core/tools/ConvertTo-IcingaNumericTimeIndex.psm1 b/lib/core/tools/ConvertTo-IcingaNumericTimeIndex.psm1 new file mode 100644 index 0000000..0d75002 --- /dev/null +++ b/lib/core/tools/ConvertTo-IcingaNumericTimeIndex.psm1 @@ -0,0 +1,24 @@ +function ConvertTo-IcingaNumericTimeIndex() +{ + param ( + [int]$TimeValue = 0 + ); + + if ($TimeValue -lt 60) { + return ([string]::Format('{0}s', $TimeValue)); + } + + [decimal]$Minutes = $TimeValue / 60; + [decimal]$Seconds = $Minutes - [math]::Truncate($Minutes); + [decimal]$Minutes = [math]::Truncate($Minutes); + [decimal]$Seconds = [math]::Round(60 * $Seconds, 0); + + $TimeIndex = New-Object -TypeName 'System.Text.StringBuilder'; + $TimeIndex.Append([string]::Format('{0}m', $Minutes)) | Out-Null; + + if ($Seconds -ne 0) { + $TimeIndex.Append([string]::Format('{0}s', $Seconds)) | Out-Null; + } + + return $TimeIndex.ToString(); +} diff --git a/lib/core/tools/Format-IcingaPerfDataLabel.psm1 b/lib/core/tools/Format-IcingaPerfDataLabel.psm1 index c0bd663..08636af 100644 --- a/lib/core/tools/Format-IcingaPerfDataLabel.psm1 +++ b/lib/core/tools/Format-IcingaPerfDataLabel.psm1 @@ -1,14 +1,20 @@ function Format-IcingaPerfDataLabel() { param( - $PerfData + $PerfData, + [switch]$MultiOutput = $FALSE ); + if ($MultiOutput) { + return (($PerfData) -Replace '[\W]', ''); + } + $Output = ((($PerfData) -Replace ' ', '_') -Replace '[\W]', ''); while ($Output.Contains('__')) { $Output = $Output.Replace('__', '_'); } + # Remove all special characters and spaces on label names return $Output; } diff --git a/lib/daemons/ServiceCheckDaemon/task/Add-IcingaServiceCheckTask.psm1 b/lib/daemons/ServiceCheckDaemon/task/Add-IcingaServiceCheckTask.psm1 index 6b218b8..50573bf 100644 --- a/lib/daemons/ServiceCheckDaemon/task/Add-IcingaServiceCheckTask.psm1 +++ b/lib/daemons/ServiceCheckDaemon/task/Add-IcingaServiceCheckTask.psm1 @@ -17,8 +17,10 @@ function Add-IcingaServiceCheckTask() # Read our check result store data from disk for this service check Read-IcingaCheckResultStore -CheckCommand $CheckCommand; + [int]$CheckInterval = ConvertTo-Seconds $Interval; + while ($TRUE) { - if ($Global:Icinga.Private.Daemons.ServiceCheck.PassedTime -lt $Interval) { + if ($Global:Icinga.Private.Daemons.ServiceCheck.PassedTime -lt $CheckInterval) { $Global:Icinga.Private.Daemons.ServiceCheck.PassedTime += 1; Start-Sleep -Seconds 1; @@ -75,12 +77,9 @@ function Add-IcingaServiceCheckTask() foreach ($calc in $Global:Icinga.Private.Daemons.ServiceCheck.AverageCalculation.Keys) { if ($Global:Icinga.Private.Daemons.ServiceCheck.AverageCalculation[$calc].Count -ne 0) { - $AverageValue = ($Global:Icinga.Private.Daemons.ServiceCheck.AverageCalculation[$calc].Sum / $Global:Icinga.Private.Daemons.ServiceCheck.AverageCalculation[$calc].Count); - [string]$MetricName = Format-IcingaPerfDataLabel ( - [string]::Format('{0}_{1}', $HashIndex, $Global:Icinga.Private.Daemons.ServiceCheck.AverageCalculation[$calc].Interval) - ); - - $Global:Icinga.Private.Scheduler.CheckData[$CheckCommand]['average'] | Add-Member -MemberType NoteProperty -Name $MetricName -Value $AverageValue -Force; + $AverageValue = ($Global:Icinga.Private.Daemons.ServiceCheck.AverageCalculation[$calc].Sum / $Global:Icinga.Private.Daemons.ServiceCheck.AverageCalculation[$calc].Count); + [string]$MetricMultiName = [string]::Format('::{0}::Interval{1}', (Format-IcingaPerfDataLabel -PerfData $HashIndex -MultiOutput), $Global:Icinga.Private.Daemons.ServiceCheck.AverageCalculation[$calc].Time); + $Global:Icinga.Private.Scheduler.CheckData[$CheckCommand]['average'] | Add-Member -MemberType NoteProperty -Name $MetricMultiName -Value $AverageValue -Force; } $Global:Icinga.Private.Daemons.ServiceCheck.AverageCalculation[$calc].Sum = 0; diff --git a/lib/daemons/ServiceCheckDaemon/tools/New-IcingaServiceCheckDaemonEnvironment.psm1 b/lib/daemons/ServiceCheckDaemon/tools/New-IcingaServiceCheckDaemonEnvironment.psm1 index 51d6818..fce6668 100644 --- a/lib/daemons/ServiceCheckDaemon/tools/New-IcingaServiceCheckDaemonEnvironment.psm1 +++ b/lib/daemons/ServiceCheckDaemon/tools/New-IcingaServiceCheckDaemonEnvironment.psm1 @@ -26,7 +26,7 @@ function New-IcingaServiceCheckDaemonEnvironment() foreach ($index in $TimeIndexes) { # Only allow numeric index values - if ((Test-Numeric $index) -eq $FALSE) { + if ((Test-Numeric (ConvertTo-Seconds $index)) -eq $FALSE) { Write-IcingaEventMessage -EventId 1450 -Namespace 'Framework' -Objects $CheckCommand, ($Arguments | Out-String), ($TimeIndexes | Out-String), $index; continue; } @@ -34,15 +34,16 @@ function New-IcingaServiceCheckDaemonEnvironment() $Global:Icinga.Private.Daemons.ServiceCheck.AverageCalculation.Add( [string]$index, @{ - 'Interval' = ([int]$index); - 'Time' = ([int]$index * 60); - 'Sum' = 0; - 'Count' = 0; + 'Interval' = ([int]($index -Replace '[^0-9]', '')); + 'RawInterval' = $index; + 'Time' = (ConvertTo-Seconds $index); + 'Sum' = 0; + 'Count' = 0; } ); } - if ($Global:Icinga.Private.Daemons.ServiceCheck.MaxTime -le [int]$index) { - $Global:Icinga.Private.Daemons.ServiceCheck.MaxTime = [int]$index; + if ($Global:Icinga.Private.Daemons.ServiceCheck.MaxTime -le (ConvertTo-Seconds $index)) { + $Global:Icinga.Private.Daemons.ServiceCheck.MaxTime = (ConvertTo-Seconds $index); } } diff --git a/lib/icinga/plugin/Compare-IcingaPluginThresholds.psm1 b/lib/icinga/plugin/Compare-IcingaPluginThresholds.psm1 index c7de343..820ad30 100644 --- a/lib/icinga/plugin/Compare-IcingaPluginThresholds.psm1 +++ b/lib/icinga/plugin/Compare-IcingaPluginThresholds.psm1 @@ -10,6 +10,7 @@ function Compare-IcingaPluginThresholds() [string]$Unit = '', $ThresholdCache = $null, [string]$CheckName = '', + [string]$PerfDataLabel = '', [hashtable]$Translation = @{ }, $Minium = $null, $Maximum = $null, @@ -44,6 +45,7 @@ function Compare-IcingaPluginThresholds() $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 ''; @@ -60,15 +62,35 @@ function Compare-IcingaPluginThresholds() } if ([string]::IsNullOrEmpty($TimeInterval) -eq $FALSE -And $null -ne $ThresholdCache) { - $TimeSeconds = ConvertTo-Seconds $TimeInterval; - $MinuteInterval = [math]::round(([TimeSpan]::FromSeconds($TimeSeconds)).TotalMinutes, 0); - $CheckPerfDataLabel = [string]::Format('{0}_{1}', (Format-IcingaPerfDataLabel $CheckName), $MinuteInterval); + $TimeSeconds = ConvertTo-Seconds $TimeInterval; + $IntervalLabelName = (Format-IcingaPerfDataLabel -PerfData $CheckName); + $IntervalMultiLabelName = (Format-IcingaPerfDataLabel -PerfData $CheckName -MultiOutput); + + if ([string]::IsNullOrEmpty($PerfDataLabel) -eq $FALSE) { + $IntervalLabelName = $PerfDataLabel; + $IntervalMultiLabelName = $PerfDataLabel; + } + + $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; if ($null -ne $ThresholdCache.$CheckPerfDataLabel) { - $InputValue = $ThresholdCache.$CheckPerfDataLabel; - $InputValue = [math]::round([decimal]$InputValue, 6); - $IcingaThresholds.TimeSpan = $MinuteInterval; - } else { + $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', diff --git a/lib/icinga/plugin/New-IcingaCheck.psm1 b/lib/icinga/plugin/New-IcingaCheck.psm1 index c330483..f516d11 100644 --- a/lib/icinga/plugin/New-IcingaCheck.psm1 +++ b/lib/icinga/plugin/New-IcingaCheck.psm1 @@ -5,6 +5,9 @@ function New-IcingaCheck() $Value = $null, $BaseValue = $null, $Unit = '', + $MetricIndex = 'default', + $MetricName = '', + $MetricTemplate = '', [string]$Minimum = '', [string]$Maximum = '', $ObjectExists = -1, @@ -21,6 +24,9 @@ function New-IcingaCheck() $IcingaCheck | Add-Member -MemberType NoteProperty -Name 'Value' -Value $Value; $IcingaCheck | Add-Member -MemberType NoteProperty -Name 'BaseValue' -Value $BaseValue; $IcingaCheck | Add-Member -MemberType NoteProperty -Name 'Unit' -Value $Unit; + $IcingaCheck | Add-Member -MemberType NoteProperty -Name 'MetricIndex' -Value $MetricIndex; + $IcingaCheck | Add-Member -MemberType NoteProperty -Name 'MetricName' -Value $MetricName; + $IcingaCheck | Add-Member -MemberType NoteProperty -Name 'MetricTemplate' -Value $MetricTemplate; $IcingaCheck | Add-Member -MemberType NoteProperty -Name 'Minimum' -Value $Minimum; $IcingaCheck | Add-Member -MemberType NoteProperty -Name 'Maximum' -Value $Maximum; $IcingaCheck | Add-Member -MemberType NoteProperty -Name 'ObjectExists' -Value $ObjectExists; @@ -114,7 +120,7 @@ function New-IcingaCheck() $TimeSpan = [string]::Format( '{0}({1}m avg.)', (&{ if ([string]::IsNullOrEmpty($PluginThresholds)) { return ''; } else { return ' ' } }), - $this.__ThresholdObject.TimeSpan + $this.__ThresholdObject.TimeSpanOutput ); } @@ -138,14 +144,15 @@ function New-IcingaCheck() # __GetTimeSpanThreshold(0, 'Core_30_20', 'Core_30') $IcingaCheck | Add-Member -MemberType ScriptMethod -Force -Name '__GetTimeSpanThreshold' -Value { - param ($TimeSpanLabel, $Label); + param ($TimeSpanLabel, $Label, $MultiOutput); [hashtable]$TimeSpans = @{ 'Warning' = ''; 'Critical' = ''; + 'Interval' = ''; } - [string]$LabelName = (Format-IcingaPerfDataLabel $this.Name); + [string]$LabelName = (Format-IcingaPerfDataLabel -PerfData $this.Name -MultiOutput:$MultiOutput); if ([string]::IsNullOrEmpty($this.LabelName) -eq $FALSE) { $LabelName = $this.LabelName; } @@ -154,7 +161,7 @@ function New-IcingaCheck() return $TimeSpans; } - $TimeSpan = $TimeSpanLabel.Replace($Label, '').Replace('_', ''); + $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; @@ -162,6 +169,7 @@ function New-IcingaCheck() 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.Interval = $TimeSpan; return $TimeSpans; } @@ -179,10 +187,11 @@ function New-IcingaCheck() return; } - [string]$LabelName = (Format-IcingaPerfDataLabel $this.Name); - $value = ConvertTo-Integer -Value $this.__ThresholdObject.RawValue -NullAsEmpty; - $warning = ''; - $critical = ''; + [string]$LabelName = (Format-IcingaPerfDataLabel -PerfData $this.Name); + [string]$MultiLabelName = (Format-IcingaPerfDataLabel -PerfData $this.Name -MultiOutput); + $value = ConvertTo-Integer -Value $this.__ThresholdObject.RawValue -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 @@ -194,7 +203,8 @@ function New-IcingaCheck() } if ([string]::IsNullOrEmpty($this.LabelName) -eq $FALSE) { - $LabelName = $this.LabelName; + $LabelName = $this.LabelName; + $MultiLabelName = $this.LabelName; } if ([string]::IsNullOrEmpty($this.Minimum) -And [string]::IsNullOrEmpty($this.Maximum)) { @@ -211,16 +221,26 @@ function New-IcingaCheck() } } + $PerfDataTemplate = ($this.__CheckCommand.Replace('Invoke-IcingaCheck', '')); + + if ([string]::IsNullOrEmpty($this.MetricTemplate) -eq $FALSE) { + $PerfDataTemplate = $this.MetricTemplate; + } + $this.__CheckPerfData = @{ - 'label' = $LabelName; - '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; + '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; }; } @@ -256,6 +276,20 @@ function New-IcingaCheck() } } + $IcingaCheck | Add-Member -MemberType ScriptMethod -Name '__ValidateMetricsName' -Value { + if ([string]::IsNullOrEmpty($this.MetricIndex)) { + Write-IcingaConsoleError -Message 'The metric index has no default value for the check object "{0}"' -Objects $this.Name; + } else { + $this.MetricIndex = Format-IcingaPerfDataLabel -PerfData $this.MetricIndex -MultiOutput; + } + + if ([string]::IsNullOrEmpty($this.MetricName)) { + $this.MetricName = Format-IcingaPerfDataLabel -PerfData $this.Name -MultiOutput; + } else { + $this.MetricName = Format-IcingaPerfDataLabel -PerfData $this.MetricName -MultiOutput; + } + } + $IcingaCheck | Add-Member -MemberType ScriptMethod -Name '__ConvertMinMax' -Value { if ([string]::IsNullOrEmpty($this.Unit) -eq $FALSE) { if ([string]::IsNullOrEmpty($this.Minimum) -eq $FALSE) { @@ -298,9 +332,14 @@ function New-IcingaCheck() # Fix possible error for identical time stamps due to internal exceptions # and check execution within the same time slot because of this [string]$TimeIndex = Get-IcingaUnixTime; + [string]$LabelName = $this.Name; - Add-IcingaHashtableItem -Hashtable $global:Icinga.Private.Scheduler.CheckData[$this.__CheckCommand]['results'] -Key $this.Name -Value @{ } | Out-Null; - Add-IcingaHashtableItem -Hashtable $global:Icinga.Private.Scheduler.CheckData[$this.__CheckCommand]['results'][$this.Name] -Key $TimeIndex -Value $this.Value -Override | Out-Null; + if ([string]::IsNullOrEmpty($this.LabelName) -eq $FALSE) { + $LabelName = $this.LabelName; + } + + Add-IcingaHashtableItem -Hashtable $global:Icinga.Private.Scheduler.CheckData[$this.__CheckCommand]['results'] -Key $LabelName -Value @{ } | Out-Null; + Add-IcingaHashtableItem -Hashtable $global:Icinga.Private.Scheduler.CheckData[$this.__CheckCommand]['results'][$LabelName] -Key $TimeIndex -Value $this.Value -Override | Out-Null; } $IcingaCheck | Add-Member -MemberType ScriptMethod -Name 'SetOk' -Value { @@ -399,6 +438,7 @@ function New-IcingaCheck() '-BaseValue' = $this.BaseValue; '-Unit' = $this.Unit; '-CheckName' = $this.__GetName(); + '-PerfDataLabel' = $this.LabelName; '-ThresholdCache' = (Get-IcingaThresholdCache -CheckCommand $this.__CheckCommand); '-Translation' = $this.Translation; '-TimeInterval' = $this.__TimeInterval; @@ -936,6 +976,7 @@ function New-IcingaCheck() $IcingaCheck.__ValidateObject(); $IcingaCheck.__ValidateUnit(); + $IcingaCheck.__ValidateMetricsName(); $IcingaCheck.__SetCurrentExecutionTime(); $IcingaCheck.__AddCheckDataToCache(); $IcingaCheck.__SetInternalTimeInterval(); diff --git a/lib/icinga/plugin/New-IcingaCheckPackage.psm1 b/lib/icinga/plugin/New-IcingaCheckPackage.psm1 index 6fdc64a..f31d483 100644 --- a/lib/icinga/plugin/New-IcingaCheckPackage.psm1 +++ b/lib/icinga/plugin/New-IcingaCheckPackage.psm1 @@ -362,12 +362,12 @@ function New-IcingaCheckPackage() # __GetTimeSpanThreshold(0, 'Core_30_20', 'Core_30') $IcingaCheckPackage | Add-Member -MemberType ScriptMethod -Force -Name '__GetTimeSpanThreshold' -Value { - param ($TimeSpanLabel, $Label); + param ($TimeSpanLabel, $Label, $MultiOutput); foreach ($check in $this.__Checks) { - $Result = $check.__GetTimeSpanThreshold($TimeSpanLabel, $Label); + $Result = $check.__GetTimeSpanThreshold($TimeSpanLabel, $Label, $MultiOutput); - if ([string]::IsNullOrEmpty($Result) -eq $FALSE) { + if ([string]::IsNullOrEmpty($Result.Interval) -eq $FALSE) { return $Result; } } @@ -375,6 +375,7 @@ function New-IcingaCheckPackage() return @{ 'Warning' = ''; 'Critical' = ''; + 'Interval' = ''; }; } diff --git a/lib/icinga/plugin/New-IcingaCheckResult.psm1 b/lib/icinga/plugin/New-IcingaCheckResult.psm1 index fe6d18c..ec9e293 100644 --- a/lib/icinga/plugin/New-IcingaCheckResult.psm1 +++ b/lib/icinga/plugin/New-IcingaCheckResult.psm1 @@ -11,6 +11,10 @@ function New-IcingaCheckResult() $IcingaCheckResult | Add-Member -MemberType NoteProperty -Name 'NoPerfData' -Value $NoPerfData; $IcingaCheckResult | Add-Member -MemberType ScriptMethod -Name 'Compile' -Value { + # Always ensure our cache is cleared before compiling new check data + Get-IcingaCheckSchedulerPluginOutput | Out-Null; + Get-IcingaCheckSchedulerPerfData | Out-Null; + if ($null -eq $this.Check) { return $IcingaEnums.IcingaExitCode.Unknown; } diff --git a/lib/icinga/plugin/New-IcingaPerformanceDataEntry.psm1 b/lib/icinga/plugin/New-IcingaPerformanceDataEntry.psm1 index 3fa8ad6..e113b66 100644 --- a/lib/icinga/plugin/New-IcingaPerformanceDataEntry.psm1 +++ b/lib/icinga/plugin/New-IcingaPerformanceDataEntry.psm1 @@ -2,23 +2,30 @@ function New-IcingaPerformanceDataEntry() { param ( $PerfDataObject, - $Label = $null, - $Value = $null, - $Warning = $null, - $Critical = $null + $Label = $null, + $Value = $null, + $Warning = $null, + $Critical = $null, + [hashtable]$PerfData = @{ }, + [string]$Interval = '' ); if ($null -eq $PerfDataObject) { - return ''; + return $PerfData; } + [string]$MetricIndex = $PerfDataObject.index; + [string]$MetricName = $PerfDataObject.name; [string]$LabelName = $PerfDataObject.label; + [string]$Template = $PerfDataObject.template; [string]$PerfValue = $PerfDataObject.value; [string]$WarningValue = $PerfDataObject.warning; [string]$CriticalValue = $PerfDataObject.critical; if ([string]::IsNullOrEmpty($Label) -eq $FALSE) { - $LabelName = $Label; + $LabelName = $Label; + $MetricInterval = $Label.Split('::')[-1]; + $MetricName = [string]::Format('{0}::{1}', $MetricName, $MetricInterval); } if ([string]::IsNullOrEmpty($Value) -eq $FALSE) { $PerfValue = $Value; @@ -26,7 +33,7 @@ function New-IcingaPerformanceDataEntry() # Override our warning/critical values only if the label does not match. # Eg. Core_1 not matching Core_1_5 - this is only required for time span checks - if ([string]::IsNullOrEmpty($Label) -eq $FALSE -And $Label -ne $PerfDataObject.label) { + if ([string]::IsNullOrEmpty($Label) -eq $FALSE -And [string]::IsNullOrEmpty($Interval) -eq $FALSE -And $Label.Contains([string]::Format('::Interval{0}', $Interval))) { $WarningValue = $Warning; $CriticalValue = $Critical; } @@ -41,16 +48,47 @@ function New-IcingaPerformanceDataEntry() $maximum = [string]::Format(';{0}', $PerfDataObject.maximum); } - return ( - [string]::Format( - "'{0}'={1}{2};{3};{4}{5}{6} ", - $LabelName.ToLower(), - (Format-IcingaPerfDataValue $PerfValue), - $PerfDataObject.unit, - (Format-IcingaPerfDataValue $WarningValue), - (Format-IcingaPerfDataValue $CriticalValue), - (Format-IcingaPerfDataValue $minimum), - (Format-IcingaPerfDataValue $maximum) - ) + [string]$MultiLabelName = ''; + $LabelName = [string]::Format('{0}::ifw_{1}::{2}', $MetricIndex, $Template, $MetricName).Replace('::::', '::'); + + if ($LabelName.Contains('::Interval') -eq $FALSE) { + if ($PerfData.ContainsKey($LabelName) -eq $FALSE) { + $PerfData.Add( + $LabelName, + @{ + 'Index' = ''; + 'Values' = @() + } + ); + } + } + + if ([string]::IsNullOrEmpty($LabelName) -eq $FALSE -And $LabelName.Contains('::Interval')) { + $IntervalName = $LabelName.Split('::')[-1]; + $LabelInterval = $IntervalName.Replace('Interval', ''); + $MetricName = $LabelName.Split('::')[4]; + $MultiLabelName = [string]::Format('{0}{1}', $MetricName, (ConvertTo-IcingaNumericTimeIndex -TimeValue $LabelInterval)); + $LabelName = [string]::Format('{0}::ifw_{1}::{2}', $MetricIndex, $Template, $MetricName); + } else { + $MultiLabelName = $LabelName; + } + + $PerfDataOutput = [string]::Format( + "'{0}'={1}{2};{3};{4}{5}{6}", + $MultiLabelName.ToLower(), + (Format-IcingaPerfDataValue $PerfValue), + $PerfDataObject.unit, + (Format-IcingaPerfDataValue $WarningValue), + (Format-IcingaPerfDataValue $CriticalValue), + (Format-IcingaPerfDataValue $minimum), + (Format-IcingaPerfDataValue $maximum) ); + + if ($MultiLabelName.Contains('::ifw_')) { + $PerfData[$LabelName].Index = $PerfDataOutput; + } else { + $PerfData[$LabelName].Values += $PerfDataOutput; + } + + return $PerfData; } diff --git a/lib/icinga/plugin/Write-IcingaPluginOutput.psm1 b/lib/icinga/plugin/Write-IcingaPluginOutput.psm1 index e7447b0..97d12f6 100644 --- a/lib/icinga/plugin/Write-IcingaPluginOutput.psm1 +++ b/lib/icinga/plugin/Write-IcingaPluginOutput.psm1 @@ -11,6 +11,6 @@ function Write-IcingaPluginOutput() Write-IcingaConsolePlain $Output; } else { # New behavior with local thread separated results - $global:Icinga.Private.Scheduler.CheckResults += $Output; + [array]$Global:Icinga.Private.Scheduler.CheckResults += $Output; } } diff --git a/lib/icinga/plugin/Write-IcingaPluginPerfData.psm1 b/lib/icinga/plugin/Write-IcingaPluginPerfData.psm1 index 2ac4e12..26ce274 100644 --- a/lib/icinga/plugin/Write-IcingaPluginPerfData.psm1 +++ b/lib/icinga/plugin/Write-IcingaPluginPerfData.psm1 @@ -25,58 +25,60 @@ function Write-IcingaPluginPerfData() $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) { - [string]$PerfDataOutput = (Get-IcingaPluginPerfDataContent -PerfData $PerformanceData -CheckResultCache $CheckResultCache -IcingaCheck $IcingaCheck); - Write-IcingaConsolePlain ([string]::Format('| {0}', $PerfDataOutput)); - } else { - [void](Get-IcingaPluginPerfDataContent -PerfData $PerformanceData -CheckResultCache $CheckResultCache -AsObject $TRUE -IcingaCheck $IcingaCheck); + if ($Global:Icinga.Private.Scheduler.PerformanceData.Count -ne 0) { + Write-IcingaConsolePlain ([string]::Format('| {0}', ([string]::Join(' ', $Global:Icinga.Private.Scheduler.PerformanceData)))); + } } } function Get-IcingaPluginPerfDataContent() { - param( + param ( $PerfData, $CheckResultCache, - [bool]$AsObject = $FALSE, - $IcingaCheck = $null + $IcingaCheck = $null ); - [string]$PerfDataOutput = ''; + [hashtable]$CompiledPerfData = @{ }; foreach ($package in $PerfData.Keys) { $data = $PerfData[$package]; if ($data.package) { - $PerfDataOutput += (Get-IcingaPluginPerfDataContent -PerfData $data.perfdata -CheckResultCache $CheckResultCache -AsObject $AsObject -IcingaCheck $IcingaCheck); + (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); - $SearchEntry = $checkresult.Name; - if ($SearchEntry -like "$SearchPattern*") { - $TimeSpan = $IcingaCheck.__GetTimeSpanThreshold($SearchEntry, $data.label); + $SearchPattern = [string]::Format('{0}_', $data.label); + $SearchPatternMulti = [string]::Format('::{0}::Interval', $data.multilabel); + $SearchEntry = $checkresult.Name; - $cachedresult = (New-IcingaPerformanceDataEntry -PerfDataObject $data -Label $SearchEntry -Value $checkresult.Value -Warning $TimeSpan.Warning -Critical $TimeSpan.Critical); - - if ($AsObject) { - # New behavior with local thread separated results - $global:Icinga.Private.Scheduler.PerformanceData += $cachedresult; - } - $PerfDataOutput += $cachedresult; + 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; } } - - $compiledPerfData = (New-IcingaPerformanceDataEntry $data); - - if ($AsObject) { - # New behavior with local thread separated results - $global:Icinga.Private.Scheduler.PerformanceData += $compiledPerfData; - } - $PerfDataOutput += $compiledPerfData; } } - return $PerfDataOutput; + 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; + } + + [array]$global:Icinga.Private.Scheduler.PerformanceData = $Global:Icinga.Private.Scheduler.PerformanceData | Sort-Object @{ + expression = { $_.Substring(1, $_.IndexOf('::') - 1) -As [int] } + }; } Export-ModuleMember -Function @( 'Write-IcingaPluginPerfData' );