diff --git a/Makefile b/Makefile index fe5dad5..30b9c8d 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ all: help .PHONY: test-plugins test-plugins: - -${POWERSHELL_BIN} -Command Install-Module Pester -Force + -${POWERSHELL_BIN} -Command Import-Module Pester -Force @for f in ${SCRIPTS_DIR}/*.ps1; do \ ${POWERSHELL_BIN} -Command Invoke-Pester $(TEST_DIR)/TestPlugNpshell.Tests.ps1 -CodeCoverage "$${f}"; done @@ -54,7 +54,7 @@ test: test-plugins .PHONY: script-analyzer-plugins script-analyzer-plugins: - -${POWERSHELL_BIN} -Command Install-Module PSScriptAnalyzer -Force + -${POWERSHELL_BIN} -Command Import-Module PSScriptAnalyzer -Force @for f in ${SCRIPTS_DIR}/*.ps1; do \ ${POWERSHELL_BIN} -Command Invoke-ScriptAnalyzer "$${f}"; done diff --git a/PlugNpshell/metric.ps1 b/PlugNpshell/metric.ps1 index d264617..b0bf67b 100644 --- a/PlugNpshell/metric.ps1 +++ b/PlugNpshell/metric.ps1 @@ -9,7 +9,6 @@ class Metric { [string] $Name [double] $Value = [long]::MinValue [string] $UOM = '' - [string] $UOMprefix = '' [string] $WarningThreshold = $null [string] $CriticalThreshold = $null [boolean] $SiBytesConversion = $false @@ -23,15 +22,38 @@ class Metric { [string] $PerfOutput = '' [string] $DisplayName = '' [string] $DisplayFormat = "{name} is {value}{unit}" - [System.Object[]] $ByteConversionValuesP - [System.Object[]] $DecConversionValuesP - [System.Object[]] $DecConversionValuesN - [System.Collections.ArrayList] $ByteUnits = @('b', 'B', 'bps', 'Bps') - [System.Collections.ArrayList] $ConvertableUnitsN = @('s', 'Hz', 'W') - [System.Collections.ArrayList] $ConvertableUnitsP = @('Hz', 'W') - [System.Collections.ArrayList] $ConvertableUnits + + [System.Collections.ArrayList] static $ByteUnits = @('b', 'B', 'bps', 'Bps') + [System.Collections.ArrayList] static $ConvertableUnitsN = @('s', 'Hz', 'W') + [System.Collections.ArrayList] static $ConvertableUnitsPositive = @('Hz', 'W') + [System.Collections.ArrayList] static $ConvertableUnitsP = [metric]::ConvertableUnitsPositive + [metric]::ByteUnits + [System.Collections.ArrayList] static $ConvertableUnits = [metric]::ConvertableUnitsN + [metric]::ConvertableUnitsP [int] $PRECISION = 2 + [UnitCollection] static $EiB = [UnitCollection]::New("ExbiByte", "E", [Math]::Pow(1024, 6)) + [UnitCollection] static $PiB = [UnitCollection]::New("PebiByte", "P", [Math]::Pow(1024, 5)) + [UnitCollection] static $TiB = [UnitCollection]::New("TebiByte", "T", [Math]::Pow(1024, 4)) + [UnitCollection] static $GiB = [UnitCollection]::New("GibiByte", "G", [Math]::Pow(1024, 3)) + [UnitCollection] static $MiB = [UnitCollection]::New("MebiByte", "M", [Math]::Pow(1024, 2)) + [UnitCollection] static $KiB = [UnitCollection]::New("KibiByte", "K", 1024) + [System.Object[]] static $ByteConversionValuesP = @([metric]::EiB, [metric]::PiB, [metric]::TiB, [metric]::GiB, ` + [metric]::MiB, [metric]::KiB) + + [UnitCollection] static $EB = [UnitCollection]::New("ExaByte", "E", 1e18) + [UnitCollection] static $PB = [UnitCollection]::New("PetaByte", "P", 1e15) + [UnitCollection] static $TB = [UnitCollection]::New("TeraByte", "T", 1e12) + [UnitCollection] static $GB = [UnitCollection]::New("GigaByte", "G", 1e9) + [UnitCollection] static $MB = [UnitCollection]::New("MegaByte", "M", 1e6) + [UnitCollection] static $KB = [UnitCollection]::New("KiloByte", "K", 1000) + [System.Object[]] static $DecConversionValuesP = @([metric]::EB, [metric]::PB, [metric]::TB, [metric]::GB, ` + [metric]::MB, [metric]::KB) + + [UnitCollection] static $Pico = [UnitCollection]::New("Pico", "p", [Math]::Pow(0.001, 4)) + [UnitCollection] static $Nano = [UnitCollection]::New("Nano", "n", [Math]::Pow(0.001, 3)) + [UnitCollection] static $Micro = [UnitCollection]::New("Micro", "u", [Math]::Pow(0.001, 2)) + [UnitCollection] static $Milli = [UnitCollection]::New("Milli", "m", [Math]::Pow(0.001, 1)) + [System.Object[]] static $DecConversionValuesN = @([metric]::Milli, [metric]::Micro, [metric]::Nano, [metric]::Pico) + <# .DESCRIPTION Object to represent Metrics added to a Check object. @@ -58,23 +80,20 @@ class Metric { $this."$key" = $_.Value } catch { - throw [ParamError] "Invalid Parameter '$key'. Check the correct naming of the argument." + throw [ParamError]::new("Invalid Parameter '$key'. Check the correct naming of the argument.") } } if ($this.Name -And $this.Value -ne [long]::MinValue) { $this.Init() } else { - throw [ParamError] "Insufficient parameters. You must specify the Name and the Value of the metric." + throw [ParamError]::new("Insufficient parameters. You must specify the Name and the Value of the metric.") } } [void] Init() { $this.DisplayName = $( if ($this.DisplayName) { $this.DisplayName } else { $this.Name } ) - $this.ConvertableUnitsP += $this.ByteUnits - $this.ConvertableUnits = $this.ConvertableUnitsN + $this.ConvertableUnitsP $this.ValidateName() - $this.CreateConversionTables() if ($this.DisplayInSummary) { $this.CreateMetricSummary() } @@ -85,60 +104,39 @@ class Metric { } - [void] CreateConversionTables() { - $EiB = [UnitCollection]::New("ExbiByte", "E", [Math]::Pow(1024, 6)) - $PiB = [UnitCollection]::New("PebiByte", "P", [Math]::Pow(1024, 5)) - $TiB = [UnitCollection]::New("TebiByte", "T", [Math]::Pow(1024, 4)) - $GiB = [UnitCollection]::New("GibiByte", "G", [Math]::Pow(1024, 3)) - $MiB = [UnitCollection]::New("MebiByte", "M", [Math]::Pow(1024, 2)) - $KiB = [UnitCollection]::New("KibiByte", "K", 1024) - $this.ByteConversionValuesP = @($EiB, $PiB, $TiB, $GiB, $MiB, $KiB) - - $EB = [UnitCollection]::New("ExaByte", "E", 1e18) - $PB = [UnitCollection]::New("PetaByte", "P", 1e15) - $TB = [UnitCollection]::New("TeraByte", "T", 1e12) - $GB = [UnitCollection]::New("GigaByte", "G", 1e9) - $MB = [UnitCollection]::New("MegaByte", "M", 1e6) - $KB = [UnitCollection]::New("KiloByte", "K", 1000) - $this.DecConversionValuesP = @($EB, $PB, $TB, $GB, $MB, $KB) - - $Pico = [UnitCollection]::New("Pico", "p", [Math]::Pow(0.001, 4)) - $Nano = [UnitCollection]::New("Nano", "n", [Math]::Pow(0.001, 3)) - $Micro = [UnitCollection]::New("Micro", "u", [Math]::Pow(0.001, 2)) - $Milli = [UnitCollection]::New("Milli", "m", [Math]::Pow(0.001, 1)) - $this.DecConversionValuesN = @($Milli, $Micro, $Nano, $Pico) - } - - [UnitCollection[]] GetConvertableUnitsArr($Val, $Unit) { + [UnitCollection[]] static GetConvertableUnitsArr($Val, $Unit, $SiConversion) { # Returns the correct array to convert the value $ConversionArr = @() - if ($Val -ge 1 -And $this.ConvertableUnitsP.Contains($Unit)) { - if ($this.ByteUnits.Contains($Unit) -And -not($this.SiBytesConversion)) { - $ConversionArr = $this.ByteConversionValuesP + if ($Val -ge 1 -And [Metric]::ConvertableUnitsP.Contains($Unit)) { + if ([Metric]::ByteUnits.Contains($Unit) -And -Not($SiConversion)) { + $ConversionArr = [Metric]::ByteConversionValuesP } else { - $ConversionArr = $this.DecConversionValuesP + $ConversionArr = [Metric]::DecConversionValuesP } } - elseif ($Val -lt 1 -And $this.ConvertableUnitsN.Contains($Unit)) { - $ConversionArr = $this.DecConversionValuesN + elseif ($Val -lt 1 -And [Metric]::ConvertableUnitsN.Contains($Unit)) { + $ConversionArr = [Metric]::DecConversionValuesN } return $ConversionArr } - [double] ConvertValue($OldValue, $ConversionTable, $Precision) { + [HashTable] static ConvertValueMethod($OldValue, $Unit, $ConversionTable, $Precision) { # Converts values with the right prefix for display. - [double] $NewValue = $OldValue + $UOMprefix = '' + [hashtable] $conversion = @{ } + [double] $newValue = $OldValue for ($i = 0; $i -lt $ConversionTable.Count; $i++) { if ($OldValue -ge $ConversionTable[$i].GetValue()) { - $NewValue = $OldValue / $ConversionTable[$i].GetValue() - $this.UOMprefix = $ConversionTable[$i].GetUnitPrefix() + $newValue = $OldValue / $ConversionTable[$i].GetValue() + $UOMprefix = $ConversionTable[$i].GetUnitPrefix() break } } - $NewValue = [math]::Round($NewValue, $Precision) - return $NewValue + $conversion.Value = [math]::Round($newValue, $Precision) + $conversion.UOM = "$UOMprefix$Unit" + return $conversion } [void] ValidateName() { @@ -167,32 +165,31 @@ class Metric { return $ReturnCode } - [double] ConvertThreshold($Threshold) { + [double] static ConvertThreshold([string]$Threshold, [string]$Uom, [boolean]$SiConversion) { # Convert threshold value. - [string] $Threshold $ConvertVal = 1 $Unit = $Threshold -replace ('\d|\.', '') - $Val = $Threshold -replace ("([^\d]|\.)+", '') + $Val = $Threshold -replace ('([^\d.])+','') $ConvertUnit = $Unit.Substring(0, 1) $Unit = $Unit.Substring(1) - If ($Unit -ne $this.UOM) { - throw [InvalidMetricThreshold] + If ($Uom -ne $Unit) { + throw [InvalidMetricThreshold]::new("Unit '$Uom' doesn't match '$Unit'") } try { $NumericValue = [float]::Parse($Val) } catch { - throw [InvalidMetricThreshold] + throw [InvalidMetricThreshold]::new("'$Val' is not a numeric value") } - $ConversionArr = $this.GetConvertableUnitsArr($NumericValue, $Unit) + $ConversionArr = [Metric]::GetConvertableUnitsArr($NumericValue, $Uom, $SiConversion) if ($ConversionArr) { $ConvertVal = ($ConversionArr | Where-Object { $_.UnitPrefix -eq $ConvertUnit }).Value } else { - throw[InvalidMetricThreshold] + throw [InvalidMetricThreshold]::new("Error make sure the '$Uom' is correct") } return $NumericValue * $ConvertVal } @@ -209,7 +206,7 @@ class Metric { if ($Val.Substring($Val.length - 1) -Match '\d') { return $Val } - return $this.ConvertThreshold($Val) + return [Metric]::ConvertThreshold($Val, $this.UOM, $this.SiBytesConversion) } [HashTable] ParseThreshold($Threshold) { @@ -241,8 +238,8 @@ class Metric { $Return.end = $this.ParseThresholdLimit($end, $false) } } - catch { - throw [InvalidMetricThreshold] ("Invalid Threshold Syntax '$Threshold'") + catch [InvalidMetricThreshold] { + throw [InvalidMetricThreshold]::new("Invalid Threshold Syntax '$Threshold'. $($_.Exception.ErrorMessage)") } return $Return } @@ -259,25 +256,32 @@ class Metric { return !$isOutsideRange } + [hashtable] static ConvertValue($Value, $Unit, $SummaryPrecision, $SiConversion) { + $convertableUnitsArr = [Metric]::GetConvertableUnitsArr($Value, $Unit, $SiConversion) + $convertedMetric = [Metric]::ConvertValueMethod($Value, $Unit, $convertableUnitsArr, $summaryPrecision) + return $convertedMetric + } + [void] CreateMetricSummary() { # Creates the summary data output string for the Check""" - $ConvertedValue = $this.Value + [hashtable] $convertedMetric = @{} + $convertedMetric.Value = $this.Value + $convertedMetric.UOM = $this.UOM if ($this.ConvertMetric) { - $ConvertableUnitsArr = $this.GetConvertableUnitsArr($this.Value, $this.UOM) - $ConvertedValue = $this.ConvertValue($this.Value, $ConvertableUnitsArr, $this.SummaryPrecision) + $convertedMetric = [Metric]::ConvertValue($this.Value, $this.UOM, $this.SummaryPrecision, $this.SiBytesConversion) } - $DisplayUOM = $this.UOMprefix + $this.UOM + $displayUOM = $convertedMetric.UOM if ($this.DisplayInSummary) { $this.DisplayFormat = $this.DisplayFormat -Replace ('{name}', $this.DisplayName) $this.DisplayFormat = $this.DisplayFormat -Replace ('{unit}', $DisplayUOM) - $this.DisplayFormat = $this.DisplayFormat -Replace ('{value}', $ConvertedValue) + $this.DisplayFormat = $this.DisplayFormat -Replace ('{value}', $convertedMetric.Value) $this.Summary = $this.DisplayFormat } } [void] CreateMetricPerfOutput() { # Creates the performance data output string for the Check""" - $MetricValue = [math]::Round($this.Value, $this.PerfDataPrecision) + $MetricValue = [Math]::Round($this.Value, $this.PerfDataPrecision) $MetricName = $( if ( $this.Name.Contains(' ')) { "'{0}'" -f $this.Name } else { $this.Name } ) $HasThresholds = $this.WarningThreshold -Or $this.CriticalThreshold $this.PerfOutput = "{0}={1}{2}{3}{4}{5}{6} " -f $MetricName, $MetricValue, $this.UOM, diff --git a/README.md b/README.md index b36aa47..924e9e8 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # PlugNpshell -*A Simple PowerShell Library for creating [Opsview Opspack plugins](https://github.com/opsview/Opsview-Integrations/blob/master/WHAT_IS_A_MONITORING_PLUGIN.md).* +*A Simple Powershell Library for creating [Opsview Opspack plugins](https://github.com/opsview/Opsview-Integrations/blob/master/WHAT_IS_A_MONITORING_PLUGIN.md).* * **category** Libraries * **copyright** Copyright (C) 2003 - 2019 Opsview Limited. All rights reserved * **license** Apache License Version 2.0 (see [LICENSE](LICENSE)) -* **link** https://github.com/opsview/plugnpshell +* **link** https://github.com/opsview/powershell ## Installing the Library @@ -269,6 +269,19 @@ This would produce the following output: `METRIC WARNING - Disk Usage is 30.56% + CPU Usage is 70.75% | 'Disk Usage'=30.56%;70;90 'CPU Usage'=70.75%;70;90` +## Helper methods + +The **Metric** class includes a helper method to make developing service checks easier. + +The **ConvertValue()** method converts a given value and unit to a more human friendly value and unit. + +```Powershell +$value = 2400; $unit = 'B'; $decimalPrecision = 2; $siBytesConversion = $false +$converted = [Metric]::ConvertValue($value, $unit, $decimalPrecision, $siBytesConversion) +``` + +The above example will return a hashtable, where **$converted.Value** will be `2.34` and **$converted.UOM** will be `'KB'`. +Both methods support the **SiBytesConversion** field. See [**Checks with automatic conversions**](#checks-with-automatic-conversions) above for more details. ## Using the Exceptions diff --git a/Test/TestPlugNpshell.Tests.ps1 b/Test/TestPlugNpshell.Tests.ps1 index ab935b5..5b336a4 100644 --- a/Test/TestPlugNpshell.Tests.ps1 +++ b/Test/TestPlugNpshell.Tests.ps1 @@ -183,7 +183,7 @@ Describe 'CheckExitCodes'{ $out = { $MetricC = [Metric]::New(@{Name = 'Metric'; Value = 10000; WarningThreshold = '1Kdd'}) } | Should -Throw -PassThru - $out.Exception.ErrorMessage | Should -Be "Invalid Threshold Syntax '1Kdd'" + $out.Exception.ErrorMessage | Should -Be "Invalid Threshold Syntax '1Kdd'. Unit '' doesn't match 'dd'" } } @@ -347,7 +347,6 @@ Describe 'CheckPrecision'{ $Check.addMetricObj($metricA) $Expected = "METRIC OK - Disk Usage is 30.554% | disk_usage=30.5543%;50;60 " (Get-Final($Check) | Should -Be $Expected) - } } @@ -387,6 +386,22 @@ Describe 'CheckCompleteOutputWhenNotConverting'{ } } +Describe 'Check Static methods'{ + $value = 10000 + $unit = 'b' + $precision = 2 + $siBytesConversion = $false + it 'Should convert the value without an object'{ + $converted = [Metric]::ConvertValue($value, $unit, $precision, $siBytesConversion) + $converted.value | should -be 9.77 + $converted.UOM | Should -Be 'Kb' + } + + it 'Should convert the threshold'{ + $converted = [Metric]::ConvertThreshold('10MB','B',$false) + $converted | Should -Be 10485760 + } +}