master
go 155 lines 3.83 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package nagios
4
5 import (
6 "fmt"
7 "math"
8 "sort"
9
10 "github.com/netdata/netdata/go/plugins/plugin/scripts.d/collector/nagios/internal/output"
11 )
12
13 const defaultPerfdataMetricKeyBudget = 64
14
15 type perfdataRouter struct {
16 maxPerJob int
17 }
18
19 func newPerfdataRouter(maxPerJob int) *perfdataRouter {
20 if maxPerJob <= 0 {
21 maxPerJob = defaultPerfdataMetricKeyBudget
22 }
23 return &perfdataRouter{
24 maxPerJob: maxPerJob,
25 }
26 }
27
28 func (r *perfdataRouter) route(checkName string, perf []output.PerfDatum) perfRouteResult {
29 if len(perf) == 0 {
30 return perfRouteResult{}
31 }
32 source := perfSourceFromCheckName(checkName)
33
34 items := make([]perfPreparedDatum, 0, len(perf))
35 for _, datum := range perf {
36 item, ok := preparePerfDatum(datum)
37 if !ok {
38 continue
39 }
40 items = append(items, item)
41 }
42 if len(items) == 0 {
43 return perfRouteResult{}
44 }
45
46 sort.SliceStable(items, func(i, j int) bool {
47 if items[i].rawLabel == items[j].rawLabel {
48 return items[i].metricKey < items[j].metricKey
49 }
50 return items[i].rawLabel < items[j].rawLabel
51 })
52
53 // Collision policy: keep the first final metric identity after lexical raw-label sort.
54 deduped := make([]perfPreparedDatum, 0, len(items))
55 seen := make(map[string]struct{}, len(items))
56 for _, item := range items {
57 identity := perfMetricIdentity(source, item)
58 if _, ok := seen[identity]; ok {
59 continue
60 }
61 seen[identity] = struct{}{}
62 deduped = append(deduped, item)
63 }
64 if len(deduped) == 0 {
65 return perfRouteResult{}
66 }
67
68 // Budget policy: deterministic cap by metric-key lexical order.
69 sort.SliceStable(deduped, func(i, j int) bool {
70 return deduped[i].metricKey < deduped[j].metricKey
71 })
72 if len(deduped) > r.maxPerJob {
73 deduped = deduped[:r.maxPerJob]
74 }
75
76 result := perfRouteResult{
77 values: make([]perfValueMeasureSet, 0, len(deduped)),
78 thresholdStates: make([]perfThresholdStateSet, 0, len(deduped)),
79 }
80 for _, item := range deduped {
81 base := perfMetricIdentity(source, item)
82 tail := perfMetricTail(item)
83 result.values = append(result.values, perfValueMeasureSet{
84 name: base,
85 checkName: source,
86 unit: unitForClass(item.class),
87 counter: item.class == perfClassCounter,
88 value: item.value,
89 })
90
91 if item.class == perfClassCounter {
92 // TODO: Add threshold-state handling for counter perfdata in a follow-up branch.
93 // Nagios counter thresholds are authored against raw totals, while the value family
94 // is emitted with counter semantics and flattened as deltas.
95 continue
96 }
97
98 result.thresholdStates = append(result.thresholdStates, perfThresholdStateSet{
99 name: perfThresholdStateMetricName(base),
100 checkName: source,
101 perfdataValue: tail,
102 state: thresholdStateForPerfDatum(item),
103 })
104 }
105
106 return result
107 }
108
109 func perfMetricIdentity(source string, item perfPreparedDatum) string {
110 return fmt.Sprintf("perfdata.%s.%s", source, perfMetricTail(item))
111 }
112
113 func perfMetricTail(item perfPreparedDatum) string {
114 return fmt.Sprintf("%s_%s", item.class, item.metricKey)
115 }
116
117 func perfThresholdStateMetricName(base string) string {
118 return base + "_threshold_state"
119 }
120
121 func thresholdStateForPerfDatum(item perfPreparedDatum) string {
122 warnDefined := item.warn != nil
123 critDefined := item.crit != nil
124 switch {
125 case !warnDefined && !critDefined:
126 return perfThresholdStateNone
127 case thresholdAlertable(item.value, item.crit):
128 return perfThresholdStateCritical
129 case thresholdAlertable(item.value, item.warn):
130 return perfThresholdStateWarning
131 default:
132 return perfThresholdStateOK
133 }
134 }
135
136 func thresholdAlertable(value float64, rng *output.ThresholdRange) bool {
137 if rng == nil {
138 return false
139 }
140
141 low := math.Inf(-1)
142 if rng.Low != nil {
143 low = *rng.Low
144 }
145 high := math.Inf(1)
146 if rng.High != nil {
147 high = *rng.High
148 }
149
150 inside := value >= low && value <= high
151 if rng.Inclusive {
152 return inside
153 }
154 return !inside
155 }