master
go 236 lines 7.25 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package nagios
4
5 import (
6 "math"
7 "strings"
8 "testing"
9
10 "github.com/netdata/netdata/go/plugins/plugin/scripts.d/collector/nagios/internal/output"
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
13 )
14
15 const testCheckName = "check_memory"
16
17 func TestPerfdataRouterRoutesAndCanonicalizesUnits(t *testing.T) {
18 router := newPerfdataRouter(64)
19
20 warnLow := 100.0
21 warnHigh := 500.0
22 got := router.route(testCheckName, []output.PerfDatum{
23 {
24 Label: "latency",
25 Unit: "ms",
26 Value: 120,
27 Warn: &output.ThresholdRange{
28 Inclusive: true,
29 Low: &warnLow,
30 High: &warnHigh,
31 },
32 },
33 {Label: "throughput", Unit: "KB", Value: 30},
34 {Label: "traffic", Unit: "Mb", Value: 1.5},
35 {Label: "free_pct", Unit: "%", Value: 40},
36 {Label: "checks", Unit: "c", Value: 3},
37 {Label: "custom", Unit: "widgets", Value: 7.25},
38 })
39
40 values := valueSampleMap(got.values)
41 units := valueSampleUnits(got.values)
42 thresholds := thresholdStateMap(got.thresholdStates)
43 thresholdLabelValues := thresholdStateLabelValues(got.thresholdStates)
44
45 assertNear(t, values["perfdata.check_memory.time_latency_value"], 0.12)
46 assertNear(t, values["perfdata.check_memory.bytes_throughput_value"], 30_000)
47 assertNear(t, values["perfdata.check_memory.bits_traffic_value"], 1_500_000)
48 assertNear(t, values["perfdata.check_memory.percent_free_pct_value"], 40)
49 assertNear(t, values["perfdata.check_memory.counter_checks_value"], 3)
50 assertNear(t, values["perfdata.check_memory.generic_custom_value"], 7.25)
51
52 assertString(t, units["perfdata.check_memory.time_latency_value"], "seconds")
53 assertString(t, units["perfdata.check_memory.bytes_throughput_value"], "bytes")
54 assertString(t, units["perfdata.check_memory.bits_traffic_value"], "bits")
55 assertString(t, units["perfdata.check_memory.percent_free_pct_value"], "%")
56 assertString(t, units["perfdata.check_memory.counter_checks_value"], "c")
57 assertString(t, units["perfdata.check_memory.generic_custom_value"], "generic")
58
59 assertString(t, thresholds["perfdata.check_memory.time_latency_threshold_state"], perfThresholdStateWarning)
60 assertString(t, thresholds["perfdata.check_memory.bytes_throughput_threshold_state"], perfThresholdStateNone)
61 assertString(t, thresholds["perfdata.check_memory.bits_traffic_threshold_state"], perfThresholdStateNone)
62 assertString(t, thresholds["perfdata.check_memory.percent_free_pct_threshold_state"], perfThresholdStateNone)
63 assertString(t, thresholds["perfdata.check_memory.generic_custom_threshold_state"], perfThresholdStateNone)
64 _, hasCounterThreshold := thresholds["perfdata.check_memory.counter_checks_threshold_state"]
65 assert.False(t, hasCounterThreshold)
66 assertString(t, thresholdLabelValues["perfdata.check_memory.time_latency_threshold_state"], "time_latency")
67 assertString(t, thresholdLabelValues["perfdata.check_memory.bytes_throughput_threshold_state"], "bytes_throughput")
68 }
69
70 func TestPerfdataRouterPolicies(t *testing.T) {
71 tests := map[string]struct {
72 budget int
73 prime []output.PerfDatum
74 input []output.PerfDatum
75 assert func(*testing.T, perfRouteResult)
76 }{
77 "collision keeps first lexical metric key": {
78 budget: 64,
79 input: []output.PerfDatum{
80 {Label: "used-kb", Unit: "KB", Value: 2},
81 {Label: "used kb", Unit: "KB", Value: 1},
82 },
83 assert: func(t *testing.T, got perfRouteResult) {
84 t.Helper()
85 samples := valueSampleMap(got.values)
86 assertNear(t, samples["perfdata.check_memory.bytes_used_kb_value"], 1_000)
87 },
88 },
89 "budget drops metrics beyond cap": {
90 budget: 2,
91 input: []output.PerfDatum{
92 {Label: "a", Unit: "c", Value: 1},
93 {Label: "b", Unit: "c", Value: 2},
94 {Label: "c", Unit: "c", Value: 3},
95 },
96 assert: func(t *testing.T, got perfRouteResult) {
97 t.Helper()
98 samples := valueSampleMap(got.values)
99 _, okA := samples["perfdata.check_memory.counter_a_value"]
100 _, okB := samples["perfdata.check_memory.counter_b_value"]
101 _, okC := samples["perfdata.check_memory.counter_c_value"]
102 assert.True(t, okA)
103 assert.True(t, okB)
104 assert.False(t, okC)
105 },
106 },
107 "budget keeps stable order for equal metric keys": {
108 budget: 1,
109 input: []output.PerfDatum{
110 {Label: "latency", Unit: "KB", Value: 1},
111 {Label: "latency", Unit: "ms", Value: 1},
112 },
113 assert: func(t *testing.T, got perfRouteResult) {
114 t.Helper()
115 samples := valueSampleMap(got.values)
116 assertNear(t, samples["perfdata.check_memory.bytes_latency_value"], 1_000)
117 _, hasTime := samples["perfdata.check_memory.time_latency_value"]
118 assert.False(t, hasTime)
119 },
120 },
121 "class changes create a new metric identity": {
122 budget: 64,
123 prime: []output.PerfDatum{
124 {Label: "latency", Unit: "ms", Value: 10},
125 },
126 input: []output.PerfDatum{
127 {Label: "latency", Unit: "KB", Value: 10},
128 },
129 assert: func(t *testing.T, got perfRouteResult) {
130 t.Helper()
131 samples := valueSampleMap(got.values)
132 assertNear(t, samples["perfdata.check_memory.bytes_latency_value"], 10_000)
133 },
134 },
135 "invalid samples are ignored": {
136 budget: 64,
137 input: []output.PerfDatum{
138 {Label: "", Unit: "ms", Value: 1},
139 {Label: "bad", Unit: "ms", Value: math.NaN()},
140 },
141 assert: func(t *testing.T, got perfRouteResult) {
142 t.Helper()
143 assert.Empty(t, got.values)
144 assert.Empty(t, got.thresholdStates)
145 },
146 },
147 }
148
149 for name, tc := range tests {
150 t.Run(name, func(t *testing.T) {
151 router := newPerfdataRouter(tc.budget)
152 if len(tc.prime) > 0 {
153 primed := router.route(testCheckName, tc.prime)
154 require.NotEmpty(t, primed.values)
155 }
156
157 got := router.route(testCheckName, tc.input)
158 tc.assert(t, got)
159 })
160 }
161 }
162
163 func TestSanitizeMetricKey(t *testing.T) {
164 tests := map[string]struct {
165 input string
166 expect string
167 expectPrefix string
168 }{
169 "plain text preserves words": {
170 input: "Disk usage",
171 expect: "disk_usage",
172 },
173 "trailing punctuation keeps boundary underscore": {
174 input: "Disk usage /",
175 expect: "disk_usage_",
176 },
177 "non alnum falls back to synthetic id": {
178 input: "///",
179 expectPrefix: "id_",
180 },
181 }
182
183 for name, tc := range tests {
184 t.Run(name, func(t *testing.T) {
185 got := sanitizeMetricKey(tc.input)
186 if tc.expect != "" {
187 assert.Equal(t, tc.expect, got)
188 }
189 if tc.expectPrefix != "" {
190 assert.True(t, strings.HasPrefix(got, tc.expectPrefix))
191 }
192 })
193 }
194 }
195
196 func valueSampleMap(sets []perfValueMeasureSet) map[string]float64 {
197 out := make(map[string]float64, len(sets))
198 for _, set := range sets {
199 out[set.name+"_"+perfFieldValue] = float64(set.value)
200 }
201 return out
202 }
203
204 func valueSampleUnits(sets []perfValueMeasureSet) map[string]string {
205 out := make(map[string]string, len(sets))
206 for _, set := range sets {
207 out[set.name+"_"+perfFieldValue] = set.unit
208 }
209 return out
210 }
211
212 func thresholdStateMap(sets []perfThresholdStateSet) map[string]string {
213 out := make(map[string]string, len(sets))
214 for _, set := range sets {
215 out[set.name] = set.state
216 }
217 return out
218 }
219
220 func thresholdStateLabelValues(sets []perfThresholdStateSet) map[string]string {
221 out := make(map[string]string, len(sets))
222 for _, set := range sets {
223 out[set.name] = set.perfdataValue
224 }
225 return out
226 }
227
228 func assertNear(t *testing.T, got, want float64) {
229 t.Helper()
230 assert.InDelta(t, want, got, 1e-9)
231 }
232
233 func assertString(t *testing.T, got, want string) {
234 t.Helper()
235 assert.Equal(t, want, got)
236 }