-
Notifications
You must be signed in to change notification settings - Fork 3
/
range.go
181 lines (153 loc) · 4.86 KB
/
range.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// 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 (
"fmt"
"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 == nil {
err := fmt.Errorf("failed to parse critical range string %s: %w ", perfData[i].Crit, ErrInvalidRangeThreshold)
p.ExitStatusCode = StateUNKNOWNExitCode
return err
}
if CriticalThresholdObject.CheckRange(perfData[i].Value) {
p.ExitStatusCode = StateCRITICALExitCode
return nil
}
}
if perfData[i].Warn != "" {
warningThresholdObject := ParseRangeString(perfData[i].Warn)
if warningThresholdObject == nil {
err := fmt.Errorf("failed to parse warning range string %s: %w ", perfData[i].Warn, ErrInvalidRangeThreshold)
p.ExitStatusCode = StateUNKNOWNExitCode
return err
}
if warningThresholdObject.CheckRange(perfData[i].Value) {
p.ExitStatusCode = StateWARNINGExitCode
return nil
}
}
}
return nil
}