chore(go.d/snmp): remove legacy custom oid collection (#21056)
Ilya Mashchenko committed
Sep 26, 2025 at 16:46 UTC
2a4c7abea7042fd673bff068a7a57ea41243797b
13 files changed
+207
-669
src/go/plugin/go.d/collector/snmp/charts.go
-108
@@ -225,111 +225,3 @@ func dimAlgoFromDdSnmpType(m ddsnmp.Metric) module.DimAlgo {
225
}
226
227
var cleanMetricName = strings.NewReplacer(".", "_", " ", "_")
228
-
229
-// deprecated custom oid charts
230
-
231
-func newUserInputCharts(configs []ChartConfig) (*module.Charts, error) {
232
- charts := &module.Charts{}
233
- for _, cfg := range configs {
234
- if len(cfg.IndexRange) == 2 {
235
- cs, err := newUserInputChartsFromIndexRange(cfg)
236
- if err != nil {
237
- return nil, err
238
- }
239
- if err := charts.Add(*cs...); err != nil {
240
- return nil, err
241
- }
242
- } else {
243
- chart, err := newUserInputChart(cfg)
244
- if err != nil {
245
- return nil, err
246
- }
247
- if err = charts.Add(chart); err != nil {
248
- return nil, err
249
- }
250
- }
251
- }
252
- return charts, nil
253
-}
254
-
255
-func newUserInputChartsFromIndexRange(cfg ChartConfig) (*module.Charts, error) {
256
- var addPrio int
257
- charts := &module.Charts{}
258
- for i := cfg.IndexRange[0]; i <= cfg.IndexRange[1]; i++ {
259
- chart, err := newUserInputChartWithOIDIndex(i, cfg)
260
- if err != nil {
261
- return nil, err
262
- }
263
- chart.Priority += addPrio
264
- addPrio += 1
265
- if err = charts.Add(chart); err != nil {
266
- return nil, err
267
- }
268
- }
269
- return charts, nil
270
-}
271
-
272
-func newUserInputChartWithOIDIndex(oidIndex int, cfg ChartConfig) (*module.Chart, error) {
273
- chart, err := newUserInputChart(cfg)
274
- if err != nil {
275
- return nil, err
276
- }
277
-
278
- chart.ID = fmt.Sprintf("%s_%d", chart.ID, oidIndex)
279
- chart.Title = fmt.Sprintf("%s %d", chart.Title, oidIndex)
280
- for _, dim := range chart.Dims {
281
- dim.ID = fmt.Sprintf("%s.%d", dim.ID, oidIndex)
282
- }
283
-
284
- return chart, nil
285
-}
286
-
287
-func newUserInputChart(cfg ChartConfig) (*module.Chart, error) {
288
- chart := &module.Chart{
289
- ID: cfg.ID,
290
- Title: cfg.Title,
291
- Units: cfg.Units,
292
- Fam: cfg.Family,
293
- Ctx: fmt.Sprintf("snmp.%s", cfg.ID),
294
- Type: module.ChartType(cfg.Type),
295
- Priority: cfg.Priority,
296
- }
297
-
298
- if chart.Title == "" {
299
- chart.Title = "Untitled chart"
300
- }
301
- if chart.Units == "" {
302
- chart.Units = "num"
303
- }
304
- if chart.Priority < module.Priority {
305
- chart.Priority += module.Priority
306
- }
307
-
308
- seen := make(map[string]struct{})
309
- var a string
310
- for _, cfg := range cfg.Dimensions {
311
- if cfg.Algorithm != "" {
312
- seen[cfg.Algorithm] = struct{}{}
313
- a = cfg.Algorithm
314
- }
315
- dim := &module.Dim{
316
- ID: strings.TrimPrefix(cfg.OID, "."),
317
- Name: cfg.Name,
318
- Algo: module.DimAlgo(cfg.Algorithm),
319
- Mul: cfg.Multiplier,
320
- Div: cfg.Divisor,
321
- }
322
- if err := chart.AddDim(dim); err != nil {
323
- return nil, err
324
- }
325
- }
326
- if len(seen) == 1 && a != "" && len(chart.Dims) > 1 {
327
- for _, d := range chart.Dims {
328
- if d.Algo == "" {
329
- d.Algo = module.DimAlgo(a)
330
- }
331
- }
332
- }
333
-
334
- return chart, nil
335
-}
src/go/plugin/go.d/collector/snmp/collect.go
+9
-12
@@ -86,14 +86,7 @@ func (c *Collector) collectMetrics() (map[string]int64, error) {
86
87
func (c *Collector) ensureInitialized() error {
88
if c.snmpClient == nil {
89
- snmpClient, err := c.initAndConnectSNMPClient()
90
- if err != nil {
91
- return err
92
- }
93
- c.snmpClient = snmpClient
94
- if c.ddSnmpColl != nil {
95
- c.ddSnmpColl.SetSNMPClient(snmpClient)
96
- }
89
+ return errors.New("snmp client not initialized")
90
}
91
92
if c.sysInfo != nil {
@@ -105,12 +98,17 @@ func (c *Collector) ensureInitialized() error {
98
return err
99
}
100
108
- if c.enableProfiles {
101
+ if c.snmpProfiles == nil {
102
c.snmpProfiles = c.setupProfiles(si)
103
}
104
112
- if c.ddSnmpColl == nil {
113
- c.ddSnmpColl = ddsnmpcollector.New(c.snmpClient, c.snmpProfiles, c.Logger, si.SysObjectID)
105
+ if c.ddSnmpColl == nil && len(c.snmpProfiles) > 0 {
106
+ c.ddSnmpColl = c.newDdSnmpColl(ddsnmpcollector.Config{
107
+ SnmpClient: c.snmpClient,
108
+ Profiles: c.snmpProfiles,
109
+ Log: c.Logger,
110
+ SysObjectID: si.SysObjectID,
111
+ })
112
}
113
114
if c.CreateVnode {
@@ -227,7 +225,6 @@ func (c *Collector) initAndConnectSNMPClient() (gosnmp.Handler, error) {
225
c.Warningf("SNMP bulk walk disabled: table metrics collection unavailable (device may not support GETBULK or max-repetitions adjustment failed)")
226
}
227
c.adjMaxRepetitions = snmpClient.MaxRepetitions()
230
- c.snmpBulkWalkOk = ok
228
}
229
230
return snmpClient, nil
src/go/plugin/go.d/collector/snmp/collect_snmp.go
+1
-55
@@ -7,27 +7,11 @@ import (
7
"sort"
8
"strings"
9
10
- "github.com/gosnmp/gosnmp"
11
-
10
"github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
11
)
12
13
func (c *Collector) collectSNMP(mx map[string]int64) error {
16
- if err := c.collectProfiles(mx); err != nil {
17
- return err
18
- }
19
-
20
- if !c.DisableLegacyCollection && len(c.customOids) > 0 {
21
- if err := c.collectLegacyCustomOIDs(mx); err != nil {
22
- return err
23
- }
24
- }
25
-
26
- return nil
27
-}
28
-
29
-func (c *Collector) collectProfiles(mx map[string]int64) error {
30
- if len(c.snmpProfiles) == 0 || c.ddSnmpColl == nil {
14
+ if c.ddSnmpColl == nil {
15
return nil
16
}
17
@@ -140,41 +124,3 @@ func tableMetricKey(m ddsnmp.Metric) string {
124
125
return sb.String()
126
}
143
-
144
-func (c *Collector) collectLegacyCustomOIDs(mx map[string]int64) error {
145
- for i, end := 0, 0; i < len(c.customOids); i += c.Options.MaxOIDs {
146
- if end = i + c.Options.MaxOIDs; end > len(c.customOids) {
147
- end = len(c.customOids)
148
- }
149
-
150
- oids := c.customOids[i:end]
151
- resp, err := c.snmpClient.Get(oids)
152
- if err != nil {
153
- c.Errorf("cannot get SNMP data: %v", err)
154
- return err
155
- }
156
-
157
- for i, oid := range oids {
158
- if i >= len(resp.Variables) {
159
- continue
160
- }
161
-
162
- switch v := resp.Variables[i]; v.Type {
163
- case gosnmp.Boolean,
164
- gosnmp.Counter32,
165
- gosnmp.Counter64,
166
- gosnmp.Gauge32,
167
- gosnmp.TimeTicks,
168
- gosnmp.Uinteger32,
169
- gosnmp.OpaqueFloat,
170
- gosnmp.OpaqueDouble,
171
- gosnmp.Integer:
172
- mx[oid] = gosnmp.ToBigInt(v.Value).Int64()
173
- default:
174
- c.Debugf("skipping OID '%s' (unsupported type '%s')", oid, v.Type)
175
- }
176
- }
177
- }
178
-
179
- return nil
180
-}
src/go/plugin/go.d/collector/snmp/collector.go
+26
-34
@@ -40,7 +40,6 @@ func New() *Collector {
40
CreateVnode: true,
41
VnodeDeviceDownThreshold: 3,
42
Community: "public",
43
- DisableLegacyCollection: true,
43
Options: OptionsConfig{
44
Port: 161,
45
Retries: 1,
@@ -70,41 +69,42 @@ func New() *Collector {
69
70
newProber: ping.NewProber,
71
newSnmpClient: gosnmp.NewHandler,
73
-
74
- snmpBulkWalkOk: true,
75
- enableProfiles: true,
72
+ newDdSnmpColl: func(cfg ddsnmpcollector.Config) ddCollector {
73
+ return ddsnmpcollector.New(cfg)
74
+ },
75
}
76
}
77
79
-type Collector struct {
80
- module.Base
81
- Config `yaml:",inline" json:""`
82
-
83
- vnode *vnodes.VirtualNode
78
+type (
79
+ Collector struct {
80
+ module.Base
81
+ Config `yaml:",inline" json:""`
82
85
- charts *module.Charts
86
- seenScalarMetrics map[string]bool
87
- seenTableMetrics map[string]bool
83
+ vnode *vnodes.VirtualNode
84
89
- prober ping.Prober
90
- newProber func(ping.ProberConfig, *logger.Logger) ping.Prober
85
+ charts *module.Charts
86
+ seenScalarMetrics map[string]bool
87
+ seenTableMetrics map[string]bool
88
92
- newSnmpClient func() gosnmp.Handler
93
- snmpClient gosnmp.Handler
94
- ddSnmpColl *ddsnmpcollector.Collector
89
+ prober ping.Prober
90
+ newProber func(ping.ProberConfig, *logger.Logger) ping.Prober
91
96
- sysInfo *snmputils.SysInfo
97
- snmpProfiles []*ddsnmp.Profile
92
+ snmpClient gosnmp.Handler
93
+ newSnmpClient func() gosnmp.Handler
94
99
- adjMaxRepetitions uint32
100
- snmpBulkWalkOk bool
95
+ ddSnmpColl ddCollector
96
+ newDdSnmpColl func(ddsnmpcollector.Config) ddCollector
97
102
- // legacy data collection parameters
103
- customOids []string
98
+ sysInfo *snmputils.SysInfo
99
+ snmpProfiles []*ddsnmp.Profile
100
105
- // only for tests
106
- enableProfiles bool
107
-}
101
+ adjMaxRepetitions uint32
102
+ }
103
+ ddCollector interface {
104
+ Collect() ([]*ddsnmp.ProfileMetrics, error)
105
+ CollectDeviceMetadata() (map[string]ddsnmp.MetaTag, error)
106
+ }
107
+)
108
109
func (c *Collector) Configuration() any {
110
return c.Config
@@ -119,12 +119,6 @@ func (c *Collector) Init(context.Context) error {
119
return fmt.Errorf("failed to initialize SNMP client: %v", err)
120
}
121
122
- charts, err := newUserInputCharts(c.ChartsInput)
123
- if err != nil {
124
- return fmt.Errorf("failed to create user charts: %v", err)
125
- }
126
- c.charts = charts
127
-
122
if c.Ping.Enabled {
123
pr, err := c.initProber()
124
if err != nil {
@@ -133,8 +127,6 @@ func (c *Collector) Init(context.Context) error {
127
c.prober = pr
128
}
129
136
- c.customOids = c.initCustomOIDs()
137
-
130
return nil
131
}
132
src/go/plugin/go.d/collector/snmp/collector_test.go
+148
-211
@@ -4,14 +4,14 @@ package snmp
4
5
import (
6
"context"
7
- "encoding/hex"
7
"errors"
9
- "fmt"
8
"os"
9
"strings"
10
"testing"
11
12
"github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
14
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector"
15
"github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/snmputils"
16
17
"github.com/golang/mock/gomock"
@@ -130,89 +130,78 @@ func TestCollector_Cleanup(t *testing.T) {
130
}
131
}
132
133
-func TestCollector_Charts(t *testing.T) {
133
+func TestCollector_Check(t *testing.T) {
134
tests := map[string]struct {
135
- prepareSNMP func(t *testing.T, m *snmpmock.MockHandler) *Collector
136
- wantNumCharts int
137
- doCollect bool
135
+ prepare func(m *snmpmock.MockHandler) *Collector
136
+ wantErr bool
137
}{
139
- "custom, no if-mib": {
140
- wantNumCharts: 10,
141
- prepareSNMP: func(t *testing.T, m *snmpmock.MockHandler) *Collector {
142
- collr := New()
143
- collr.Config = prepareConfigWithUserCharts(prepareV2Config(), 0, 9)
138
+ "success: connects and reads sysInfo": {
139
+ wantErr: false,
140
+ prepare: func(m *snmpmock.MockHandler) *Collector {
141
+ setMockClientInitExpect(m)
142
+ setMockClientSysInfoExpect(m)
143
145
- return collr
144
+ c := New()
145
+ c.Config = prepareV2Config()
146
+ c.CreateVnode = false
147
+ c.Ping.Enabled = false
148
+ c.newSnmpClient = func() gosnmp.Handler { return m }
149
+ return c
150
},
151
},
148
- }
149
-
150
- for name, test := range tests {
151
- t.Run(name, func(t *testing.T) {
152
- mockSNMP, cleanup := mockInit(t)
153
- defer cleanup()
154
-
155
- setMockClientInitExpect(mockSNMP)
152
157
- collr := test.prepareSNMP(t, mockSNMP)
158
- collr.newSnmpClient = func() gosnmp.Handler { return mockSNMP }
159
-
160
- require.NoError(t, collr.Init(context.Background()))
161
-
162
- if test.doCollect {
163
- _ = collr.Check(context.Background())
164
- _ = collr.Collect(context.Background())
165
- }
166
-
167
- assert.Equal(t, test.wantNumCharts, len(*collr.Charts()))
168
- })
169
- }
170
-}
171
-
172
-func TestCollector_Check(t *testing.T) {
173
- tests := map[string]struct {
174
- wantFail bool
175
- prepareSNMP func(m *snmpmock.MockHandler) *Collector
176
- }{
177
- "success when sysinfo collected": {
178
- wantFail: false,
179
- prepareSNMP: func(m *snmpmock.MockHandler) *Collector {
180
- collr := New()
181
- collr.Config = prepareV2Config()
182
-
183
- setMockClientSysInfoExpect(m)
184
-
185
- return collr
153
+ "failure: SNMP connect error": {
154
+ wantErr: true,
155
+ prepare: func(m *snmpmock.MockHandler) *Collector {
156
+ setMockClientSetterExpect(m)
157
+ m.EXPECT().Connect().Return(errors.New("connect failed")).AnyTimes()
158
+
159
+ c := New()
160
+ c.Config = prepareV2Config()
161
+ c.CreateVnode = false
162
+ c.Ping.Enabled = false
163
+ c.newSnmpClient = func() gosnmp.Handler { return m }
164
+ return c
165
},
166
},
188
- "fail when snmp client Get fails": {
189
- wantFail: true,
190
- prepareSNMP: func(m *snmpmock.MockHandler) *Collector {
191
- collr := New()
192
- collr.Config = prepareConfigWithUserCharts(prepareV2Config(), 0, 3)
193
- m.EXPECT().WalkAll(snmputils.RootOidMibSystem).Return(nil, errors.New("mock Get() error")).Times(1)
167
195
- return collr
168
+ "failure: sysInfo walk error": {
169
+ wantErr: true,
170
+ prepare: func(m *snmpmock.MockHandler) *Collector {
171
+ // Normal init succeeds
172
+ setMockClientInitExpect(m)
173
+ // But sysInfo retrieval (WalkAll on system tree) fails
174
+ // If your helper is too opinionated, stub directly:
175
+ // The collector ultimately calls WalkAll on the system OID tree.
176
+ m.EXPECT().
177
+ WalkAll(gomock.Any()).
178
+ Return(nil, errors.New("walk failed"))
179
+
180
+ c := New()
181
+ c.Config = prepareV2Config()
182
+ c.CreateVnode = false
183
+ c.Ping.Enabled = false
184
+ c.newSnmpClient = func() gosnmp.Handler { return m }
185
+ return c
186
},
187
},
188
}
189
200
- for name, test := range tests {
190
+ for name, tc := range tests {
191
t.Run(name, func(t *testing.T) {
202
- mockSNMP, cleanup := mockInit(t)
203
- defer cleanup()
204
-
205
- setMockClientInitExpect(mockSNMP)
192
+ ctrl := gomock.NewController(t)
193
+ defer ctrl.Finish()
194
207
- collr := test.prepareSNMP(mockSNMP)
208
- collr.newSnmpClient = func() gosnmp.Handler { return mockSNMP }
195
+ mockSNMP := snmpmock.NewMockHandler(ctrl)
196
197
+ collr := tc.prepare(mockSNMP)
198
require.NoError(t, collr.Init(context.Background()))
199
212
- if test.wantFail {
213
- assert.Error(t, collr.Check(context.Background()))
200
+ err := collr.Check(context.Background())
201
+ if tc.wantErr {
202
+ assert.Error(t, err)
203
} else {
215
- assert.NoError(t, collr.Check(context.Background()))
204
+ assert.NoError(t, err)
205
}
206
})
207
}
@@ -220,118 +209,114 @@ func TestCollector_Check(t *testing.T) {
209
210
func TestCollector_Collect(t *testing.T) {
211
tests := map[string]struct {
223
- prepareSNMP func(m *snmpmock.MockHandler) *Collector
224
- wantCollected map[string]int64
212
+ prepare func(m *snmpmock.MockHandler) *Collector
213
+ want map[string]int64
214
}{
226
- "success only custom OIDs supported type": {
227
- prepareSNMP: func(m *snmpmock.MockHandler) *Collector {
228
- collr := New()
229
- collr.Config = prepareConfigWithUserCharts(prepareV2Config(), 0, 3)
230
- collr.enableProfiles = false
231
-
232
- m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
233
- Variables: []gosnmp.SnmpPDU{
234
- {Value: 10, Type: gosnmp.Counter32},
235
- {Value: 20, Type: gosnmp.Counter64},
236
- {Value: 30, Type: gosnmp.Gauge32},
237
- {Value: 1, Type: gosnmp.Boolean},
238
- {Value: 40, Type: gosnmp.Gauge32},
239
- {Value: 50, Type: gosnmp.TimeTicks},
240
- {Value: 60, Type: gosnmp.Uinteger32},
241
- {Value: 70, Type: gosnmp.Integer},
242
- },
243
- }, nil).Times(1)
215
+ "collects scalar metric": {
216
+ prepare: func(m *snmpmock.MockHandler) *Collector {
217
+ setMockClientInitExpect(m)
218
+ setMockClientSysInfoExpect(m)
219
245
- return collr
246
- },
247
- wantCollected: map[string]int64{
248
- //"TestMetric": 1,
249
- "1.3.6.1.2.1.2.2.1.10.0": 10,
250
- "1.3.6.1.2.1.2.2.1.16.0": 20,
251
- "1.3.6.1.2.1.2.2.1.10.1": 30,
252
- "1.3.6.1.2.1.2.2.1.16.1": 1,
253
- "1.3.6.1.2.1.2.2.1.10.2": 40,
254
- "1.3.6.1.2.1.2.2.1.16.2": 50,
255
- "1.3.6.1.2.1.2.2.1.10.3": 60,
256
- "1.3.6.1.2.1.2.2.1.16.3": 70,
257
- },
258
- },
259
- "success only custom OIDs supported and unsupported type": {
260
- prepareSNMP: func(m *snmpmock.MockHandler) *Collector {
220
collr := New()
262
- collr.Config = prepareConfigWithUserCharts(prepareV2Config(), 0, 2)
263
- collr.enableProfiles = false
264
-
265
- m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
266
- Variables: []gosnmp.SnmpPDU{
267
- {Value: 10, Type: gosnmp.Counter32},
268
- {Value: 20, Type: gosnmp.Counter64},
269
- {Value: 30, Type: gosnmp.Gauge32},
270
- {Value: nil, Type: gosnmp.NoSuchInstance},
271
- {Value: nil, Type: gosnmp.NoSuchInstance},
272
- {Value: nil, Type: gosnmp.NoSuchInstance},
273
- },
274
- }, nil).Times(1)
275
-
221
+ collr.Config = prepareV2Config()
222
+ collr.CreateVnode = false
223
+ collr.Ping.Enabled = false
224
+ collr.snmpProfiles = []*ddsnmp.Profile{{}} // non-empty to enable collectSNMP()
225
+ collr.newSnmpClient = func() gosnmp.Handler { return m }
226
+ collr.newDdSnmpColl = func(ddsnmpcollector.Config) ddCollector {
227
+ return &mockDdSnmpCollector{pms: []*ddsnmp.ProfileMetrics{
228
+ {
229
+ Metrics: []ddsnmp.Metric{
230
+ {
231
+ Name: "uptime",
232
+ IsTable: false,
233
+ Value: 123,
234
+ Unit: "s",
235
+ Tags: map[string]string{},
236
+ Profile: &ddsnmp.ProfileMetrics{Tags: map[string]string{}},
237
+ },
238
+ },
239
+ },
240
+ }}
241
+ }
242
return collr
243
},
278
- wantCollected: map[string]int64{
279
- //"TestMetric": 1,
280
- "1.3.6.1.2.1.2.2.1.10.0": 10,
281
- "1.3.6.1.2.1.2.2.1.16.0": 20,
282
- "1.3.6.1.2.1.2.2.1.10.1": 30,
244
+ want: map[string]int64{
245
+ // scalar → "snmp_device_prof_<name>"
246
+ "snmp_device_prof_uptime": 123,
247
},
248
},
285
- "fails when only custom OIDs unsupported type": {
286
- prepareSNMP: func(m *snmpmock.MockHandler) *Collector {
287
- collr := New()
288
- collr.Config = prepareConfigWithUserCharts(prepareV2Config(), 0, 2)
289
- collr.enableProfiles = false
290
-
291
- m.EXPECT().Get(gomock.Any()).Return(&gosnmp.SnmpPacket{
292
- Variables: []gosnmp.SnmpPDU{
293
- {Value: nil, Type: gosnmp.NoSuchInstance},
294
- {Value: nil, Type: gosnmp.NoSuchInstance},
295
- {Value: nil, Type: gosnmp.NoSuchObject},
296
- {Value: "192.0.2.0", Type: gosnmp.NsapAddress},
297
- {Value: []uint8{118, 101, 116}, Type: gosnmp.OctetString},
298
- {Value: ".1.3.6.1.2.1.4.32.1.5.2.1.4.10.19.0.0.16", Type: gosnmp.ObjectIdentifier},
299
- },
300
- }, nil).Times(1)
249
+ "collects table multivalue metric": {
250
+ prepare: func(m *snmpmock.MockHandler) *Collector {
251
+ setMockClientInitExpect(m)
252
+ setMockClientSysInfoExpect(m)
253
254
+ collr := New()
255
+ collr.Config = prepareV2Config()
256
+ collr.CreateVnode = false
257
+ collr.Ping.Enabled = false
258
+ collr.snmpProfiles = []*ddsnmp.Profile{{}}
259
+ collr.newSnmpClient = func() gosnmp.Handler { return m }
260
+ collr.newDdSnmpColl = func(ddsnmpcollector.Config) ddCollector {
261
+ return &mockDdSnmpCollector{pms: []*ddsnmp.ProfileMetrics{
262
+ {
263
+ Metrics: []ddsnmp.Metric{
264
+ {
265
+ Name: "if_octets",
266
+ IsTable: true,
267
+ Unit: "bit/s",
268
+ Tags: map[string]string{"ifName": "eth0"},
269
+ Profile: &ddsnmp.ProfileMetrics{Tags: map[string]string{}},
270
+ MultiValue: map[string]int64{
271
+ "in": 1,
272
+ "out": 2,
273
+ },
274
+ },
275
+ },
276
+ },
277
+ }}
278
+ }
279
return collr
280
},
281
+ want: map[string]int64{
282
+ // table key: "snmp_device_prof_<name>_<sorted tag values>_<subkey>"
283
+ // here tags = {"ifName":"eth0"} → key part becomes "_eth0"
284
+ "snmp_device_prof_if_octets_eth0_in": 1,
285
+ "snmp_device_prof_if_octets_eth0_out": 2,
286
+ },
287
},
288
}
289
307
- for name, test := range tests {
290
+ for name, tc := range tests {
291
t.Run(name, func(t *testing.T) {
309
- mockSNMP, cleanup := mockInit(t)
310
- defer cleanup()
311
-
312
- setMockClientInitExpect(mockSNMP)
313
- setMockClientSysInfoExpect(mockSNMP)
292
+ mockCtl := gomock.NewController(t)
293
+ defer mockCtl.Finish()
294
315
- collr := test.prepareSNMP(mockSNMP)
316
- collr.newSnmpClient = func() gosnmp.Handler { return mockSNMP }
295
+ mockSNMP := snmpmock.NewMockHandler(mockCtl)
296
297
+ collr := tc.prepare(mockSNMP)
298
require.NoError(t, collr.Init(context.Background()))
299
300
_ = collr.Check(context.Background())
301
322
- mx := collr.Collect(context.Background())
323
-
324
- assert.Equal(t, test.wantCollected, mx)
302
+ got := collr.Collect(context.Background())
303
+ assert.Equal(t, tc.want, got)
304
})
305
}
306
}
307
329
-func mockInit(t *testing.T) (*snmpmock.MockHandler, func()) {
330
- mockCtl := gomock.NewController(t)
331
- cleanup := func() { mockCtl.Finish() }
332
- mockSNMP := snmpmock.NewMockHandler(mockCtl)
308
+type mockDdSnmpCollector struct {
309
+ pms []*ddsnmp.ProfileMetrics
310
+ meta map[string]ddsnmp.MetaTag
311
+ err error
312
+}
313
334
- return mockSNMP, cleanup
314
+func (m *mockDdSnmpCollector) Collect() ([]*ddsnmp.ProfileMetrics, error) {
315
+ return m.pms, m.err
316
+}
317
+
318
+func (m *mockDdSnmpCollector) CollectDeviceMetadata() (map[string]ddsnmp.MetaTag, error) {
319
+ return m.meta, nil
320
}
321
322
func prepareV3Config() Config {
@@ -370,45 +355,20 @@ func prepareV1Config() Config {
355
}
356
}
357
373
-func prepareConfigWithUserCharts(cfg Config, start, end int) Config {
374
- if start > end || start < 0 || end < 1 {
375
- panic(fmt.Sprintf("invalid index range ('%d'-'%d')", start, end))
376
- }
377
- cfg.ChartsInput = []ChartConfig{
378
- {
379
- ID: "test_chart1",
380
- Title: "This is Test Chart1",
381
- Units: "kilobits/s",
382
- Family: "family",
383
- Type: module.Area.String(),
384
- Priority: module.Priority,
385
- Dimensions: []DimensionConfig{
386
- {
387
- OID: "1.3.6.1.2.1.2.2.1.10",
388
- Name: "in",
389
- Algorithm: module.Incremental.String(),
390
- Multiplier: 8,
391
- Divisor: 1000,
392
- },
393
- {
394
- OID: "1.3.6.1.2.1.2.2.1.16",
395
- Name: "out",
396
- Algorithm: module.Incremental.String(),
397
- Multiplier: 8,
398
- Divisor: 1000,
399
- },
400
- },
401
- },
402
- }
403
-
404
- for i := range cfg.ChartsInput {
405
- cfg.ChartsInput[i].IndexRange = []int{start, end}
406
- }
358
+func mockInit(t *testing.T) (*snmpmock.MockHandler, func()) {
359
+ mockCtl := gomock.NewController(t)
360
+ cleanup := func() { mockCtl.Finish() }
361
+ mockSNMP := snmpmock.NewMockHandler(mockCtl)
362
408
- return cfg
363
+ return mockSNMP, cleanup
364
}
365
366
func setMockClientInitExpect(m *snmpmock.MockHandler) {
367
+ setMockClientSetterExpect(m)
368
+ m.EXPECT().Connect().Return(nil).AnyTimes()
369
+}
370
+
371
+func setMockClientSetterExpect(m *snmpmock.MockHandler) {
372
m.EXPECT().Target().AnyTimes()
373
m.EXPECT().Port().AnyTimes()
374
m.EXPECT().Version().AnyTimes()
@@ -425,26 +385,9 @@ func setMockClientInitExpect(m *snmpmock.MockHandler) {
385
m.EXPECT().SetSecurityModel(gomock.Any()).AnyTimes()
386
m.EXPECT().SetMsgFlags(gomock.Any()).AnyTimes()
387
m.EXPECT().SetSecurityParameters(gomock.Any()).AnyTimes()
428
- m.EXPECT().Connect().Return(nil).AnyTimes()
388
m.EXPECT().MaxRepetitions().Return(uint32(25)).AnyTimes()
389
}
390
432
-func setMockClientSysObjectidExpect(m *snmpmock.MockHandler) {
433
- m.EXPECT().Get([]string{snmputils.OidSysObject}).Return(&gosnmp.SnmpPacket{
434
- Variables: []gosnmp.SnmpPDU{
435
- {Value: ".1.1.1",
436
- Name: ".1.3.6.1.2.1.1.2.0",
437
- Type: gosnmp.ObjectIdentifier},
438
- },
439
- }, nil).MinTimes(1)
440
- m.EXPECT().Get([]string{"1.1.1.0"}).Return(&gosnmp.SnmpPacket{
441
- Variables: []gosnmp.SnmpPDU{
442
- {Name: "1.1.1.0", Value: 1, Type: gosnmp.Integer},
443
- },
444
- }, nil).MinTimes(1)
445
-
446
-}
447
-
391
func setMockClientSysInfoExpect(m *snmpmock.MockHandler) {
392
m.EXPECT().WalkAll(snmputils.RootOidMibSystem).Return([]gosnmp.SnmpPDU{
393
{Name: snmputils.OidSysDescr, Value: []uint8("mock sysDescr"), Type: gosnmp.OctetString},
@@ -454,9 +397,3 @@ func setMockClientSysInfoExpect(m *snmpmock.MockHandler) {
397
{Name: snmputils.OidSysLocation, Value: []uint8("mock sysLocation"), Type: gosnmp.OctetString},
398
}, nil).MinTimes(1)
399
}
457
-
458
-func decodePhysAddr(s string) []uint8 {
459
- s = strings.ReplaceAll(s, ":", "")
460
- v, _ := hex.DecodeString(s)
461
- return v
462
-}
src/go/plugin/go.d/collector/snmp/config.go
-4
@@ -23,10 +23,6 @@ type (
23
ManualProfiles []string `yaml:"manual_profiles,omitempty" json:"manual_profiles"`
24
25
Ping PingConfig `yaml:"ping,omitempty" json:"ping"`
26
-
27
- // legacy
28
- ChartsInput []ChartConfig `yaml:"charts,omitempty" json:"charts"`
29
- DisableLegacyCollection bool `yaml:"disable_legacy_collection,omitempty" json:"disable_legacy_collection"`
26
}
27
28
PingConfig struct {
src/go/plugin/go.d/collector/snmp/config_schema.json
+3
-183
@@ -188,157 +188,9 @@
188
}
189
}
190
},
191
- "charts": {
192
- "title": "Charts configuration",
193
- "type": [
194
- "array",
195
- "null"
196
- ],
197
- "uniqueItems": true,
198
- "items": {
199
- "title": "Chart",
200
- "type": [
201
- "object",
202
- "null"
203
- ],
204
- "properties": {
205
- "id": {
206
- "title": "ID",
207
- "description": "Unique identifier for the chart.",
208
- "type": "string"
209
- },
210
- "title": {
211
- "title": "Title",
212
- "description": "Title of the chart.",
213
- "type": "string"
214
- },
215
- "units": {
216
- "title": "Units",
217
- "description": "Unit label for the vertical axis on charts.",
218
- "type": "string"
219
- },
220
- "family": {
221
- "title": "Family",
222
- "description": "Subsection on the dashboard where the chart will be displayed.",
223
- "type": "string"
224
- },
225
- "type": {
226
- "title": "Type",
227
- "type": "string",
228
- "enum": [
229
- "line",
230
- "area",
231
- "stacked"
232
- ],
233
- "default": "line"
234
- },
235
- "priority": {
236
- "title": "Priority",
237
- "description": "Rendering priority of the chart on the dashboard. Lower priority values will cause the chart to appear before those with higher priority values.",
238
- "type": "integer",
239
- "minimum": 1,
240
- "default": 90000
241
- },
242
- "multiply_range": {
243
- "title": "OID index range",
244
- "description": "Specifies the range of indexes used to create multiple charts. If set, a chart will be created for each index in the specified range. Each chart will have the index appended to the OID dimension.",
245
- "type": [
246
- "array",
247
- "null"
248
- ],
249
- "items": {
250
- "title": "Index",
251
- "type": "integer",
252
- "minimum": 0
253
- },
254
- "uniqueItems": true,
255
- "maxItems": 2
256
- },
257
- "dimensions": {
258
- "title": "Dimensions",
259
- "description": "Configuration for dimensions of the chart.",
260
- "type": [
261
- "array",
262
- "null"
263
- ],
264
- "uniqueItems": true,
265
- "minItems": 1,
266
- "items": {
267
- "title": "Dimension configuration",
268
- "type": [
269
- "object",
270
- "null"
271
- ],
272
- "properties": {
273
- "oid": {
274
- "title": "OID",
275
- "description": "SNMP OID.",
276
- "type": "string"
277
- },
278
- "name": {
279
- "title": "Dimension",
280
- "description": "Name of the dimension.",
281
- "type": "string"
282
- },
283
- "algorithm": {
284
- "title": "Algorithm",
285
- "description": "Algorithm of the dimension.",
286
- "type": "string",
287
- "enum": [
288
- "absolute",
289
- "incremental"
290
- ],
291
- "default": "absolute"
292
- },
293
- "multiplier": {
294
- "title": "Multiplier",
295
- "description": "Value to multiply the collected value.",
296
- "type": "integer",
297
- "not": {
298
- "const": 0
299
- },
300
- "default": 1
301
- },
302
- "divisor": {
303
- "title": "Divisor",
304
- "description": "Value to divide the collected value.",
305
- "type": "integer",
306
- "not": {
307
- "const": 0
308
- },
309
- "default": 1
310
- }
311
- },
312
- "required": [
313
- "oid",
314
- "name",
315
- "algorithm",
316
- "multiplier",
317
- "divisor"
318
- ]
319
- }
320
- }
321
- },
322
- "required": [
323
- "id",
324
- "title",
325
- "units",
326
- "family",
327
- "type",
328
- "priority",
329
- "dimensions"
330
- ]
331
- }
332
- },
333
- "disable_legacy_collection": {
334
- "title": "Disable Legacy SNMP Collection",
335
- "description": "Disable the legacy SNMP collection method, forcing the collector to use only SNMP profiles (YAML-based configuration). When enabled, the collector will ignore any non-profile based collection logic.",
336
- "type": "boolean",
337
- "default": true
338
- },
191
"manual_profiles": {
192
"title": "Manual SNMP Profiles",
341
- "description": "Profiles to apply if automatic detection cannot be used.",
193
+ "description": "Profiles to apply if automatic detection cannot be used. In most cases, **leave this empty**.",
194
"type": [
195
"array",
196
"null"
@@ -467,32 +319,6 @@
319
"ui:widget": "hidden"
320
}
321
},
470
- "charts": {
471
- "ui:widget": "hidden",
472
- "items": {
473
- "ui:collapsible": true,
474
- "type": {
475
- "ui:widget": "radio",
476
- "ui:options": {
477
- "inline": true
478
- }
479
- },
480
- "multiply_range": {
481
- "ui:listFlavour": "list"
482
- },
483
- "dimensions": {
484
- "items": {
485
- "ui:collapsible": true,
486
- "algorithm": {
487
- "ui:widget": "radio",
488
- "ui:options": {
489
- "inline": true
490
- }
491
- }
492
- }
493
- }
494
- }
495
- },
322
"ui:flavour": "tabs",
323
"ui:options": {
324
"tabs": [
@@ -501,7 +327,8 @@
327
"fields": [
328
"update_every",
329
"hostname",
504
- "community"
330
+ "community",
331
+ "manual_profiles"
332
]
333
},
334
{
@@ -529,13 +356,6 @@
356
"fields": [
357
"user"
358
]
532
- },
533
- {
534
- "title": "Profiles",
535
- "fields": [
536
- "manual_profiles",
537
- "disable_legacy_collection"
538
- ]
359
}
360
]
361
}
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector.go
+14
-7
@@ -18,24 +18,31 @@ import (
18
"github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddprofiledefinition"
19
)
20
21
-func New(snmpClient gosnmp.Handler, profiles []*ddsnmp.Profile, log *logger.Logger, sysobjectid string) *Collector {
21
+type Config struct {
22
+ SnmpClient gosnmp.Handler
23
+ Profiles []*ddsnmp.Profile
24
+ Log *logger.Logger
25
+ SysObjectID string
26
+}
27
+
28
+func New(cfg Config) *Collector {
29
coll := &Collector{
23
- log: log.With(slog.String("ddsnmp", "collector")),
30
+ log: cfg.Log.With(slog.String("ddsnmp", "collector")),
31
profiles: make(map[string]*profileState),
32
missingOIDs: make(map[string]bool),
33
tableCache: newTableCache(30*time.Minute, 1),
34
}
35
29
- for _, prof := range profiles {
36
+ for _, prof := range cfg.Profiles {
37
prof := prof
38
handleCrossTableTagsWithoutMetrics(prof)
39
coll.profiles[prof.SourceFile] = &profileState{profile: prof}
40
}
41
35
- coll.globalTagsCollector = newGlobalTagsCollector(snmpClient, coll.missingOIDs, coll.log)
36
- coll.deviceMetadataCollector = newDeviceMetadataCollector(snmpClient, coll.missingOIDs, coll.log, sysobjectid)
37
- coll.scalarCollector = newScalarCollector(snmpClient, coll.missingOIDs, coll.log)
38
- coll.tableCollector = newTableCollector(snmpClient, coll.missingOIDs, coll.tableCache, coll.log)
42
+ coll.globalTagsCollector = newGlobalTagsCollector(cfg.SnmpClient, coll.missingOIDs, coll.log)
43
+ coll.deviceMetadataCollector = newDeviceMetadataCollector(cfg.SnmpClient, coll.missingOIDs, coll.log, cfg.SysObjectID)
44
+ coll.scalarCollector = newScalarCollector(cfg.SnmpClient, coll.missingOIDs, coll.log)
45
+ coll.tableCollector = newTableCollector(cfg.SnmpClient, coll.missingOIDs, coll.tableCache, coll.log)
46
coll.vmetricsCollector = newVirtualMetricsCollector(coll.log)
47
48
return coll
src/go/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector/collector_table_test.go
+6
-1
@@ -4612,7 +4612,12 @@ func TestCollector_Collect_TableCaching(t *testing.T) {
4612
mockHandler := snmpmock.NewMockHandler(ctrl)
4613
tc.setupMock(mockHandler)
4614
4615
- collector := New(mockHandler, tc.profiles, logger.New(), "")
4615
+ collector := New(Config{
4616
+ SnmpClient: mockHandler,
4617
+ Profiles: tc.profiles,
4618
+ Log: logger.New(),
4619
+ SysObjectID: "",
4620
+ })
4621
4622
// Configure cache based on test requirements
4623
if tc.enableCache {
src/go/plugin/go.d/collector/snmp/init.go
-9
@@ -83,12 +83,3 @@ func (c *Collector) initProber() (ping.Prober, error) {
83
84
return c.newProber(conf, c.Logger), nil
85
}
86
-
87
-func (c *Collector) initCustomOIDs() (oids []string) {
88
- for _, c := range *c.charts {
89
- for _, d := range c.Dims {
90
- oids = append(oids, d.ID)
91
- }
92
- }
93
- return oids
94
-}
src/go/plugin/go.d/collector/snmp/metadata.yaml
-5
@@ -226,11 +226,6 @@ modules:
226
description: Profiles to apply if automatic detection cannot be used.
227
default_value: "[]"
228
required: false
229
- - name: disable_legacy_collection
230
- group: Profiles
231
- description: Disable the legacy SNMP collection method, forcing the collector to use only SNMP profiles (YAML-based configuration). When enabled, the collector will ignore any non-profile based collection logic.
232
- default_value: "true"
233
- required: false
229
230
- name: create_vnode
231
group: Virtual node
src/go/plugin/go.d/collector/snmp/testdata/config.json
-23
@@ -28,29 +28,6 @@
28
"max_request_size": 123,
29
"max_repetitions": 123
30
},
31
- "charts": [
32
- {
33
- "id": "ok",
34
- "title": "ok",
35
- "units": "ok",
36
- "family": "ok",
37
- "type": "ok",
38
- "priority": 123,
39
- "multiply_range": [
40
- 123
41
- ],
42
- "dimensions": [
43
- {
44
- "oid": "ok",
45
- "name": "ok",
46
- "algorithm": "ok",
47
- "multiplier": 123,
48
- "divisor": 123
49
- }
50
- ]
51
- }
52
- ],
53
- "disable_legacy_collection": true,
31
"manual_profiles": [
32
"ok"
33
],
src/go/plugin/go.d/collector/snmp/testdata/config.yaml
-17
@@ -2,7 +2,6 @@ update_every: 123
2
hostname: "ok"
3
create_vnode: yes
4
vnode_device_down_threshold: 123
5
-disable_legacy_collection: yes
5
manual_profiles:
6
- "ok"
7
@@ -31,22 +30,6 @@ options:
30
max_request_size: 123
31
max_repetitions: 123
32
34
-charts:
35
- - id: "ok"
36
- title: "ok"
37
- units: "ok"
38
- family: "ok"
39
- type: "ok"
40
- priority: 123
41
- multiply_range:
42
- - 123
43
- dimensions:
44
- - oid: "ok"
45
- name: "ok"
46
- algorithm: "ok"
47
- multiplier: 123
48
- divisor: 123
49
-
33
ping:
34
enabled: yes
35
network: ip