@cryptotaxi247 / netdata-1 / commits / ebe6daecc

SNMP Collector, use custom YAML files for auto single metrics (#20020)

* work so far, units and description * tgo mod tidy * revert custom units and description * remove custom package go mod entries * tidy up go.mod * fix dimension initialization * fix tests and include new code due to new snmpGet run * disable new funct * change priority back * add enableProfile field --------- Co-authored-by: ilyam8 <ilya@netdata.cloud>

Fotis Voutsas committed Apr 9, 2025 at 13:27 UTC ebe6daecc78319f47d2721d175ce02e1b3b4becf
9 files changed +186 -721
src/go/go.mod
+1 -1
@@ -58,7 +58,6 @@ require (
58 gopkg.in/ini.v1 v1.67.0
59 gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.2
60 gopkg.in/yaml.v2 v2.4.0
61 - gopkg.in/yaml.v3 v3.0.1
61 k8s.io/api v0.32.3
62 k8s.io/apimachinery v0.32.3
63 k8s.io/client-go v0.32.3
@@ -165,6 +164,7 @@ require (
164 gopkg.in/cenkalti/backoff.v2 v2.2.1 // indirect
165 gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
166 gopkg.in/inf.v0 v0.9.1 // indirect
167 + gopkg.in/yaml.v3 v3.0.1 // indirect
168 k8s.io/klog/v2 v2.130.1 // indirect
169 k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
170 k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
src/go/plugin/go.d/collector/snmp/charts.go
+46
@@ -19,6 +19,8 @@ const (
19 prioNetIfaceAdminStatus
20 prioNetIfaceOperStatus
21 prioSysUptime
22 +
23 + priosnmp
24 )
25
26 var netIfaceChartsTmpl = module.Charts{
@@ -32,6 +34,20 @@ var netIfaceChartsTmpl = module.Charts{
34 netIfaceOperStatusChartTmpl.Copy(),
35 }
36
37 +var (
38 + snmpChartTemplate = module.Chart{
39 + ID: "%s",
40 + Title: "%s",
41 + Units: "%s",
42 + Fam: "%s",
43 + Ctx: "snmp.%s",
44 + Priority: priosnmp,
45 + Dims: module.Dims{
46 + {ID: "%s", Name: "%s"},
47 + },
48 + }
49 +)
50 +
51 var (
52 netIfaceTrafficChartTmpl = module.Chart{
53 ID: "snmp_device_net_iface_%s_traffic",
@@ -178,6 +194,36 @@ func (c *Collector) addNetIfaceCharts(iface *netInterface) {
194 }
195 }
196
197 +func (c *Collector) addSNMPChart(processedMetric processedMetric) {
198 + if processedMetric.tableName == "" {
199 + chart := snmpChartTemplate.Copy()
200 +
201 + chart.ID = fmt.Sprintf(chart.ID, processedMetric.name)
202 + chart.Title = fmt.Sprintf(chart.Title, processedMetric.name)
203 + chart.Units = fmt.Sprintf(chart.Units, "TBD unit")
204 + chart.Ctx = fmt.Sprintf(chart.Ctx, processedMetric.name)
205 + chart.Fam = fmt.Sprintf(chart.Fam, processedMetric.name)
206 +
207 + for _, dim := range chart.Dims {
208 + dim.ID = fmt.Sprintf(dim.ID, processedMetric.name)
209 + dim.Name = fmt.Sprintf(dim.Name, processedMetric.name)
210 + }
211 +
212 + if err := c.Charts().Add(chart); err != nil {
213 + c.Warning(err)
214 + }
215 + }
216 +}
217 +
218 +func (c *Collector) removeSNMPChart(name string) {
219 + for _, chart := range *c.Charts() {
220 + if chart.ID == name {
221 + chart.MarkRemove()
222 + chart.MarkNotCreated()
223 + }
224 + }
225 +}
226 +
227 func (c *Collector) removeNetIfaceCharts(iface *netInterface) {
228 px := fmt.Sprintf("snmp_device_net_iface_%s_", cleanIfaceName(iface.ifName))
229 for _, chart := range *c.Charts() {
src/go/plugin/go.d/collector/snmp/collect.go
+43 -9
@@ -5,17 +5,36 @@ package snmp
5 import (
6 "errors"
7 "fmt"
8 + "log"
9 "slices"
10 "strings"
11
11 - "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/discovery/sd/discoverer/snmpsd"
12 - "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/vnodes"
13 -
12 "github.com/google/uuid"
13 "github.com/gosnmp/gosnmp"
14 +
15 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/discovery/sd/discoverer/snmpsd"
16 + "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/vnodes"
17 + "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
18 )
19
20 func (c *Collector) collect() (map[string]int64, error) {
21 + if c.enableProfiles {
22 + sysObjectID, err := c.getSysObjectID(snmpsd.OidSysObject)
23 + if err != nil {
24 + return nil, err
25 + }
26 +
27 + matchingProfiles := ddsnmp.Find(sysObjectID)
28 +
29 + metricMap, err := c.parseMetricsFromProfiles(matchingProfiles)
30 + if err != nil {
31 + return nil, err
32 + }
33 + seen := make(map[string]bool)
34 + mx := make(map[string]int64)
35 + c.makeChartsFromMetricMap(mx, metricMap, seen)
36 + }
37 +
38 if c.sysInfo == nil {
39 si, err := snmpsd.GetSysInfo(c.snmpClient)
40 if err != nil {
@@ -56,24 +75,39 @@ func (c *Collector) getSysObjectID(oid string) (string, error) {
75 if err != nil {
76 return "", err
77 }
59 -
78 return strings.Replace(resp.Variables[0].Value.(string), ".", "", 1), nil
79 }
80
63 -func (c *Collector) makeChartsFromMetricMap(mx map[string]int64, metricMap map[string]processedMetric) error {
64 -
81 +func (c *Collector) makeChartsFromMetricMap(mx map[string]int64, metricMap map[string]processedMetric, seen map[string]bool) error {
82 for _, metric := range metricMap {
83 if metric.tableName == "" {
84 switch s := metric.value.(type) {
85 case int:
86
70 - // log.Println(metric)
87 + log.Println(metric)
88
72 - // c.addSNMPChart(metric)
73 - mx[metric.name] = int64(s)
89 + name := metric.name
90 + if name == "" {
91 + continue
92 + }
93
94 + seen[name] = true
95 +
96 + if !c.seenMetrics[name] {
97 + c.seenMetrics[name] = true
98 + c.addSNMPChart(metric)
99 + }
100 +
101 + mx[metric.name] = int64(s)
102 }
103 }
104 +
105 + }
106 + for name := range c.seenMetrics {
107 + if !seen[name] {
108 + delete(c.seenMetrics, name)
109 + c.removeSNMPChart(name)
110 + }
111 }
112 return nil
113 }
src/go/plugin/go.d/collector/snmp/collector.go
+8 -1
@@ -50,6 +50,10 @@ func New() *Collector {
50 },
51 },
52
53 + charts: &module.Charts{},
54 +
55 + seenMetrics: make(map[string]bool),
56 +
57 newSnmpClient: gosnmp.NewHandler,
58
59 checkMaxReps: true,
@@ -62,9 +66,12 @@ type Collector struct {
66 module.Base
67 Config `yaml:",inline" json:""`
68
69 + enableProfiles bool
70 +
71 vnode *vnodes.VirtualNode
72
67 - charts *module.Charts
73 + charts *module.Charts
74 + seenMetrics map[string]bool
75
76 newSnmpClient func() gosnmp.Handler
77 snmpClient gosnmp.Handler
src/go/plugin/go.d/collector/snmp/collector_test.go
+52 -1
@@ -156,6 +156,9 @@ func TestCollector_Charts(t *testing.T) {
156 prepareSNMP: func(t *testing.T, m *snmpmock.MockHandler) *Collector {
157 collr := New()
158 collr.Config = prepareV2Config()
159 + if collr.enableProfiles {
160 + setMockClientSysObjectidExpect(m)
161 + }
162 setMockClientSysExpect(m)
163 setMockClientIfMibExpect(m)
164
@@ -205,6 +208,9 @@ func TestCollector_Check(t *testing.T) {
208 prepareSNMP: func(m *snmpmock.MockHandler) *Collector {
209 collr := New()
210 collr.Config = prepareV2Config()
211 + if collr.enableProfiles {
212 + setMockClientSysObjectidExpect(m)
213 + }
214 setMockClientIfMibExpect(m)
215
216 return collr
@@ -217,6 +223,10 @@ func TestCollector_Check(t *testing.T) {
223 collr.Config = prepareConfigWithUserCharts(prepareV2Config(), 0, 3)
224 collr.collectIfMib = false
225
226 + if collr.enableProfiles {
227 + setMockClientSysObjectidExpect(m)
228 + }
229 +
230 m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
231 Variables: []gosnmp.SnmpPDU{
232 {Value: 10, Type: gosnmp.Counter32},
@@ -239,7 +249,9 @@ func TestCollector_Check(t *testing.T) {
249 collr := New()
250 collr.Config = prepareConfigWithUserCharts(prepareV2Config(), 0, 3)
251 collr.collectIfMib = false
242 -
252 + if collr.enableProfiles {
253 + setMockClientSysObjectidExpect(m)
254 + }
255 m.EXPECT().Get(gomock.Any()).Return(nil, errors.New("mock Get() error")).Times(1)
256
257 return collr
@@ -279,11 +291,15 @@ func TestCollector_Collect(t *testing.T) {
291 collr := New()
292 collr.Config = prepareV2Config()
293
294 + if collr.enableProfiles {
295 + setMockClientSysObjectidExpect(m)
296 + }
297 setMockClientIfMibExpect(m)
298
299 return collr
300 },
301 wantCollected: map[string]int64{
302 + //"TestMetric": 1,
303 "net_iface_ether1_admin_status_down": 0,
304 "net_iface_ether1_admin_status_testing": 0,
305 "net_iface_ether1_admin_status_up": 1,
@@ -381,6 +397,10 @@ func TestCollector_Collect(t *testing.T) {
397 collr.Config = prepareConfigWithUserCharts(prepareV2Config(), 0, 3)
398 collr.collectIfMib = false
399
400 + if collr.enableProfiles {
401 + setMockClientSysObjectidExpect(m)
402 + }
403 +
404 m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
405 Variables: []gosnmp.SnmpPDU{
406 {Value: 10, Type: gosnmp.Counter32},
@@ -397,6 +417,7 @@ func TestCollector_Collect(t *testing.T) {
417 return collr
418 },
419 wantCollected: map[string]int64{
420 + //"TestMetric": 1,
421 "1.3.6.1.2.1.2.2.1.10.0": 10,
422 "1.3.6.1.2.1.2.2.1.16.0": 20,
423 "1.3.6.1.2.1.2.2.1.10.1": 30,
@@ -414,6 +435,10 @@ func TestCollector_Collect(t *testing.T) {
435 collr.Config = prepareConfigWithUserCharts(prepareV2Config(), 0, 2)
436 collr.collectIfMib = false
437
438 + if collr.enableProfiles {
439 + setMockClientSysObjectidExpect(m)
440 + }
441 +
442 m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
443 Variables: []gosnmp.SnmpPDU{
444 {Value: 10, Type: gosnmp.Counter32},
@@ -428,6 +453,7 @@ func TestCollector_Collect(t *testing.T) {
453 return collr
454 },
455 wantCollected: map[string]int64{
456 + //"TestMetric": 1,
457 "1.3.6.1.2.1.2.2.1.10.0": 10,
458 "1.3.6.1.2.1.2.2.1.16.0": 20,
459 "1.3.6.1.2.1.2.2.1.10.1": 30,
@@ -440,6 +466,10 @@ func TestCollector_Collect(t *testing.T) {
466 collr.Config = prepareConfigWithUserCharts(prepareV2Config(), 0, 2)
467 collr.collectIfMib = false
468
469 + if collr.enableProfiles {
470 + setMockClientSysObjectidExpect(m)
471 + }
472 +
473 m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
474 Variables: []gosnmp.SnmpPDU{
475 {Value: nil, Type: gosnmp.NoSuchInstance},
@@ -454,6 +484,7 @@ func TestCollector_Collect(t *testing.T) {
484 return collr
485 },
486 wantCollected: map[string]int64{
487 + //"TestMetric": 1,
488 "uptime": 60,
489 },
490 },
@@ -474,6 +505,10 @@ func TestCollector_Collect(t *testing.T) {
505
506 mx := collr.Collect(context.Background())
507
508 + if collr.enableProfiles {
509 + mx["TestMetric"] = 1
510 + }
511 +
512 assert.Equal(t, test.wantCollected, mx)
513 })
514 }
@@ -581,6 +616,22 @@ func setMockClientInitExpect(m *snmpmock.MockHandler) {
616 m.EXPECT().Connect().Return(nil).AnyTimes()
617 }
618
619 +func setMockClientSysObjectidExpect(m *snmpmock.MockHandler) {
620 + m.EXPECT().Get([]string{snmpsd.OidSysObject}).Return(&gosnmp.SnmpPacket{
621 + Variables: []gosnmp.SnmpPDU{
622 + {Value: ".1.1.1",
623 + Name: ".1.3.6.1.2.1.1.2.0",
624 + Type: gosnmp.ObjectIdentifier},
625 + },
626 + }, nil).MinTimes(1)
627 + m.EXPECT().Get([]string{"1.1.1.0"}).Return(&gosnmp.SnmpPacket{
628 + Variables: []gosnmp.SnmpPDU{
629 + {Name: "1.1.1.0", Value: 1, Type: gosnmp.Integer},
630 + },
631 + }, nil).MinTimes(1)
632 +
633 +}
634 +
635 func setMockClientSysExpect(m *snmpmock.MockHandler) {
636 m.EXPECT().WalkAll(snmpsd.RootOidMibSystem).Return([]gosnmp.SnmpPDU{
637 {Name: snmpsd.OidSysDescr, Value: []uint8("mock sysDescr"), Type: gosnmp.OctetString},
src/go/plugin/go.d/collector/snmp/helpers.go
+12 -12
@@ -13,18 +13,18 @@ func sliceToStrings(items []interface{}) []string {
13 return strs
14 }
15
16 -func sliceToTableMetricTags(items []interface{}) []TableMetricTag {
17 - var metricTag []TableMetricTag
18 - for _, v := range items {
19 - s, ok := v.(TableMetricTag)
20 - if !ok {
21 - // Handle error if an element is not a string.
22 - continue
23 - }
24 - metricTag = append(metricTag, s)
25 - }
26 - return metricTag
27 -}
16 +// func sliceToTableMetricTags(items []interface{}) []TableMetricTag {
17 +// var metricTag []TableMetricTag
18 +// for _, v := range items {
19 +// s, ok := v.(TableMetricTag)
20 +// if !ok {
21 +// // Handle error if an element is not a string.
22 +// continue
23 +// }
24 +// metricTag = append(metricTag, s)
25 +// }
26 +// return metricTag
27 +// }
28
29 func mergeTableBatches(target tableBatches, source tableBatches) tableBatches {
30 merged := tableBatches{}
src/go/plugin/go.d/collector/snmp/parsing.go
+19 -509
@@ -3,12 +3,12 @@ package snmp
3 import (
4 "errors"
5 "fmt"
6 - "log"
7 - "reflect"
6 "regexp"
7 +
8 + "github.com/DataDog/datadog-agent/pkg/networkdevice/profile/profiledefinition"
9 )
10
11 -func parseMetrics(metrics []Metric) (parsedResult, error) {
11 +func parseMetrics(metrics []profiledefinition.MetricsConfig) (parsedResult, error) {
12 oids := []string{}
13 next_oids := []string{}
14 bulk_oids := []string{}
@@ -49,7 +49,7 @@ func parseMetrics(metrics []Metric) (parsedResult, error) {
49 next_oids: next_oids, bulk_oids: bulk_oids, parsed_metrics: parsed_metrics}, nil
50 }
51
52 -func parseMetric(metric Metric) (metricParseResult, error) {
52 +func parseMetric(metric profiledefinition.MetricsConfig) (metricParseResult, error) {
53 /*Can either be:
54
55 * An OID metric:
@@ -83,177 +83,26 @@ func parseMetric(metric Metric) (metricParseResult, error) {
83 name: ifInErrors
84 ```*/
85
86 - // Cast the tags to either array
87 - castedStringMetricTags := []string{}
88 - castedTableMetricTags := []TableMetricTag{}
89 -
90 - if len(metric.MetricTags) > 0 {
91 - /*//TODO investigate if there are metric tags and not only table metric tags
92 - fmt.Println("parseMetric MetricTags switch, have:")
93 - spew.Dump(metric.MetricTags)
94 - switch metric.MetricTags[0].(type) {
95 - case string:
96 - os.Exit(-40)
97 - castedStringMetricTags = sliceToStrings(metric.MetricTags)
98 -
99 - case MetricTag:
100 - os.Exit(-40)
101 - castedTableMetricTags = sliceToTableMetricTags(metric.MetricTags)
102 - }*/
103 - }
86 + // Can't support tags at the moment
87
88 if len(metric.OID) > 0 {
89 // TODO investigate if this exists in the yamls
107 - return (parseOIDMetric(oidMetric{name: metric.Name, oid: metric.OID, metricTags: castedStringMetricTags, forcedType: metric.MetricType, options: metric.Options})), nil
90 + // return (parseOIDMetric(oidMetric{name: metric.Name, oid: metric.OID, metricTags: castedStringMetricTags, forcedType: string(metric.MetricType), options: metric.Options})), nil
91 + return metricParseResult{}, nil
92 +
93 } else if len(metric.MIB) == 0 {
94 return metricParseResult{}, fmt.Errorf("unsupported metric {%v}", metric)
110 - } else if metric.Symbol != (Symbol{}) {
95 + } else if metric.Symbol != (profiledefinition.SymbolConfig{}) {
96 // Single Metric
112 - return (parseSymbolMetric(symbolMetric{mib: metric.MIB, symbol: metric.Symbol, forcedType: metric.MetricType, metricTags: castedStringMetricTags, options: metric.Options}))
113 - } else if metric.Table != nil {
114 - // Table
115 - if len(metric.MetricTags) > 0 {
116 -
117 - /*//This will be cleared out when tags are supported
118 - // for _, rawItem := range metric.MetricTags {
119 - // item, ok := rawItem.(map[string]interface{})
120 - // if !ok {
121 - // continue
122 - // }
123 - // fmt.Println("ITEM", item["symbol"])
124 -
125 - // var index int
126 - // if val, exists := item["Index"]; exists {
127 - // if i, ok := val.(int); ok {
128 - // index = i
129 - // } else {
130 - // index = -1
131 - // }
132 - // }
133 -
134 - // var mapping map[int]string
135 - // if val, exists := item["mapping"]; exists {
136 - // if m, ok := val.(map[int]string); ok {
137 - // fmt.Print("IN")
138 - // os.Exit(-90)
139 - // mapping = m
140 - // }
141 - // }
142 -
143 - // var tag string
144 - // if val, exists := item["Tag"]; exists {
145 - // if s, ok := val.(string); ok {
146 - // tag = s
147 - // }
148 - // }
149 -
150 - // var symbol Symbol
151 - // if rawSymbol, exists := item["symbol"]; exists {
152 - // if symMap, ok := rawSymbol.(map[string]interface{}); ok {
153 - // var oid, name string
154 -
155 - // if v, exists := symMap["OID"]; exists {
156 - // if oidStr, ok := v.(string); ok {
157 - // oid = oidStr
158 - // }
159 - // }
160 -
161 - // if v, exists := symMap["name"]; exists {
162 - // if nameStr, ok := v.(string); ok {
163 - // name = nameStr
164 - // }
165 - // }
166 -
167 - // symbol = Symbol{OID: oid, Name: name}
168 - // } else {
169 - // fmt.Println("symbol is not a map[string]interface{}")
170 - // }
171 - // }
172 -
173 - // var table string
174 - // if val, exists := item["Table"]; exists {
175 - // if s, ok := val.(string); ok {
176 - // table = s
177 - // } else {
178 - // table = ""
179 - // }
180 - // }
181 -
182 - // var mib string
183 - // if val, exists := item["MIB"]; exists {
184 - // if s, ok := val.(string); ok {
185 - // mib = s
186 - // } else {
187 - // mib = ""
188 - // }
189 - // }
190 -
191 - // var indexTransform []IndexSlice
192 - // if val, exists := item["IndexTransform"]; exists {
193 - // if xs, ok := val.([]IndexSlice); ok {
194 - // indexTransform = xs
195 - // }
196 - // }*/
197 -
198 - castedTableMetricTags = append(castedTableMetricTags, metric.MetricTags...)
199 - }
200 -
201 - if metric.Symbols == nil {
202 - return metricParseResult{}, fmt.Errorf("when specifying a table, you must specify a list of symbols %v", metric)
203 - }
204 -
205 - return (parseTableMetric(tableMetric{
206 - mib: metric.MIB,
207 - table: metric.Table,
208 - symbols: metric.Symbols,
209 - forcedType: metric.MetricType,
210 - metricTags: castedTableMetricTags,
211 - options: metric.Options,
212 - }))
213 -
214 - }
215 - return metricParseResult{}, fmt.Errorf("unsupported metric {%v}", metric)
216 -}
217 -
218 -// TODO error outs on functions
219 -func parseOIDMetric(metric oidMetric) metricParseResult {
220 - /*Parse a fully resolved OID/name metric.
221 -
222 - Note: This `OID/name` syntax is deprecated in favour of `symbol` syntax.
223 -
224 - Example:
225 -
226 - ```
227 - metrics:
228 - - OID: 1.3.6.1.2.1.2.1
229 - name: ifNumber
230 - ```
231 - */
232 - name := metric.name
233 - oid := metric.oid
234 -
235 - // TODO can't find a profile with this metric type
236 -
237 - parsed_symbol_metric := parsedSymbolMetric{
238 - name: name,
239 - tags: metric.metricTags,
240 - forcedType: metric.forcedType,
241 - enforceScalar: true,
242 - options: metric.options,
243 - baseoid: oid,
244 - }
245 -
246 - return metricParseResult{
247 - oidsToFetch: []string{oid},
248 - oidsToResolve: map[string]string{name: oid},
249 - parsedMetrics: []parsedMetric{parsed_symbol_metric},
250 - tableBatches: nil,
251 - indexMappings: nil,
97 + return (parseSymbolMetric(metric.Symbol, metric.MIB)) // TODO metric tags might be needed here.
98 + //Can't support tables at the moment
99 + } else {
100 + return metricParseResult{}, nil
101 }
102 }
103
104 // TODO error outs on functions
256 -func parseSymbolMetric(metric symbolMetric) (metricParseResult, error) {
105 +func parseSymbolMetric(symbol profiledefinition.SymbolConfig, mib string) (metricParseResult, error) {
106 /* Parse a symbol metric (= an OID in a MIB).
107 Example:
108
@@ -268,8 +117,7 @@ func parseSymbolMetric(metric symbolMetric) (metricParseResult, error) {
117 - MIB: IF-MIB
118 symbol: tcpActiveOpens # require MIB syntax
119 ```*/
271 - mib := metric.mib
272 - symbol := metric.symbol
120 +
121 parsed_symbol, err := parseSymbol(mib, symbol)
122 if err != nil {
123 return metricParseResult{}, err
@@ -277,10 +125,10 @@ func parseSymbolMetric(metric symbolMetric) (metricParseResult, error) {
125
126 parsed_symbol_metric := parsedSymbolMetric{
127 name: parsed_symbol.name,
280 - tags: metric.metricTags,
281 - forcedType: metric.forcedType,
128 + tags: nil,
129 + forcedType: string(symbol.MetricType),
130 enforceScalar: false,
283 - options: metric.options,
131 + options: nil,
132 extractValuePattern: parsed_symbol.extractValuePattern,
133 baseoid: parsed_symbol.oid,
134 }
@@ -294,341 +142,6 @@ func parseSymbolMetric(metric symbolMetric) (metricParseResult, error) {
142 }, nil
143 }
144
297 -// TODO error outs on functions
298 -func parseTableMetric(metric tableMetric) (metricParseResult, error) {
299 -
300 - mib := metric.mib
301 - parsed_table, err := parseSymbol(mib, metric.table)
302 - if err != nil {
303 - return metricParseResult{}, err
304 - }
305 -
306 - table_name := parsed_table.name
307 - table_oid := parsed_table.oid
308 -
309 - oids_to_resolve := parsed_table.oidsToResolve
310 -
311 - var index_tags []indexTag
312 - var column_tags []columnTag
313 - var index_mappings []indexMapping
314 - var table_batches map[tableBatchKey]tableBatch
315 -
316 - if metric.metricTags != nil {
317 - for _, metric_tag := range metric.metricTags {
318 - parsed_table_metric_tag, err := parseTableMetricTag(mib, parsed_table, metric_tag)
319 - if err != nil {
320 - return metricParseResult{}, err
321 - }
322 -
323 - if parsed_table_metric_tag.oidsToResolve != nil {
324 - oids_to_resolve = mergeStringMaps(oids_to_resolve, parsed_table_metric_tag.oidsToResolve)
325 -
326 - column_tags = append(column_tags, parsed_table_metric_tag.columnTags...)
327 -
328 - table_batches = mergeTableBatches(table_batches, parsed_table_metric_tag.tableBatches)
329 - } else {
330 -
331 - index_tags = append(index_tags, parsed_table_metric_tag.indexTags...)
332 -
333 - for index, mapping := range parsed_table_metric_tag.indexMappings {
334 - for _, symbol := range metric.symbols {
335 - index_mappings = append(index_mappings, indexMapping{tag: symbol.Name, index: index, mapping: mapping})
336 - }
337 -
338 - for _, tag := range metric.metricTags {
339 - if reflect.DeepEqual(tag.Symbol, Symbol{}) {
340 - tag = TableMetricTag{
341 - Tag: tag.Tag,
342 - Symbol: tag.Symbol,
343 - }
344 - index_mappings = append(index_mappings, indexMapping{
345 - tag: tag.Symbol.Name,
346 - index: index,
347 - mapping: mapping,
348 - })
349 - }
350 - }
351 - }
352 - }
353 - }
354 - }
355 -
356 - table_oids := []string{}
357 - parsed_metrics := []parsedMetric{}
358 -
359 - for _, symbol := range metric.symbols {
360 - parsed_symbol, err := parseSymbol(mib, symbol)
361 - if err != nil {
362 - return metricParseResult{}, nil
363 - }
364 -
365 - for key, value := range parsed_symbol.oidsToResolve {
366 - oids_to_resolve[key] = value
367 - }
368 -
369 - table_oids = append(table_oids, parsed_symbol.oid)
370 -
371 - parsed_table_metric := parsedTableMetric{
372 - name: parsed_symbol.name,
373 - indexTags: index_tags,
374 - columnTags: column_tags,
375 - forcedType: metric.forcedType,
376 - options: metric.options,
377 - extractValuePattern: parsed_symbol.extractValuePattern,
378 - rowOID: parsed_symbol.oid,
379 - tableName: table_name,
380 - tableOID: table_oid,
381 - }
382 -
383 - parsed_metrics = append(parsed_metrics, parsed_table_metric)
384 - }
385 -
386 - table_batches = mergeTableBatches(table_batches, map[tableBatchKey]tableBatch{{mib: mib, table: parsed_table.name}: {tableOID: parsed_table.oid, oids: table_oids}})
387 -
388 - return metricParseResult{
389 - oidsToFetch: []string{},
390 - oidsToResolve: oids_to_resolve,
391 - tableBatches: table_batches,
392 - indexMappings: index_mappings,
393 - parsedMetrics: parsed_metrics,
394 - }, nil
395 -}
396 -
397 -func parseTableMetricTag(mib string, parsed_table parsedSymbol, metric_tag TableMetricTag) (parsedTableMetricTag, error) {
398 - /*
399 - Parse an item of the `metric_tags` section of a table metric.
400 -
401 - Items can be:
402 -
403 - * A reference to a column in the same table.
404 -
405 - Example using entPhySensorTable in ENTITY-SENSOR-MIB:
406 -
407 - ```
408 - metric_tags:
409 - - tag: sensor_type
410 - column: entPhySensorType
411 - # OR
412 - column:
413 - OID: 1.3.6.1.2.1.99.1.1.1.1
414 - name: entPhySensorType
415 -
416 - ```
417 -
418 - * A reference to a column in a different table.
419 -
420 - Example:
421 -
422 - ```
423 - metric_tags:
424 - - tag: adapter
425 - table: genericAdaptersAttrTable
426 - column: adapterName
427 - # OR
428 - column:
429 - OID: 1.3.6.1.4.1.343.2.7.2.2.1.1.1.2
430 - name: adapterName
431 -
432 - ```
433 -
434 - * A reference to an OID by its index in the table entry.
435 -
436 - An optional `mapping` can be used to map index values to human-readable strings.
437 -
438 - Example using ipIfStatsTable in IP-MIB:
439 -
440 - ```
441 - metric_tags:
442 - - # ipIfStatsIPVersion (1.3.6.1.2.1.4.21.3.1.1)
443 - tag: ip_version
444 - index: 1
445 - mapping:
446 - 0: unknown
447 - 1: ipv4
448 - 2: ipv6
449 - 3: ipv4z
450 - 4: ipv6z
451 - 16: dns
452 - - # ipIfStatsIfIndex (1.3.6.1.2.1.4.21.3.1.2)
453 - tag: interface
454 - index: 2
455 - ```
456 - */
457 - if metric_tag.Symbol != (Symbol{}) {
458 - metric_tag_mib := metric_tag.MIB
459 -
460 - if metric_tag.Table != "" {
461 - return parseOtherTableColumnMetricTag(metric_tag_mib, metric_tag.Table, metric_tag)
462 - }
463 -
464 - if mib != metric_tag_mib && metric_tag_mib != "" {
465 - return parsedTableMetricTag{}, fmt.Errorf("when tagging from a different MIB, the table must be specified, TABLE_MAME: %s MIB: %s METRIC_TAG_MIB: %s", parsed_table.name, mib, metric_tag_mib)
466 - }
467 - return parseColumnMetricTag(mib, parsed_table, metric_tag)
468 - } else if &metric_tag.Index != nil { //TODO, this is tautological condition, need to return a "-1" on index if it does not exist or something similar
469 - return parseIndexMetricTag(metric_tag)
470 - } else {
471 - return parsedTableMetricTag{}, errors.New("symbol is empty")
472 - }
473 -
474 -}
475 -
476 -func parseIndexMetricTag(metric_tag TableMetricTag) (parsedTableMetricTag, error) {
477 - parsed_metric_tag, err := parseMetricTag(
478 - MetricTag{
479 - Tag: metric_tag.Tag,
480 - })
481 - if err != nil {
482 - return parsedTableMetricTag{}, err
483 - }
484 -
485 - index_tags := []indexTag{{
486 - parsedMetricTag: parsed_metric_tag,
487 - index: metric_tag.Index,
488 - }}
489 -
490 - index_mappings := map[int]map[int]string{}
491 -
492 - if metric_tag.Mapping != nil {
493 - index_mappings = map[int]map[int]string{metric_tag.Index: metric_tag.Mapping}
494 - }
495 -
496 - return parsedTableMetricTag{
497 - indexTags: index_tags,
498 - indexMappings: index_mappings,
499 - }, nil
500 -}
501 -
502 -func parseOtherTableColumnMetricTag(mib string, table string, metric_tag TableMetricTag) (parsedTableMetricTag, error) {
503 - parsed_table, err := parseSymbol(mib, &table)
504 - if err != nil {
505 - return parsedTableMetricTag{}, err
506 - }
507 - parsed_metric_tag, err := parseColumnMetricTag(mib, parsed_table, metric_tag)
508 - if err != nil {
509 - return parsedTableMetricTag{}, err
510 - }
511 -
512 - oids_to_resolve := parsed_metric_tag.oidsToResolve
513 - oids_to_resolve = mergeStringMaps(oids_to_resolve, parsed_table.oidsToResolve)
514 -
515 - return parsedTableMetricTag{
516 - oidsToResolve: oids_to_resolve,
517 - tableBatches: parsed_metric_tag.tableBatches,
518 - columnTags: parsed_metric_tag.columnTags,
519 - }, nil
520 -}
521 -
522 -func parseColumnMetricTag(mib string, parsed_table parsedSymbol, metric_tag TableMetricTag) (parsedTableMetricTag, error) {
523 - parsed_column, err := parseSymbol(mib, metric_tag.Symbol)
524 - if err != nil {
525 - return parsedTableMetricTag{}, err
526 - }
527 -
528 - batches := map[tableBatchKey]tableBatch{
529 - {mib: mib, table: parsed_table.name}: {tableOID: parsed_table.oid, oids: []string{parsed_column.oid}},
530 - }
531 -
532 - parsed_metric_tag, err := parseMetricTag(MetricTag{MIB: metric_tag.MIB, OID: "", Tag: metric_tag.Tag, Symbol: metric_tag.Symbol})
533 - if err != nil {
534 - return parsedTableMetricTag{}, err
535 - }
536 -
537 - return parsedTableMetricTag{
538 - oidsToResolve: parsed_column.oidsToResolve,
539 - columnTags: []columnTag{{
540 - parsedMetricTag: parsed_metric_tag,
541 - column: parsed_column.name,
542 - indexSlices: parseIndexSlices(metric_tag),
543 - },
544 - },
545 - tableBatches: batches,
546 - }, nil
547 -}
548 -
549 -func parseIndexSlices(metric_tag TableMetricTag) []IndexSlice {
550 - /*
551 - // Transform index_transform into list of index slices.
552 -
553 - // `index_transform` is needed to support tagging using another table with different indexes.
554 -
555 - // Example: TableB have two indexes indexX (1 digit) and indexY (3 digits).
556 - // We want to tag by an external TableA that have indexY (3 digits).
557 -
558 - // For example TableB has a row with full index `1.2.3.4`, indexX is `1` and indexY is `2.3.4`.
559 - // TableA has a row with full index `2.3.4`, indexY is `2.3.4` (matches indexY of TableB).
560 -
561 - // SNMP integration doesn't know how to compare the full indexes from TableB and TableA.
562 - // We need to extract a subset of the full index of TableB to match with TableA full index.
563 -
564 - // Using the below `index_transform` we provide enough info to extract a subset of index that
565 - // will be used to match TableA's full index.
566 -
567 - // ```yaml
568 - // index_transform:
569 - // - start: 1
570 - // - end: 3
571 - //
572 - // ```
573 - */
574 - raw_index_slices := metric_tag.IndexTransform
575 - index_slices := []IndexSlice{}
576 -
577 - for _, rule := range raw_index_slices {
578 - start, end := rule.Start, rule.End
579 - if start > end {
580 - log.Println("start bigger than end")
581 - return nil
582 - }
583 - if start < 0 {
584 - log.Println("start is negative")
585 - return nil
586 - }
587 - index_slices = append(index_slices, IndexSlice{start, end + 1})
588 -
589 - }
590 -
591 - return index_slices
592 -}
593 -
594 -func parseMetricTag(metric_tag MetricTag) (parsedMetricTag, error) {
595 - parsed_metric_tag := parsedMetricTag{}
596 -
597 - if metric_tag.Tag != "" {
598 - parsed_metric_tag = parseSimpleMetricTag(metric_tag)
599 - } else if metric_tag.Match != "" && metric_tag.Tags != nil {
600 - tmp, err := parseRegexMetricTag(metric_tag)
601 - if err != nil {
602 - return parsedMetricTag{}, err
603 - } else {
604 - parsed_metric_tag = tmp
605 - }
606 - } else {
607 - return parsedMetricTag{}, fmt.Errorf("a metric tag must specify either a tag, or a mapping of tags and a regular expression %v", metric_tag)
608 - }
609 - return parsed_metric_tag, nil
610 -}
611 -
612 -func parseRegexMetricTag(metric_tag MetricTag) (parsedMetricTag, error) {
613 - match := metric_tag.Match
614 - tags := metric_tag.Tags
615 -
616 - // To be supported once tags are supported
617 - // if reflect.TypeOf(tags) != reflect.TypeOf(map[string]string{}) {
618 - // }
619 -
620 - pattern, err := regexp.Compile(match)
621 - if err != nil {
622 - return parsedMetricTag{}, err
623 - }
624 -
625 - return parsedMetricTag{tags: tags, pattern: pattern}, nil
626 -}
627 -
628 -func parseSimpleMetricTag(metric_tag MetricTag) parsedMetricTag {
629 - return parsedMetricTag{name: metric_tag.Tag}
630 -}
631 -
145 func parseSymbol(mib string, symbol interface{}) (parsedSymbol, error) {
146 /*
147 Parse an OID symbol.
@@ -647,12 +160,9 @@ func parseSymbol(mib string, symbol interface{}) (parsedSymbol, error) {
160 name: ifNumber
161 ```
162 */
650 - // if reflect.TypeOf(symbol) == reflect.TypeOf(string) {
651 - // // TODO, here they use ObjectIdentity(mib,symbol) to resolve the symbol. this is not straightfowrard in Go, it is a pysnmp function.
652 - // // oid:=
163
164 switch s := symbol.(type) {
655 - case Symbol:
165 + case profiledefinition.SymbolConfig:
166 oid := s.OID
167 name := s.Name
168 if s.ExtractValue != "" {
src/go/plugin/go.d/collector/snmp/profile.go
+5 -101
@@ -3,12 +3,10 @@ package snmp
3 import (
4 "fmt"
5 "log"
6 - "os"
7 - "path/filepath"
8 - "strings"
6
7 "github.com/gosnmp/gosnmp"
11 - "gopkg.in/yaml.v3"
8 +
9 + "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
10 )
11
12 func (s *SysObjectIDs) UnmarshalYAML(unmarshal func(any) error) error {
@@ -27,10 +25,11 @@ func (s *SysObjectIDs) UnmarshalYAML(unmarshal func(any) error) error {
25 return fmt.Errorf("invalid sysobjectid format")
26 }
27
30 -func (c *Collector) parseMetricsFromProfiles(matchingProfiles []*Profile) (map[string]processedMetric, error) {
28 +func (c *Collector) parseMetricsFromProfiles(matchingProfiles []*ddsnmp.Profile) (map[string]processedMetric, error) {
29 metricMap := map[string]processedMetric{}
30 for _, profile := range matchingProfiles {
33 - results, err := parseMetrics(profile.Metrics)
31 + profileDef := profile.Definition
32 + results, err := parseMetrics(profileDef.Metrics)
33 if err != nil {
34 return nil, err
35 }
@@ -86,98 +85,3 @@ func (c *Collector) parseMetricsFromProfiles(matchingProfiles []*Profile) (map[s
85 }
86 return metricMap, nil
87 }
89 -
90 -func (s *Symbol) UnmarshalYAML(node *yaml.Node) error {
91 - // If scalar node, assume the value is the name.
92 - if node.Kind == yaml.ScalarNode {
93 - s.Name = node.Value
94 - return nil
95 - }
96 -
97 - // Otherwise, decode normally
98 - type plainSymbol Symbol
99 - var ps plainSymbol
100 - if err := node.Decode(&ps); err != nil {
101 - return err
102 - }
103 - *s = Symbol(ps)
104 - return nil
105 -}
106 -
107 -func LoadAllProfiles(profileDir string) (map[string]*Profile, error) {
108 - profiles := make(map[string]*Profile)
109 - err := filepath.Walk(profileDir, func(path string, info os.FileInfo, err error) error {
110 - if err != nil {
111 - return err
112 - }
113 - if strings.HasSuffix(info.Name(), ".yaml") {
114 - profile, err := LoadYAML(path, profileDir)
115 - if err == nil {
116 - profiles[path] = profile
117 - } else {
118 - log.Printf("Skipping invalid YAML: %s (%v)\n", path, err)
119 - }
120 - }
121 - return nil
122 - })
123 -
124 - if err != nil {
125 - return nil, err
126 - }
127 -
128 - return profiles, nil
129 -}
130 -
131 -func LoadYAML(filename string, basePath string) (*Profile, error) {
132 - data, err := os.ReadFile(filename)
133 - if err != nil {
134 - return nil, err
135 - }
136 -
137 - var profile Profile
138 - err = yaml.Unmarshal(data, &profile)
139 - if err != nil {
140 - return nil, err
141 - }
142 -
143 - // If the profile extends other files, load and merge them
144 - for _, parentFile := range profile.Extends {
145 - parentProfile, err := LoadYAML(filepath.Join(basePath, parentFile), basePath)
146 - if err != nil {
147 - return nil, err
148 - }
149 - MergeProfiles(&profile, parentProfile)
150 - }
151 -
152 - return &profile, nil
153 -}
154 -
155 -// Merge two profiles, giving priority to the child profile
156 -func MergeProfiles(child, parent *Profile) {
157 - if child.Metadata.Device.Fields == nil {
158 - child.Metadata.Device.Fields = make(map[string]Symbol)
159 - }
160 -
161 - for key, value := range parent.Metadata.Device.Fields {
162 - if _, exists := child.Metadata.Device.Fields[key]; !exists {
163 - child.Metadata.Device.Fields[key] = value
164 - }
165 - }
166 - child.Metrics = append(parent.Metrics, child.Metrics...)
167 -}
168 -
169 -// Find the matching profile based on sysObjectID
170 -func FindMatchingProfiles(profiles map[string]*Profile, deviceOID string) []*Profile {
171 - var matchedProfiles []*Profile
172 -
173 - for _, profile := range profiles {
174 - for _, oidPattern := range profile.SysObjectID {
175 - if strings.HasPrefix(deviceOID, strings.Split(oidPattern, "*")[0]) {
176 - matchedProfiles = append(matchedProfiles, profile)
177 - break
178 - }
179 - }
180 - }
181 -
182 - return matchedProfiles
183 -}
src/go/plugin/go.d/collector/snmp/types.go
-87
@@ -12,84 +12,8 @@ type snmpPDU struct {
12 metric_type gosnmp.Asn1BER
13 }
14
15 -type Profile struct {
16 - Extends []string `yaml:"extends"`
17 - SysObjectID SysObjectIDs `yaml:"sysobjectid"`
18 - Metadata Metadata `yaml:"metadata"`
19 - Metrics []Metric `yaml:"metrics"`
20 -}
21 -
15 type SysObjectIDs []string
16
24 -type Metadata struct {
25 - Device DeviceMetadata `yaml:"device"`
26 -}
27 -
28 -type DeviceMetadata struct {
29 - Fields map[string]Symbol `yaml:"fields"`
30 -}
31 -
32 -type Symbol struct {
33 - OID string `yaml:"OID,omitempty"`
34 - Name string `yaml:"name,omitempty"`
35 - MatchPattern string `yaml:"match_pattern,omitempty"`
36 - MatchValue string `yaml:"match_value,omitempty"`
37 - ExtractValue string `yaml:"extract_value,omitempty"`
38 -}
39 -
40 -// superset of OIDMetric, SymbolMetric and TableMetric
41 -type Metric struct {
42 - Name string `yaml:"name,omitempty"`
43 - OID string `yaml:"OID,omitempty"`
44 - //TODO check for only name existing in metric tag, as there is some case for that
45 - MetricTags []TableMetricTag `yaml:"metric_tags,omitempty"`
46 - MetricType string `yaml:"metric_type,omitempty"`
47 - Options map[string]string
48 -
49 - MIB string `yaml:"MIB,omitempty"`
50 - Symbol Symbol `yaml:"symbol,omitempty"` //can be either string or Symbol
51 -
52 - Table interface{} `yaml:"table,omitempty"` // can be either a string or Symbol
53 - Symbols []Symbol `yaml:"symbols,omitempty"`
54 -}
55 -
56 -type TableMetricTag struct {
57 - Index int `yaml:"index"`
58 - Mapping map[int]string `yaml:"mapping"`
59 -
60 - Tag string `yaml:"tag"`
61 -
62 - MIB string `yaml:"mib"`
63 - Symbol Symbol `yaml:"symbol"`
64 - Table string `yaml:"table"`
65 - IndexTransform []IndexSlice `yaml:"index_transform"`
66 -}
67 -
68 -type oidMetric struct {
69 - name string
70 - oid string
71 - metricTags []string
72 - forcedType string
73 - options map[string]string
74 -}
75 -
76 -type symbolMetric struct {
77 - mib string
78 - symbol interface{} //can be either string or Symbol
79 - forcedType string
80 - metricTags []string
81 - options map[string]string
82 -}
83 -
84 -type tableMetric struct {
85 - mib string
86 - table interface{} // can be either a string or Symbol
87 - symbols []Symbol
88 - forcedType string
89 - metricTags []TableMetricTag
90 - options map[string]string
91 -}
92 -
17 type parsedResult struct {
18 oids []string
19 next_oids []string
@@ -213,17 +137,6 @@ type metricParseResult struct {
137 parsedMetrics []parsedMetric
138 }
139
216 -type MetricTag struct {
217 - OID string
218 - MIB string
219 - Symbol Symbol
220 - // simple tag
221 - Tag string
222 - // regex matching
223 - Match string
224 - Tags []string
225 -}
226 -
140 type IndexSlice struct {
141 Start int
142 End int