Skip to content

Commit 3ece825

Browse files
RincewindsHatLorenz KästleRincewindsHat
authored
Perfdata errors (#116)
* Introduce new perfdata formatting function with error returns This patch adds a new perfdata formatting function which validates the the performance data value to stricter criteria (no Inf and NAN) and returns an error if such a value occurs. The normal String function now returns the empty string in such cases. --------- Co-authored-by: Lorenz Kästle <[email protected]> Co-authored-by: RincewindsHat <[email protected]>
1 parent 8bc3d64 commit 3ece825

File tree

3 files changed

+121
-18
lines changed

3 files changed

+121
-18
lines changed

perfdata/list.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,13 @@ func (l PerfdataList) String() string {
1313
var out strings.Builder
1414

1515
for _, p := range l {
16-
out.WriteString(" ")
17-
out.WriteString(p.String())
16+
pfDataString, err := p.ValidatedString()
17+
18+
// Ignore perfdata points which fail to format
19+
if err == nil {
20+
out.WriteString(" ")
21+
out.WriteString(pfDataString)
22+
}
1823
}
1924

2025
return strings.Trim(out.String(), " ")

perfdata/type.go

+49-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package perfdata
22

33
import (
4+
"errors"
45
"fmt"
6+
"math"
57
"strings"
68

79
"github.com/NETWAYS/go-check"
@@ -13,16 +15,36 @@ var replacer = strings.NewReplacer("=", "_", "`", "_", "'", "_", "\"", "_")
1315
// formatNumeric returns a string representation of various possible numerics
1416
//
1517
// This supports most internal types of Go and all fmt.Stringer interfaces.
16-
func formatNumeric(value interface{}) string {
18+
// Returns an eror in some known cases where the value of a data type does not
19+
// represent a valid measurement, e.g INF for floats
20+
// This error can probably ignored in most cases and the perfdata point omitted,
21+
// but silently dropping the value and returning the empty strings seems like bad style
22+
func formatNumeric(value interface{}) (string, error) {
1723
switch v := value.(type) {
1824
case float64:
19-
return check.FormatFloat(v)
25+
if math.IsInf(v, 0) {
26+
return "", errors.New("Perfdata value is inifinite")
27+
}
28+
29+
if math.IsNaN(v) {
30+
return "", errors.New("Perfdata value is inifinite")
31+
}
32+
33+
return check.FormatFloat(v), nil
2034
case float32:
21-
return check.FormatFloat(float64(v))
35+
if math.IsInf(float64(v), 0) {
36+
return "", errors.New("Perfdata value is inifinite")
37+
}
38+
39+
if math.IsNaN(float64(v)) {
40+
return "", errors.New("Perfdata value is inifinite")
41+
}
42+
43+
return check.FormatFloat(float64(v)), nil
2244
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
23-
return fmt.Sprintf("%d", v)
45+
return fmt.Sprintf("%d", v), nil
2446
case fmt.Stringer, string:
25-
return fmt.Sprint(v)
47+
return fmt.Sprint(v), nil
2648
default:
2749
panic(fmt.Sprintf("unsupported type for perfdata: %T", value))
2850
}
@@ -51,7 +73,17 @@ type Perfdata struct {
5173
}
5274

5375
// String returns the proper format for the plugin output
76+
// on errors (occurs with invalid data, the empty string is returned
5477
func (p Perfdata) String() string {
78+
tmp, _ := p.ValidatedString()
79+
return tmp
80+
}
81+
82+
// ValidatedString returns the proper format for the plugin output
83+
// Returns an eror in some known cases where the value of a data type does not
84+
// represent a valid measurement, see the explanation for "formatNumeric" for
85+
// perfdata values.
86+
func (p Perfdata) ValidatedString() (string, error) {
5587
var sb strings.Builder
5688

5789
// Add quotes if string contains any whitespace
@@ -61,7 +93,12 @@ func (p Perfdata) String() string {
6193
sb.WriteString(replacer.Replace(p.Label) + "=")
6294
}
6395

64-
sb.WriteString(formatNumeric(p.Value))
96+
pfVal, err := formatNumeric(p.Value)
97+
if err != nil {
98+
return "", err
99+
}
100+
101+
sb.WriteString(pfVal)
65102
sb.WriteString(p.Uom)
66103

67104
// Thresholds
@@ -78,9 +115,13 @@ func (p Perfdata) String() string {
78115
sb.WriteString(";")
79116

80117
if value != nil {
81-
sb.WriteString(formatNumeric(value))
118+
pfVal, err := formatNumeric(value)
119+
// Attention: we ignore limits if they are faulty
120+
if err == nil {
121+
sb.WriteString(pfVal)
122+
}
82123
}
83124
}
84125

85-
return strings.TrimRight(sb.String(), ";")
126+
return strings.TrimRight(sb.String(), ";"), nil
86127
}

perfdata/type_test.go

+65-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package perfdata
22

33
import (
4+
"math"
45
"testing"
56

67
"github.com/NETWAYS/go-check"
@@ -71,19 +72,75 @@ func TestRenderPerfdata(t *testing.T) {
7172
},
7273
}
7374

75+
testcasesWithErrors := map[string]struct {
76+
perf Perfdata
77+
expected string
78+
}{
79+
"invalid-value": {
80+
perf: Perfdata{
81+
Label: "to infinity",
82+
Value: math.Inf(+1),
83+
},
84+
expected: "",
85+
},
86+
}
87+
7488
for name, tc := range testcases {
7589
t.Run(name, func(t *testing.T) {
76-
assert.Equal(t, tc.expected, tc.perf.String())
90+
pfVal, err := tc.perf.ValidatedString()
91+
assert.NoError(t, err)
92+
assert.Equal(t, tc.expected, pfVal)
93+
})
94+
}
95+
96+
for name, tc := range testcasesWithErrors {
97+
t.Run(name, func(t *testing.T) {
98+
pfVal, err := tc.perf.ValidatedString()
99+
assert.Error(t, err)
100+
assert.Equal(t, tc.expected, pfVal)
77101
})
78102
}
79103
}
80104

105+
type pfFormatTest struct {
106+
Result string
107+
InputValue interface{}
108+
}
109+
81110
func TestFormatNumeric(t *testing.T) {
82-
assert.Equal(t, "10", formatNumeric(10))
83-
assert.Equal(t, "-10", formatNumeric(-10))
84-
assert.Equal(t, "10", formatNumeric(uint8(10)))
85-
assert.Equal(t, "1234.567", formatNumeric(1234.567))
86-
assert.Equal(t, "1234.567", formatNumeric(float32(1234.567)))
87-
assert.Equal(t, "1234.567", formatNumeric("1234.567"))
88-
assert.Equal(t, "1234567890.988", formatNumeric(1234567890.9877))
111+
testdata := []pfFormatTest{
112+
{
113+
Result: "10",
114+
InputValue: 10,
115+
},
116+
{
117+
Result: "-10",
118+
InputValue: -10,
119+
},
120+
{
121+
Result: "10",
122+
InputValue: uint8(10),
123+
},
124+
{
125+
Result: "1234.567",
126+
InputValue: 1234.567,
127+
},
128+
{
129+
Result: "1234.567",
130+
InputValue: float32(1234.567),
131+
},
132+
{Result: "1234.567",
133+
InputValue: "1234.567",
134+
},
135+
{
136+
Result: "1234567890.988",
137+
InputValue: 1234567890.9877,
138+
},
139+
}
140+
141+
for _, val := range testdata {
142+
formatted, err := formatNumeric(val.InputValue)
143+
assert.NoError(t, err)
144+
assert.Equal(t, val.Result, formatted)
145+
}
89146
}

0 commit comments

Comments
 (0)