Skip to content

Commit

Permalink
Merge pull request #181 from atc0005/i176-refactor-newly-added-range-…
Browse files Browse the repository at this point in the history
…support

Refactor newly added Range support
  • Loading branch information
atc0005 authored Jan 26, 2023
2 parents 77ed0c2 + 9a45c91 commit d1d65af
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 168 deletions.
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Shared Golang package for Nagios plugins
- [Changelog](#changelog)
- [Examples](#examples)
- [License](#license)
- [Used by](#used-by)
- [References](#references)

## Status
Expand Down Expand Up @@ -47,9 +48,11 @@ various plugins and help reduce typos associated with literal strings.
- simple label and exit code "wrapper"
- useful in client code as a way to map internal check results to a Nagios
service state value
- Supports "branding" callback function to display application name,
version, or other information as a "trailer" for check results provided to
Nagios
- Support for evaluating a given performance data value and setting the final
plugin state exit code
- Support for using a "branding" callback function to display application
name, version, or other information as a "trailer" for check results
provided to Nagios
- this could be useful for identifying what version of a plugin determined
the service or host state to be an issue
- Panics from client code are captured and reported
Expand Down Expand Up @@ -139,6 +142,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

## Used by

See the Known importers lists below for a dynamically updated list of projects
using either this library or the original project.

- [Known importers (pkg.go.dev)](https://pkg.go.dev/github.com/atc0005/go-nagios?tab=importedby)
- <https://github.com/infraweavers/monitoring-agent-check-nt-replacement>

## References

- Nagios
Expand All @@ -159,3 +170,8 @@ SOFTWARE.
- <https://github.com/golang/go/wiki/Modules>
- Panics, stack traces
- <https://www.golangprograms.com/example-stack-and-caller-from-runtime-package.html>

See also the [Used by](#used-by) section for projects known to be using this
package. Please
[report](https://github.com/atc0005/go-nagios/issues/new/choose) any
additional projects that we've missed!
147 changes: 0 additions & 147 deletions nagios.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ import (
"fmt"
"io"
"os"
"regexp"
"runtime/debug"
"strconv"
"strings"
"time"
)
Expand Down Expand Up @@ -169,122 +167,6 @@ type PerformanceData struct {
Max string
}

// Range represents the thresholds that the user can pass in for warning
// and critical, this format is defined here:
// https://www.monitoring-plugins.org/doc/guidelines.html#THRESHOLDFORMAT
type Range struct {
StartInfinity bool
EndInfinity bool
AlertOn string
Start float64
End float64
}

// CheckRange returns Returns true if an alert should be raised,
// otherwise false
func (r Range) CheckRange(value string) bool {

valueAsAFloat, _ := strconv.ParseFloat(value, 64)
isOutsideRange := r.checkOutsideRange(valueAsAFloat)
if r.AlertOn == "INSIDE" {
return !isOutsideRange
}
return isOutsideRange
}

// checkOutsideRange returns in the inverse of CheckRange
// it is used to handle the inverting logic of "inside" vs
// "outside" ranges in a cleanish way
func (r Range) checkOutsideRange(valueAsAFloat float64) bool {

if r.EndInfinity == false && r.StartInfinity == false {
if r.Start <= valueAsAFloat && valueAsAFloat <= r.End {
return false
} else {
return true
}
} else if r.StartInfinity == false && r.EndInfinity == true {
if valueAsAFloat >= r.Start {
return false
} else {
return true
}
} else if r.StartInfinity == true && r.EndInfinity == false {
if valueAsAFloat <= r.End {
return false
} else {
return true
}
} else {
return false
}
}

// ParseRangeString static method to construct a Range object
// from the string representation based on the definition here:
// https://www.monitoring-plugins.org/doc/guidelines.html#THRESHOLDFORMAT
func ParseRangeString(input string) *Range {

r := Range{}

digitOrInfinity := regexp.MustCompile(`[\d~]`)
optionalInvertAndRange := regexp.MustCompile(`^\@?((?:[-+]?[\d\.]+)(?:e(?:[-+]?[\d\.]+))?|~)?(:((?:[-+]?[\d\.]+)(?:e(?:[-+]?[\d\.]+))?)?)?$`)
firstHalfOfRange := regexp.MustCompile(`^((?:[-+]?[\d\.]+)(?:e(?:[-+]?[\d\.]+))?)?:`)
endOfRange := regexp.MustCompile(`^(?:[-+]?[\d\.]+)(?:e(?:[-+]?[\d\.]+))?$`)

r.Start = 0
r.StartInfinity = false
r.End = 0
r.EndInfinity = false
r.AlertOn = "OUTSIDE"

valid := true

if !(digitOrInfinity.MatchString(input) && optionalInvertAndRange.MatchString(input)) { //not match regex
return nil
}

// invert the range, i.e. @10:20 means ≥ 10 and ≤ 20, (inside the range of {10 .. 20} inclusive)
if strings.HasPrefix(input, "@") {
r.AlertOn = "INSIDE"
input = input[1:]
}
// ~ represents infinity
if strings.HasPrefix(input, "~") {
r.StartInfinity = true
input = input[1:]
}

// 10:
rangeComponents := firstHalfOfRange.FindAllStringSubmatch(input, -1)
if rangeComponents != nil {
if rangeComponents[0][1] != "" {
r.Start, _ = strconv.ParseFloat(rangeComponents[0][1], 64)
r.StartInfinity = false
}

r.EndInfinity = true
input = strings.TrimPrefix(input, rangeComponents[0][0])
valid = true
}

// x:10 or 10
endOfRangeComponents := endOfRange.FindAllStringSubmatch(input, -1)
if endOfRangeComponents != nil {

r.End, _ = strconv.ParseFloat(endOfRangeComponents[0][0], 64)
r.EndInfinity = false
valid = true
}

if valid && (r.StartInfinity || r.EndInfinity || r.Start <= r.End) {
return &r
} else {
return nil
}

}

// Validate performs basic validation of PerformanceData. An error is returned
// for any validation failures.
func (pd PerformanceData) Validate() error {
Expand Down Expand Up @@ -573,35 +455,6 @@ func (p *Plugin) AddPerfData(skipValidate bool, perfData ...PerformanceData) err
return nil
}

// EvaluateThreshold causes the performance data to be checked against
// the Warn and Crit provided and set the ExitStatusCode of the plugin
// as is appropriate
func (p *Plugin) EvaluateThreshold(perfData ...PerformanceData) error {
for i := range perfData {

if perfData[i].Crit != "" {

CriticalThresholdObject := ParseRangeString(perfData[i].Crit)

if CriticalThresholdObject.CheckRange(perfData[i].Value) {
p.ExitStatusCode = StateCRITICALExitCode
return nil
}
}

if perfData[i].Warn != "" {
warningThresholdObject := ParseRangeString(perfData[i].Warn)

if warningThresholdObject.CheckRange(perfData[i].Value) {
p.ExitStatusCode = StateWARNINGExitCode
return nil
}
}
}

return nil
}

// AddError appends provided errors to the collection.
func (p *Plugin) AddError(err ...error) {
p.Errors = append(p.Errors, err...)
Expand Down
170 changes: 170 additions & 0 deletions range.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Copyright 2023 Codeweavers Ltd
// Copyright 2023 Adam Chalkley
//
// https://github.com/atc0005/go-nagios
//
// Licensed under the MIT License. See LICENSE file in the project root for
// full license information.

package nagios

import (
"regexp"
"strconv"
"strings"
)

// Range represents the thresholds that the user can pass in for warning and
// critical, this format is based on the [Nagios Plugin Dev Guidelines:
// Threshold and Ranges] definition.
//
// [Nagios Plugin Dev Guidelines: Threshold and Ranges]: https://nagios-plugins.org/doc/guidelines.html#THRESHOLDFORMAT
type Range struct {
StartInfinity bool
EndInfinity bool
AlertOn string
Start float64
End float64
}

// CheckRange returns true if an alert should be raised for a given
// performance data Value, otherwise false.
func (r Range) CheckRange(value string) bool {
valueAsAFloat, _ := strconv.ParseFloat(value, 64)
isOutsideRange := r.checkOutsideRange(valueAsAFloat)
if r.AlertOn == "INSIDE" {
return !isOutsideRange
}
return isOutsideRange
}

// checkOutsideRange returns in the inverse of CheckRange. It is used to
// handle the inverting logic of "inside" vs "outside" ranges.
//
// See the [Nagios Plugin Dev Guidelines: Threshold and Ranges] definition for
// additional details.
//
// [Nagios Plugin Dev Guidelines: Threshold and Ranges]: https://nagios-plugins.org/doc/guidelines.html#THRESHOLDFORMAT
func (r Range) checkOutsideRange(valueAsAFloat float64) bool {
switch {
case !r.EndInfinity && !r.StartInfinity:
if r.Start <= valueAsAFloat && valueAsAFloat <= r.End {
return false
}
return true

case !r.StartInfinity && r.EndInfinity:
if valueAsAFloat >= r.Start {
return false
}
return true

case r.StartInfinity && !r.EndInfinity:
if valueAsAFloat <= r.End {
return false
}
return true

default:
return false
}
}

// ParseRangeString static method to construct a Range object from the string
// representation based on the [Nagios Plugin Dev Guidelines: Threshold and
// Ranges] definition.
//
// [Nagios Plugin Dev Guidelines: Threshold and Ranges]: https://nagios-plugins.org/doc/guidelines.html#THRESHOLDFORMAT
func ParseRangeString(input string) *Range {
r := Range{}

digitOrInfinity := regexp.MustCompile(`[\d~]`)
optionalInvertAndRange := regexp.MustCompile(`^\@?((?:[-+]?[\d\.]+)(?:e(?:[-+]?[\d\.]+))?|~)?(:((?:[-+]?[\d\.]+)(?:e(?:[-+]?[\d\.]+))?)?)?$`)
firstHalfOfRange := regexp.MustCompile(`^((?:[-+]?[\d\.]+)(?:e(?:[-+]?[\d\.]+))?)?:`)
endOfRange := regexp.MustCompile(`^(?:[-+]?[\d\.]+)(?:e(?:[-+]?[\d\.]+))?$`)

r.Start = 0
r.StartInfinity = false
r.End = 0
r.EndInfinity = false
r.AlertOn = "OUTSIDE"

valid := true

// If regex does not match ...
if !(digitOrInfinity.MatchString(input) && optionalInvertAndRange.MatchString(input)) {
return nil
}

// Invert the range.
//
// i.e. @10:20 means ≥ 10 and ≤ 20 (inside the range of {10 .. 20}
// inclusive)
if strings.HasPrefix(input, "@") {
r.AlertOn = "INSIDE"
input = input[1:]
}

// ~ represents infinity
if strings.HasPrefix(input, "~") {
r.StartInfinity = true
input = input[1:]
}

// 10:
rangeComponents := firstHalfOfRange.FindAllStringSubmatch(input, -1)
if rangeComponents != nil {
if rangeComponents[0][1] != "" {
r.Start, _ = strconv.ParseFloat(rangeComponents[0][1], 64)
r.StartInfinity = false
}

r.EndInfinity = true
input = strings.TrimPrefix(input, rangeComponents[0][0])
valid = true
}

// x:10 or 10
endOfRangeComponents := endOfRange.FindAllStringSubmatch(input, -1)
if endOfRangeComponents != nil {

r.End, _ = strconv.ParseFloat(endOfRangeComponents[0][0], 64)
r.EndInfinity = false
valid = true
}

if valid && (r.StartInfinity || r.EndInfinity || r.Start <= r.End) {
return &r
}

return nil
}

// EvaluateThreshold causes the performance data to be checked against the
// Warn and Crit thresholds provided by client code and sets the
// ExitStatusCode of the plugin as appropriate.
func (p *Plugin) EvaluateThreshold(perfData ...PerformanceData) error {
for i := range perfData {

if perfData[i].Crit != "" {

CriticalThresholdObject := ParseRangeString(perfData[i].Crit)

if CriticalThresholdObject.CheckRange(perfData[i].Value) {
p.ExitStatusCode = StateCRITICALExitCode
return nil
}
}

if perfData[i].Warn != "" {
warningThresholdObject := ParseRangeString(perfData[i].Warn)

if warningThresholdObject.CheckRange(perfData[i].Value) {
p.ExitStatusCode = StateWARNINGExitCode
return nil
}
}
}

return nil
}
Loading

0 comments on commit d1d65af

Please sign in to comment.