master
go 260 lines 6.09 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package nagios
4
5 import (
6 "crypto/sha1"
7 "fmt"
8 "math"
9 "path/filepath"
10 "strings"
11
12 "github.com/netdata/netdata/go/plugins/plugin/scripts.d/collector/nagios/internal/output"
13 )
14
15 type perfUnitClass string
16
17 const (
18 perfClassTime perfUnitClass = "time"
19 perfClassBytes perfUnitClass = "bytes"
20 perfClassBits perfUnitClass = "bits"
21 perfClassPercent perfUnitClass = "percent"
22 perfClassCounter perfUnitClass = "counter"
23 perfClassGeneric perfUnitClass = "generic"
24 )
25
26 type perfPreparedDatum struct {
27 rawLabel string
28 metricKey string
29 class perfUnitClass
30 value float64
31 warn *output.ThresholdRange
32 crit *output.ThresholdRange
33 }
34
35 func preparePerfDatum(datum output.PerfDatum) (perfPreparedDatum, bool) {
36 rawLabel := strings.TrimSpace(datum.Label)
37 if rawLabel == "" || !isFinite(datum.Value) {
38 return perfPreparedDatum{}, false
39 }
40
41 metricKey := sanitizeMetricKey(rawLabel)
42 if metricKey == "" {
43 metricKey = "metric"
44 }
45
46 class, normalized := normalizePerfValue(datum.Unit, datum.Value)
47 item := perfPreparedDatum{
48 rawLabel: rawLabel,
49 metricKey: metricKey,
50 class: class,
51 value: normalized,
52 warn: normalizeThresholdRange(datum.Unit, datum.Warn),
53 crit: normalizeThresholdRange(datum.Unit, datum.Crit),
54 }
55 return item, true
56 }
57
58 func normalizeOptionalFinite(unit string, v *float64) *float64 {
59 if v == nil || !isFinite(*v) {
60 return nil
61 }
62 _, normalized := normalizePerfValue(unit, *v)
63 out := normalized
64 return &out
65 }
66
67 func normalizeThresholdRange(unit string, rng *output.ThresholdRange) *output.ThresholdRange {
68 if rng == nil {
69 return nil
70 }
71 return &output.ThresholdRange{
72 Inclusive: rng.Inclusive,
73 Low: normalizeOptionalFinite(unit, rng.Low),
74 High: normalizeOptionalFinite(unit, rng.High),
75 }
76 }
77
78 func normalizePerfValue(unit string, value float64) (perfUnitClass, float64) {
79 lower := strings.ToLower(strings.TrimSpace(unit))
80 switch lower {
81 case "s", "sec", "secs", "second", "seconds":
82 return perfClassTime, value
83 case "ms", "millisecond", "milliseconds":
84 return perfClassTime, value / 1_000
85 case "us", "µs", "usec", "microsecond", "microseconds":
86 return perfClassTime, value / 1_000_000
87 case "ns", "nanosecond", "nanoseconds":
88 return perfClassTime, value / 1_000_000_000
89 case "%":
90 return perfClassPercent, value
91 case "c":
92 return perfClassCounter, value
93 }
94
95 if class, multiplier, ok := byteOrBitMultiplier(unit); ok {
96 return class, value * multiplier
97 }
98 return perfClassGeneric, value
99 }
100
101 func byteOrBitMultiplier(unit string) (perfUnitClass, float64, bool) {
102 base, ok := trimPerSecondSuffix(unit)
103 if !ok || base == "" {
104 return "", 0, false
105 }
106 class, prefix, ok := splitByteOrBitUnit(base)
107 if !ok {
108 return "", 0, false
109 }
110 multiplier, ok := byteMagnitude(prefix)
111 if !ok {
112 return "", 0, false
113 }
114 return class, multiplier, true
115 }
116
117 func trimPerSecondSuffix(unit string) (string, bool) {
118 trimmed := strings.TrimSpace(unit)
119 lower := strings.ToLower(trimmed)
120 switch {
121 case strings.HasSuffix(lower, "/s"):
122 return strings.TrimSpace(trimmed[:len(trimmed)-2]), true
123 case strings.HasSuffix(lower, "ps"):
124 return strings.TrimSpace(trimmed[:len(trimmed)-2]), true
125 default:
126 return trimmed, true
127 }
128 }
129
130 func splitByteOrBitUnit(unit string) (perfUnitClass, string, bool) {
131 trimmed := strings.TrimSpace(unit)
132 lower := strings.ToLower(trimmed)
133 switch {
134 case strings.HasSuffix(lower, "bytes"):
135 return perfClassBytes, trimmed[:len(trimmed)-5], true
136 case strings.HasSuffix(lower, "byte"):
137 return perfClassBytes, trimmed[:len(trimmed)-4], true
138 case strings.HasSuffix(lower, "bits"):
139 return perfClassBits, trimmed[:len(trimmed)-4], true
140 case strings.HasSuffix(lower, "bit"):
141 return perfClassBits, trimmed[:len(trimmed)-3], true
142 }
143 if trimmed == "" {
144 return "", "", false
145 }
146 switch last := trimmed[len(trimmed)-1]; last {
147 case 'B':
148 return perfClassBytes, trimmed[:len(trimmed)-1], true
149 case 'b':
150 return perfClassBits, trimmed[:len(trimmed)-1], true
151 default:
152 return "", "", false
153 }
154 }
155
156 func byteMagnitude(prefix string) (float64, bool) {
157 switch strings.ToLower(strings.TrimSpace(prefix)) {
158 case "":
159 return 1, true
160 case "k":
161 return 1_000, true
162 case "m":
163 return 1_000_000, true
164 case "g":
165 return 1_000_000_000, true
166 case "t":
167 return 1_000_000_000_000, true
168 default:
169 return 0, false
170 }
171 }
172
173 func isFinite(v float64) bool {
174 return !math.IsNaN(v) && !math.IsInf(v, 0)
175 }
176
177 func unitForClass(class perfUnitClass) string {
178 switch class {
179 case perfClassTime:
180 return "seconds"
181 case perfClassBytes:
182 return "bytes"
183 case perfClassBits:
184 return "bits"
185 case perfClassPercent:
186 return "%"
187 case perfClassCounter:
188 return "c"
189 default:
190 return "generic"
191 }
192 }
193
194 func sanitizeMetricKey(name string) string {
195 lower := strings.ToLower(name)
196 var b strings.Builder
197 b.Grow(len(lower))
198 lastUnderscore := false
199 hasAlnum := false
200 for _, r := range lower {
201 if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
202 b.WriteRune(r)
203 lastUnderscore = false
204 hasAlnum = true
205 continue
206 }
207 if r == '_' || r == '-' || isWhitespace(r) {
208 if !lastUnderscore {
209 b.WriteRune('_')
210 lastUnderscore = true
211 }
212 continue
213 }
214 if !lastUnderscore {
215 b.WriteRune('_')
216 lastUnderscore = true
217 }
218 }
219 result := b.String()
220 if hasAlnum && result != "" {
221 return result
222 }
223 sum := sha1.Sum([]byte(name))
224 return fmt.Sprintf("id_%x", sum[:6])
225 }
226
227 func isWhitespace(r rune) bool {
228 switch r {
229 case ' ', '\t', '\n', '\r':
230 return true
231 }
232 return false
233 }
234
235 func perfSourceFromPlugin(pluginPath string) string {
236 base := filepath.Base(strings.TrimSpace(pluginPath))
237 if base == "" || base == "." || base == string(filepath.Separator) {
238 base = "script"
239 }
240 ext := filepath.Ext(base)
241 if ext != "" {
242 base = strings.TrimSuffix(base, ext)
243 }
244 return sanitizeMetricKey(base)
245 }
246
247 func perfSourceFromCheckName(checkName string) string {
248 trimmed := strings.TrimSpace(checkName)
249 if trimmed == "" {
250 return "check"
251 }
252 return sanitizeMetricKey(trimmed)
253 }
254
255 func normalizedCheckName(checkName, pluginPath string) string {
256 if strings.TrimSpace(checkName) == "" {
257 return perfSourceFromPlugin(pluginPath)
258 }
259 return perfSourceFromCheckName(checkName)
260 }