master
go 373 lines 9.91 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package snmp
4
5 import (
6 "fmt"
7 "maps"
8 "strings"
9
10 "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
11 "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
12 "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
13 )
14
15 const (
16 prioProfileChart = collectorapi.Priority + iota
17 prioPingRtt
18 prioPingStdDev
19 )
20
21 const (
22 prioInternalStatsTimings = prioLicenseState + 1 + iota
23 prioInternalStatsSnmpOps
24 prioInternalStatsMetrics
25 prioInternalStatsTableCache
26 prioInternalStatsErrors
27 )
28
29 var (
30 pingCharts = collectorapi.Charts{
31 pingRTTChart.Copy(),
32 pingRTTStdDevChart.Copy(),
33 }
34 pingRTTChart = collectorapi.Chart{
35 ID: "ping_rtt",
36 Title: "Ping round-trip time",
37 Units: "milliseconds",
38 Fam: "Ping/RTT",
39 Ctx: "snmp.device_ping_rtt",
40 Priority: prioPingRtt,
41 Type: collectorapi.Area,
42 Dims: collectorapi.Dims{
43 {ID: "ping_rtt_min", Name: "min", Div: 1e3},
44 {ID: "ping_rtt_max", Name: "max", Div: 1e3},
45 {ID: "ping_rtt_avg", Name: "avg", Div: 1e3},
46 },
47 }
48 pingRTTStdDevChart = collectorapi.Chart{
49 ID: "ping_rtt_stddev",
50 Title: "Ping round-trip time standard deviation",
51 Units: "milliseconds",
52 Fam: "Ping/RTT",
53 Ctx: "snmp.device_ping_rtt_stddev",
54 Priority: prioPingStdDev,
55 Dims: collectorapi.Dims{
56 {ID: "ping_rtt_stddev", Name: "stddev", Div: 1e3},
57 },
58 }
59 )
60
61 func (c *Collector) addPingCharts() {
62 charts := pingCharts.Copy()
63
64 labels := c.chartBaseLabels()
65
66 for _, chart := range *charts {
67 for k, v := range labels {
68 chart.Labels = append(chart.Labels, collectorapi.Label{Key: k, Value: v})
69 }
70 }
71
72 if err := c.Charts().Add(*charts...); err != nil {
73 c.Warningf("failed to add ping charts: %v", err)
74 }
75 }
76
77 var (
78 profileStatsChartsTmpl = collectorapi.Charts{
79 profileStatsTimingsChartTmpl.Copy(),
80 profileStatsSnmpChartTmpl.Copy(),
81 profileStatsMetricsChartTmpl.Copy(),
82 profileStatsTableCacheChartTmpl.Copy(),
83 profileStatsErrorsChartTmpl.Copy(),
84 }
85
86 profileStatsTimingsChartTmpl = collectorapi.Chart{
87 ID: "snmp_device_prof_%s_stats_timings",
88 Title: "SNMP profile collection timings",
89 Units: "milliseconds",
90 Fam: "Internal/Stats",
91 Ctx: "snmp.device_prof_stats_timings",
92 Priority: prioInternalStatsTimings,
93 Dims: collectorapi.Dims{
94 {ID: "snmp_device_prof_%s_stats_timings_scalar", Name: "scalar"},
95 {ID: "snmp_device_prof_%s_stats_timings_table", Name: "table"},
96 {ID: "snmp_device_prof_%s_stats_timings_licensing", Name: "licensing"},
97 {ID: "snmp_device_prof_%s_stats_timings_bgp", Name: "bgp"},
98 {ID: "snmp_device_prof_%s_stats_timings_virtual", Name: "virtual"},
99 },
100 }
101
102 profileStatsSnmpChartTmpl = collectorapi.Chart{
103 ID: "snmp_device_prof_%s_stats_snmp",
104 Title: "SNMP profile operations",
105 Units: "operations",
106 Fam: "Internal/Stats",
107 Ctx: "snmp.device_prof_stats_snmp",
108 Priority: prioInternalStatsSnmpOps,
109 Dims: collectorapi.Dims{
110 {ID: "snmp_device_prof_%s_stats_snmp_get_requests", Name: "get_requests"},
111 {ID: "snmp_device_prof_%s_stats_snmp_get_oids", Name: "get_oids"},
112 {ID: "snmp_device_prof_%s_stats_snmp_walk_requests", Name: "walk_requests"},
113 {ID: "snmp_device_prof_%s_stats_snmp_walk_pdus", Name: "walk_pdus"},
114 {ID: "snmp_device_prof_%s_stats_snmp_tables_walked", Name: "tables_walked"},
115 {ID: "snmp_device_prof_%s_stats_snmp_tables_cached", Name: "tables_cached"},
116 },
117 }
118
119 profileStatsMetricsChartTmpl = collectorapi.Chart{
120 ID: "snmp_device_prof_%s_stats_metrics",
121 Title: "SNMP profile metric counts",
122 Units: "metrics",
123 Fam: "Internal/Stats",
124 Ctx: "snmp.device_prof_stats_metrics",
125 Priority: prioInternalStatsMetrics,
126 Dims: collectorapi.Dims{
127 {ID: "snmp_device_prof_%s_stats_metrics_scalar", Name: "scalar"},
128 {ID: "snmp_device_prof_%s_stats_metrics_table", Name: "table"},
129 {ID: "snmp_device_prof_%s_stats_metrics_virtual", Name: "virtual"},
130 {ID: "snmp_device_prof_%s_stats_metrics_licensing", Name: "licensing"},
131 {ID: "snmp_device_prof_%s_stats_metrics_bgp", Name: "bgp"},
132 {ID: "snmp_device_prof_%s_stats_metrics_tables", Name: "tables"},
133 {ID: "snmp_device_prof_%s_stats_metrics_rows", Name: "rows"},
134 },
135 }
136
137 profileStatsTableCacheChartTmpl = collectorapi.Chart{
138 ID: "snmp_device_prof_%s_stats_table_cache",
139 Title: "SNMP profile table cache",
140 Units: "tables",
141 Fam: "Internal/Stats",
142 Ctx: "snmp.device_prof_stats_table_cache",
143 Priority: prioInternalStatsTableCache,
144 Dims: collectorapi.Dims{
145 {ID: "snmp_device_prof_%s_stats_table_cache_hits", Name: "hits"},
146 {ID: "snmp_device_prof_%s_stats_table_cache_misses", Name: "misses"},
147 },
148 }
149
150 profileStatsErrorsChartTmpl = collectorapi.Chart{
151 ID: "snmp_device_prof_%s_stats_errors",
152 Title: "SNMP profile errors",
153 Units: "errors",
154 Fam: "Internal/Stats",
155 Ctx: "snmp.device_prof_stats_errors",
156 Priority: prioInternalStatsErrors,
157 Dims: collectorapi.Dims{
158 {ID: "snmp_device_prof_%s_stats_errors_snmp", Name: "snmp"},
159 {ID: "snmp_device_prof_%s_stats_errors_processing_scalar", Name: "processing_scalar"},
160 {ID: "snmp_device_prof_%s_stats_errors_processing_table", Name: "processing_table"},
161 {ID: "snmp_device_prof_%s_stats_errors_processing_licensing", Name: "processing_licensing"},
162 {ID: "snmp_device_prof_%s_stats_errors_processing_bgp", Name: "processing_bgp"},
163 },
164 }
165 )
166
167 func (c *Collector) addProfileStatsCharts(name string) {
168 if name == "" {
169 return
170 }
171
172 charts := profileStatsChartsTmpl.Copy()
173
174 labels := c.chartBaseLabels()
175 labels["profile"] = name
176
177 for _, chart := range *charts {
178 chart.ID = fmt.Sprintf(chart.ID, name)
179 for _, dim := range chart.Dims {
180 dim.ID = fmt.Sprintf(dim.ID, name)
181 }
182 for k, v := range labels {
183 chart.Labels = append(chart.Labels, collectorapi.Label{Key: k, Value: v})
184 }
185 }
186
187 if err := c.Charts().Add(*charts...); err != nil {
188 c.Warningf("failed to add profile stats charts for %s: %v", name, err)
189 }
190 }
191
192 func (c *Collector) addProfileScalarMetricChart(m ddsnmp.Metric) {
193 if m.Name == "" {
194 return
195 }
196
197 chart := &collectorapi.Chart{
198 ID: chartIDFromName(m.Name),
199 Title: m.Description,
200 Type: collectorapi.ChartType(m.ChartType),
201 Units: m.Unit,
202 Fam: m.Family,
203 Ctx: chartContextID(m.Name),
204 Priority: prioProfileChart,
205 }
206 if chart.Title == "" {
207 chart.Title = fmt.Sprintf("SNMP metric %s", m.Name)
208 }
209 if chart.Units == "" {
210 chart.Units = "1"
211 }
212 if chart.Fam == "" {
213 chart.Fam = m.Name
214 }
215 if chart.Units == "bit/s" {
216 chart.Type = collectorapi.Area
217 }
218
219 tags := c.chartBaseLabels()
220
221 maps.Copy(tags, m.Profile.Tags)
222 addMetricTagLabels(tags, m.Tags)
223 for k, v := range tags {
224 chart.Labels = append(chart.Labels, collectorapi.Label{Key: k, Value: v})
225 }
226
227 if len(m.MultiValue) > 0 {
228 seen := make(map[string]bool)
229 for k := range m.MultiValue {
230 if !seen[k] {
231 seen[k] = true
232 id := metricIDFromName(m.Name, k)
233 chart.Dims = append(chart.Dims, &collectorapi.Dim{ID: id, Name: k, Algo: dimAlgoFromDdSnmpType(m)})
234 }
235 }
236 } else {
237 id := metricIDFromName(m.Name)
238 chart.Dims = collectorapi.Dims{
239 {ID: id, Name: m.Name, Algo: dimAlgoFromDdSnmpType(m)},
240 }
241 }
242
243 if err := c.Charts().Add(chart); err != nil {
244 c.Warning(err)
245 }
246 }
247
248 func (c *Collector) addProfileTableMetricChart(m ddsnmp.Metric) {
249 if m.Name == "" {
250 return
251 }
252
253 key := tableMetricKey(m)
254
255 chart := &collectorapi.Chart{
256 ID: chartIDFromKey(key),
257 Title: m.Description,
258 Units: m.Unit,
259 Fam: m.Family,
260 Ctx: chartContextID(m.Name),
261 Priority: prioProfileChart,
262 }
263 if chart.Title == "" {
264 chart.Title = fmt.Sprintf("SNMP metric %s", m.Name)
265 }
266 if chart.Units == "" {
267 chart.Units = "1"
268 }
269 if chart.Fam == "" {
270 chart.Fam = m.Name
271 }
272 if chart.Units == "bit/s" {
273 chart.Type = collectorapi.Area
274 }
275
276 tags := c.chartBaseLabels()
277
278 maps.Copy(tags, m.Profile.Tags)
279 addMetricTagLabels(tags, m.Tags)
280
281 for k, v := range tags {
282 chart.Labels = append(chart.Labels, collectorapi.Label{Key: k, Value: v})
283 }
284
285 if len(m.MultiValue) > 0 {
286 seen := make(map[string]bool)
287 for k := range m.MultiValue {
288 if !seen[k] {
289 seen[k] = true
290 id := metricIDFromKey(key, k)
291 chart.Dims = append(chart.Dims, &collectorapi.Dim{ID: id, Name: k, Algo: dimAlgoFromDdSnmpType(m)})
292 }
293 }
294 } else {
295 id := metricIDFromKey(key)
296 chart.Dims = collectorapi.Dims{
297 {ID: id, Name: m.Name, Algo: dimAlgoFromDdSnmpType(m)},
298 }
299 }
300
301 if err := c.Charts().Add(chart); err != nil {
302 c.Warning(err)
303 }
304 }
305
306 func (c *Collector) removeProfileTableMetricChart(key string) {
307 id := chartIDFromKey(key)
308 if chart := c.Charts().Get(id); chart != nil {
309 chart.MarkRemove()
310 chart.MarkNotCreated()
311 }
312 }
313
314 func (c *Collector) chartBaseLabels() map[string]string {
315 si := c.sysInfo
316
317 labels := map[string]string{
318 "sysName": si.Name,
319 "address": c.Hostname,
320 }
321
322 if si.Vendor != "" {
323 labels["vendor"] = si.Vendor
324 } else if si.Organization != "" {
325 labels["vendor"] = si.Organization
326 }
327 if si.Category != "" {
328 labels["device_type"] = si.Category
329 }
330 if si.Model != "" {
331 labels["model"] = si.Model
332 }
333
334 return labels
335 }
336
337 func addMetricTagLabels(labels, tags map[string]string) {
338 for k, v := range tags {
339 if strings.HasPrefix(k, "_") {
340 continue
341 }
342 addMetricTagLabel(labels, k, v)
343 }
344 for k, v := range tags {
345 if !strings.HasPrefix(k, "_") {
346 continue
347 }
348 key := strings.TrimPrefix(k, "_")
349 if key == "" {
350 continue
351 }
352 addMetricTagLabel(labels, key, v)
353 }
354 }
355
356 func addMetricTagLabel(labels map[string]string, key, value string) {
357 if existing, ok := labels[key]; !ok || existing == "" {
358 labels[key] = value
359 }
360 }
361
362 func dimAlgoFromDdSnmpType(m ddsnmp.Metric) collectorapi.DimAlgo {
363 switch m.MetricType {
364 case ddprofiledefinition.ProfileMetricTypeGauge,
365 ddprofiledefinition.ProfileMetricTypeMonotonicCount,
366 ddprofiledefinition.ProfileMetricTypeMonotonicCountAndRate:
367 return collectorapi.Absolute
368 default:
369 return collectorapi.Incremental
370 }
371 }
372
373 var cleanMetricName = strings.NewReplacer(".", "_", " ", "_")