feat(go.d/snmp): add optional ICMP ping metrics (#21052)
Ilya Mashchenko committed
Sep 26, 2025 at 12:13 UTC
5f07bb33fdbd09ec7a4e65ce3b094fa19655bab0
22 files changed
+596
-834
src/go/plugin/go.d/agent/discovery/sd/discoverer/snmpsd/config.go
+5
-72
@@ -9,6 +9,7 @@ import (
9
10
"github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/confopt"
11
"github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/iprange"
12
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/snmputils"
13
)
14
15
type (
@@ -122,7 +123,7 @@ func (c *Config) validateAndParse() ([]subnet, error) {
123
}
124
125
func setCredential(client gosnmp.Handler, cred CredentialConfig) {
125
- switch parseSNMPVersion(cred) {
126
+ switch snmputils.ParseSNMPVersion(cred.Version) {
127
case gosnmp.Version1:
128
client.SetVersion(gosnmp.Version1)
129
client.SetCommunity(cred.Community)
@@ -132,81 +133,13 @@ func setCredential(client gosnmp.Handler, cred CredentialConfig) {
133
case gosnmp.Version3:
134
client.SetVersion(gosnmp.Version3)
135
client.SetSecurityModel(gosnmp.UserSecurityModel)
135
- client.SetMsgFlags(parseSNMPv3SecurityLevel(cred))
136
+ client.SetMsgFlags(snmputils.ParseSNMPv3SecurityLevel(cred.SecurityLevel))
137
client.SetSecurityParameters(&gosnmp.UsmSecurityParameters{
138
UserName: cred.UserName,
138
- AuthenticationProtocol: parseSNMPv3AuthProtocol(cred),
139
+ AuthenticationProtocol: snmputils.ParseSNMPv3AuthProtocol(cred.AuthProtocol),
140
AuthenticationPassphrase: cred.AuthPassphrase,
140
- PrivacyProtocol: parseSNMPv3PrivProtocol(cred),
141
+ PrivacyProtocol: snmputils.ParseSNMPv3PrivProtocol(cred.PrivacyProtocol),
142
PrivacyPassphrase: cred.PrivacyPassphrase,
143
})
144
}
145
}
145
-
146
-func parseSNMPVersion(cred CredentialConfig) gosnmp.SnmpVersion {
147
- switch cred.Version {
148
- case "0", "1":
149
- return gosnmp.Version1
150
- case "2", "2c", "":
151
- return gosnmp.Version2c
152
- case "3":
153
- return gosnmp.Version3
154
- default:
155
- return gosnmp.Version2c
156
- }
157
-}
158
-
159
-func parseSNMPv3SecurityLevel(cred CredentialConfig) gosnmp.SnmpV3MsgFlags {
160
- switch cred.SecurityLevel {
161
- case "1", "none", "noAuthNoPriv", "":
162
- return gosnmp.NoAuthNoPriv
163
- case "2", "authNoPriv":
164
- return gosnmp.AuthNoPriv
165
- case "3", "authPriv":
166
- return gosnmp.AuthPriv
167
- default:
168
- return gosnmp.NoAuthNoPriv
169
- }
170
-}
171
-
172
-func parseSNMPv3AuthProtocol(cred CredentialConfig) gosnmp.SnmpV3AuthProtocol {
173
- switch cred.AuthProtocol {
174
- case "1", "none", "noAuth", "":
175
- return gosnmp.NoAuth
176
- case "2", "md5", "MD5":
177
- return gosnmp.MD5
178
- case "3", "sha", "SHA":
179
- return gosnmp.SHA
180
- case "4", "sha224", "SHA224":
181
- return gosnmp.SHA224
182
- case "5", "sha256", "SHA256":
183
- return gosnmp.SHA256
184
- case "6", "sha384", "SHA384":
185
- return gosnmp.SHA384
186
- case "7", "sha512", "SHA512":
187
- return gosnmp.SHA512
188
- default:
189
- return gosnmp.NoAuth
190
- }
191
-}
192
-
193
-func parseSNMPv3PrivProtocol(cred CredentialConfig) gosnmp.SnmpV3PrivProtocol {
194
- switch cred.PrivacyProtocol {
195
- case "1", "none", "noPriv", "":
196
- return gosnmp.NoPriv
197
- case "2", "des", "DES":
198
- return gosnmp.DES
199
- case "3", "aes", "AES":
200
- return gosnmp.AES
201
- case "4", "aes192", "AES192":
202
- return gosnmp.AES192
203
- case "5", "aes256", "AES256":
204
- return gosnmp.AES256
205
- case "6", "aes192c", "AES192C":
206
- return gosnmp.AES192C
207
- case "7", "aes256c", "AES256C":
208
- return gosnmp.AES256C
209
- default:
210
- return gosnmp.NoPriv
211
- }
212
-}
src/go/plugin/go.d/agent/discovery/sd/discoverer/snmpsd/discoverer.go
+1
-14
@@ -6,7 +6,6 @@ import (
6
"context"
7
"fmt"
8
"log/slog"
9
- "strings"
9
"time"
10
11
"github.com/gohugoio/hashstructure"
@@ -179,7 +178,7 @@ func (d *Discoverer) discoverNetwork(ctx context.Context, in chan<- []model.Targ
178
client.SetTimeout(d.timeout)
179
client.SetRetries(0)
180
setCredential(client, sub.credential)
182
- d.Debugf("SNMP client info for '%s': %s", sub.str, snmpClientConnInfo(client))
181
+ d.Debugf("SNMP client info for '%s': %s", sub.str, snmputils.SnmpClientConnInfo(client))
182
183
for ip := range sub.ips.Iterate() {
184
ipAddr := ip.String()
@@ -282,15 +281,3 @@ func isDone(ctx context.Context) bool {
281
return false
282
}
283
}
285
-
286
-func snmpClientConnInfo(c gosnmp.Handler) string {
287
- var info strings.Builder
288
- info.WriteString(fmt.Sprintf("hostname='%s',port='%d',snmp_version='%s'", c.Target(), c.Port(), c.Version()))
289
- switch c.Version() {
290
- case gosnmp.Version1, gosnmp.Version2c:
291
- info.WriteString(fmt.Sprintf(",community='%s'", c.Community()))
292
- case gosnmp.Version3:
293
- info.WriteString(fmt.Sprintf(",security_level='%d,%s'", c.MsgFlags(), c.SecurityParameters().Description()))
294
- }
295
- return info.String()
296
-}
src/go/plugin/go.d/collector/ping/collect.go
+1
-1
@@ -22,7 +22,7 @@ func (c *Collector) collect() (map[string]int64, error) {
22
}
23
24
func (c *Collector) pingHost(host string, mx map[string]int64, mu *sync.Mutex) {
25
- stats, err := c.prober.ping(host)
25
+ stats, err := c.prober.Ping(host)
26
if err != nil {
27
c.Error(err)
28
return
src/go/plugin/go.d/collector/ping/collector.go
+14
-16
@@ -31,27 +31,25 @@ func init() {
31
func New() *Collector {
32
return &Collector{
33
Config: Config{
34
- Network: "ip",
35
- Privileged: true,
36
- SendPackets: 5,
37
- Interval: confopt.Duration(time.Millisecond * 100),
34
+ ProberConfig: ProberConfig{
35
+ Network: "ip",
36
+ Privileged: true,
37
+ Packets: 5,
38
+ Interval: confopt.Duration(time.Millisecond * 100),
39
+ },
40
},
41
42
charts: &module.Charts{},
43
hosts: make(map[string]bool),
42
- newProber: newPingProber,
44
+ newProber: NewProber,
45
}
46
}
47
48
type Config struct {
47
- Vnode string `yaml:"vnode,omitempty" json:"vnode"`
48
- UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
49
- Hosts []string `yaml:"hosts" json:"hosts"`
50
- Network string `yaml:"network,omitempty" json:"network"`
51
- Privileged bool `yaml:"privileged" json:"privileged"`
52
- SendPackets int `yaml:"packets,omitempty" json:"packets"`
53
- Interval confopt.Duration `yaml:"interval,omitempty" json:"interval"`
54
- Interface string `yaml:"interface,omitempty" json:"interface"`
49
+ Vnode string `yaml:"vnode,omitempty" json:"vnode"`
50
+ UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
51
+ Hosts []string `yaml:"hosts" json:"hosts"`
52
+ ProberConfig `yaml:",inline" json:",inline"`
53
}
54
55
type Collector struct {
@@ -60,8 +58,8 @@ type Collector struct {
58
59
charts *module.Charts
60
63
- prober prober
64
- newProber func(pingProberConfig, *logger.Logger) prober
61
+ prober Prober
62
+ newProber func(ProberConfig, *logger.Logger) Prober
63
64
hosts map[string]bool
65
}
@@ -78,7 +76,7 @@ func (c *Collector) Init(context.Context) error {
76
77
pr, err := c.initProber()
78
if err != nil {
81
- return fmt.Errorf("init prober: %v", err)
79
+ return fmt.Errorf("init ping prober: %v", err)
80
}
81
c.prober = pr
82
src/go/plugin/go.d/collector/ping/collector_test.go
+12
-10
@@ -47,8 +47,10 @@ func TestCollector_Init(t *testing.T) {
47
"success when 'hosts' set": {
48
wantFail: false,
49
config: Config{
50
- SendPackets: 1,
51
- Hosts: []string{"192.0.2.0"},
50
+ ProberConfig: ProberConfig{
51
+ Packets: 1,
52
+ },
53
+ Hosts: []string{"192.0.2.0"},
54
},
55
},
56
}
@@ -81,11 +83,11 @@ func TestCollector_Check(t *testing.T) {
83
wantFail bool
84
prepare func(t *testing.T) *Collector
85
}{
84
- "success when ping does not return an error": {
86
+ "success when Ping does not return an error": {
87
wantFail: false,
88
prepare: casePingSuccess,
89
},
88
- "fail when ping returns an error": {
90
+ "fail when Ping returns an error": {
91
wantFail: true,
92
prepare: casePingError,
93
},
@@ -110,7 +112,7 @@ func TestCollector_Collect(t *testing.T) {
112
wantMetrics map[string]int64
113
wantNumCharts int
114
}{
113
- "success when ping does not return an error": {
115
+ "success when Ping does not return an error": {
116
prepare: casePingSuccess,
117
wantMetrics: map[string]int64{
118
"host_192.0.2.1_avg_rtt": 15000,
@@ -137,7 +139,7 @@ func TestCollector_Collect(t *testing.T) {
139
},
140
wantNumCharts: 3 * len(hostChartsTmpl),
141
},
140
- "fail when ping returns an error": {
142
+ "fail when Ping returns an error": {
143
prepare: casePingError,
144
wantMetrics: nil,
145
wantNumCharts: 0,
@@ -163,7 +165,7 @@ func casePingSuccess(t *testing.T) *Collector {
165
collr := New()
166
collr.UpdateEvery = 1
167
collr.Hosts = []string{"192.0.2.1", "192.0.2.2", "example.com"}
166
- collr.newProber = func(_ pingProberConfig, _ *logger.Logger) prober {
168
+ collr.newProber = func(_ ProberConfig, _ *logger.Logger) Prober {
169
return &mockProber{}
170
}
171
require.NoError(t, collr.Init(context.Background()))
@@ -174,7 +176,7 @@ func casePingError(t *testing.T) *Collector {
176
collr := New()
177
collr.UpdateEvery = 1
178
collr.Hosts = []string{"192.0.2.1", "192.0.2.2", "example.com"}
177
- collr.newProber = func(_ pingProberConfig, _ *logger.Logger) prober {
179
+ collr.newProber = func(_ ProberConfig, _ *logger.Logger) Prober {
180
return &mockProber{errOnPing: true}
181
}
182
require.NoError(t, collr.Init(context.Background()))
@@ -185,9 +187,9 @@ type mockProber struct {
187
errOnPing bool
188
}
189
188
-func (m *mockProber) ping(host string) (*probing.Statistics, error) {
190
+func (m *mockProber) Ping(host string) (*probing.Statistics, error) {
191
if m.errOnPing {
190
- return nil, errors.New("mock.ping() error")
192
+ return nil, errors.New("mock.Ping() error")
193
}
194
195
stats := probing.Statistics{
src/go/plugin/go.d/collector/ping/init.go
+7
-13
@@ -11,29 +11,23 @@ func (c *Collector) validateConfig() error {
11
if len(c.Hosts) == 0 {
12
return errors.New("'hosts' can't be empty")
13
}
14
- if c.SendPackets <= 0 {
14
+ if c.Packets <= 0 {
15
return errors.New("'send_packets' can't be <= 0")
16
}
17
return nil
18
}
19
20
-func (c *Collector) initProber() (prober, error) {
20
+func (c *Collector) initProber() (Prober, error) {
21
mul := 0.9
22
if c.UpdateEvery > 1 {
23
mul = 0.95
24
}
25
- deadline := time.Millisecond * time.Duration(float64(c.UpdateEvery)*mul*1000)
26
- if deadline.Milliseconds() == 0 {
27
- return nil, errors.New("zero ping deadline")
28
- }
29
-
30
- conf := pingProberConfig{
31
- privileged: c.Privileged,
32
- packets: c.SendPackets,
33
- ifaceName: c.Interface,
34
- interval: c.Interval.Duration(),
35
- deadline: deadline,
25
+ timeout := time.Millisecond * time.Duration(float64(c.UpdateEvery)*mul*1000)
26
+ if timeout.Milliseconds() == 0 {
27
+ return nil, errors.New("zero ping timeout")
28
}
29
+ conf := c.Config.ProberConfig
30
+ conf.Timeout = timeout
31
32
return c.newProber(conf, c.Logger), nil
33
}
src/go/plugin/go.d/collector/ping/prober.go
+21
-30
@@ -7,61 +7,52 @@ import (
7
"time"
8
9
"github.com/netdata/netdata/go/plugins/logger"
10
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/confopt"
11
12
probing "github.com/prometheus-community/pro-bing"
13
)
14
14
-type prober interface {
15
- ping(host string) (*probing.Statistics, error)
15
+type Prober interface {
16
+ Ping(host string) (*probing.Statistics, error)
17
}
18
18
-func newPingProber(conf pingProberConfig, log *logger.Logger) prober {
19
+func NewProber(conf ProberConfig, log *logger.Logger) Prober {
20
return &pingProber{
20
- network: conf.network,
21
- interfaceName: conf.ifaceName,
22
- privileged: conf.privileged,
23
- packets: conf.packets,
24
- interval: conf.interval,
25
- deadline: conf.deadline,
26
- Logger: log,
21
+ conf: conf,
22
+ Logger: log,
23
}
24
}
25
30
-type pingProberConfig struct {
31
- network string
32
- ifaceName string
33
- privileged bool
34
- packets int
35
- interval time.Duration
36
- deadline time.Duration
26
+type ProberConfig struct {
27
+ Network string `yaml:"network,omitempty" json:"network"`
28
+ Interface string `yaml:"interface,omitempty" json:"interface"`
29
+ Privileged bool `yaml:"privileged" json:"privileged"`
30
+ Packets int `yaml:"packets,omitempty" json:"packets"`
31
+ Interval confopt.Duration `yaml:"interval,omitempty" json:"interval"`
32
+ Timeout time.Duration `yaml:"-,omitempty" json:",omitempty"`
33
}
34
35
type pingProber struct {
36
*logger.Logger
37
42
- network string
43
- interfaceName string
44
- privileged bool
45
- packets int
46
- interval time.Duration
47
- deadline time.Duration
38
+ conf ProberConfig
39
}
40
50
-func (p *pingProber) ping(host string) (*probing.Statistics, error) {
41
+func (p *pingProber) Ping(host string) (*probing.Statistics, error) {
42
pr := probing.New(host)
43
53
- pr.SetNetwork(p.network)
44
+ pr.SetNetwork(p.conf.Network)
45
46
if err := pr.Resolve(); err != nil {
47
return nil, fmt.Errorf("DNS lookup '%s' : %v", host, err)
48
}
49
50
pr.RecordRtts = false
60
- pr.Interval = p.interval
61
- pr.Count = p.packets
62
- pr.Timeout = p.deadline
63
- pr.InterfaceName = p.interfaceName
64
- pr.SetPrivileged(p.privileged)
51
+ pr.Interval = p.conf.Interval.Duration()
52
+ pr.Count = p.conf.Packets
53
+ pr.Timeout = p.conf.Timeout
54
+ pr.InterfaceName = p.conf.Interface
55
+ pr.SetPrivileged(p.conf.Privileged)
56
pr.SetLogger(nil)
57
58
if err := pr.Run(); err != nil {
src/go/plugin/go.d/collector/snmp/charts.go
+144
-102
@@ -13,113 +13,47 @@ import (
13
)
14
15
const (
16
- prioProfileChart = module.Priority
16
+ prioProfileChart = module.Priority + iota
17
+ prioPingRtt
18
+ prioPingStdDev
19
)
20
19
-func newUserInputCharts(configs []ChartConfig) (*module.Charts, error) {
20
- charts := &module.Charts{}
21
- for _, cfg := range configs {
22
- if len(cfg.IndexRange) == 2 {
23
- cs, err := newUserInputChartsFromIndexRange(cfg)
24
- if err != nil {
25
- return nil, err
26
- }
27
- if err := charts.Add(*cs...); err != nil {
28
- return nil, err
29
- }
30
- } else {
31
- chart, err := newUserInputChart(cfg)
32
- if err != nil {
33
- return nil, err
34
- }
35
- if err = charts.Add(chart); err != nil {
36
- return nil, err
37
- }
38
- }
39
- }
40
- return charts, nil
41
-}
42
-
43
-func newUserInputChartsFromIndexRange(cfg ChartConfig) (*module.Charts, error) {
44
- var addPrio int
45
- charts := &module.Charts{}
46
- for i := cfg.IndexRange[0]; i <= cfg.IndexRange[1]; i++ {
47
- chart, err := newUserInputChartWithOIDIndex(i, cfg)
48
- if err != nil {
49
- return nil, err
50
- }
51
- chart.Priority += addPrio
52
- addPrio += 1
53
- if err = charts.Add(chart); err != nil {
54
- return nil, err
55
- }
56
- }
57
- return charts, nil
58
-}
59
-
60
-func newUserInputChartWithOIDIndex(oidIndex int, cfg ChartConfig) (*module.Chart, error) {
61
- chart, err := newUserInputChart(cfg)
62
- if err != nil {
63
- return nil, err
64
- }
65
-
66
- chart.ID = fmt.Sprintf("%s_%d", chart.ID, oidIndex)
67
- chart.Title = fmt.Sprintf("%s %d", chart.Title, oidIndex)
68
- for _, dim := range chart.Dims {
69
- dim.ID = fmt.Sprintf("%s.%d", dim.ID, oidIndex)
70
- }
71
-
72
- return chart, nil
73
-}
74
-
75
-func newUserInputChart(cfg ChartConfig) (*module.Chart, error) {
76
- chart := &module.Chart{
77
- ID: cfg.ID,
78
- Title: cfg.Title,
79
- Units: cfg.Units,
80
- Fam: cfg.Family,
81
- Ctx: fmt.Sprintf("snmp.%s", cfg.ID),
82
- Type: module.ChartType(cfg.Type),
83
- Priority: cfg.Priority,
84
- }
85
-
86
- if chart.Title == "" {
87
- chart.Title = "Untitled chart"
88
- }
89
- if chart.Units == "" {
90
- chart.Units = "num"
91
- }
92
- if chart.Priority < module.Priority {
93
- chart.Priority += module.Priority
21
+var (
22
+ pingCharts = module.Charts{
23
+ pingRTTChart.Copy(),
24
+ pingRTTStdDevChart.Copy(),
25
+ }
26
+ pingRTTChart = module.Chart{
27
+ ID: "ping_rtt",
28
+ Title: "Ping round-trip time",
29
+ Units: "milliseconds",
30
+ Fam: "Ping/RTT",
31
+ Ctx: "snmp.device_ping_rtt",
32
+ Priority: prioPingRtt,
33
+ Type: module.Area,
34
+ Dims: module.Dims{
35
+ {ID: "ping_rtt_min", Name: "min", Div: 1e3},
36
+ {ID: "ping_rtt_max", Name: "max", Div: 1e3},
37
+ {ID: "ping_rtt_avg", Name: "avg", Div: 1e3},
38
+ },
39
+ }
40
+ pingRTTStdDevChart = module.Chart{
41
+ ID: "ping_rtt_stddev",
42
+ Title: "Ping round-trip time standard deviation",
43
+ Units: "milliseconds",
44
+ Fam: "Ping/RTT",
45
+ Ctx: "snmp.device_ping_rtt_stddev",
46
+ Priority: prioPingStdDev,
47
+ Dims: module.Dims{
48
+ {ID: "ping_rtt_stddev", Name: "stddev", Div: 1e3},
49
+ },
50
}
51
+)
52
96
- seen := make(map[string]struct{})
97
- var a string
98
- for _, cfg := range cfg.Dimensions {
99
- if cfg.Algorithm != "" {
100
- seen[cfg.Algorithm] = struct{}{}
101
- a = cfg.Algorithm
102
- }
103
- dim := &module.Dim{
104
- ID: strings.TrimPrefix(cfg.OID, "."),
105
- Name: cfg.Name,
106
- Algo: module.DimAlgo(cfg.Algorithm),
107
- Mul: cfg.Multiplier,
108
- Div: cfg.Divisor,
109
- }
110
- if err := chart.AddDim(dim); err != nil {
111
- return nil, err
112
- }
53
+func (c *Collector) addPingCharts() {
54
+ if err := c.Charts().Add(*pingCharts.Copy()...); err != nil {
55
+ c.Warningf("failed to add ping charts: %v", err)
56
}
114
- if len(seen) == 1 && a != "" && len(chart.Dims) > 1 {
115
- for _, d := range chart.Dims {
116
- if d.Algo == "" {
117
- d.Algo = module.DimAlgo(a)
118
- }
119
- }
120
- }
121
-
122
- return chart, nil
57
}
58
59
func (c *Collector) addProfileScalarMetricChart(m ddsnmp.Metric) {
@@ -263,3 +197,111 @@ func dimAlgoFromDdSnmpType(m ddsnmp.Metric) module.DimAlgo {
197
}
198
199
var cleanMetricName = strings.NewReplacer(".", "_", " ", "_")
200
+
201
+// deprecated custom oid charts
202
+
203
+func newUserInputCharts(configs []ChartConfig) (*module.Charts, error) {
204
+ charts := &module.Charts{}
205
+ for _, cfg := range configs {
206
+ if len(cfg.IndexRange) == 2 {
207
+ cs, err := newUserInputChartsFromIndexRange(cfg)
208
+ if err != nil {
209
+ return nil, err
210
+ }
211
+ if err := charts.Add(*cs...); err != nil {
212
+ return nil, err
213
+ }
214
+ } else {
215
+ chart, err := newUserInputChart(cfg)
216
+ if err != nil {
217
+ return nil, err
218
+ }
219
+ if err = charts.Add(chart); err != nil {
220
+ return nil, err
221
+ }
222
+ }
223
+ }
224
+ return charts, nil
225
+}
226
+
227
+func newUserInputChartsFromIndexRange(cfg ChartConfig) (*module.Charts, error) {
228
+ var addPrio int
229
+ charts := &module.Charts{}
230
+ for i := cfg.IndexRange[0]; i <= cfg.IndexRange[1]; i++ {
231
+ chart, err := newUserInputChartWithOIDIndex(i, cfg)
232
+ if err != nil {
233
+ return nil, err
234
+ }
235
+ chart.Priority += addPrio
236
+ addPrio += 1
237
+ if err = charts.Add(chart); err != nil {
238
+ return nil, err
239
+ }
240
+ }
241
+ return charts, nil
242
+}
243
+
244
+func newUserInputChartWithOIDIndex(oidIndex int, cfg ChartConfig) (*module.Chart, error) {
245
+ chart, err := newUserInputChart(cfg)
246
+ if err != nil {
247
+ return nil, err
248
+ }
249
+
250
+ chart.ID = fmt.Sprintf("%s_%d", chart.ID, oidIndex)
251
+ chart.Title = fmt.Sprintf("%s %d", chart.Title, oidIndex)
252
+ for _, dim := range chart.Dims {
253
+ dim.ID = fmt.Sprintf("%s.%d", dim.ID, oidIndex)
254
+ }
255
+
256
+ return chart, nil
257
+}
258
+
259
+func newUserInputChart(cfg ChartConfig) (*module.Chart, error) {
260
+ chart := &module.Chart{
261
+ ID: cfg.ID,
262
+ Title: cfg.Title,
263
+ Units: cfg.Units,
264
+ Fam: cfg.Family,
265
+ Ctx: fmt.Sprintf("snmp.%s", cfg.ID),
266
+ Type: module.ChartType(cfg.Type),
267
+ Priority: cfg.Priority,
268
+ }
269
+
270
+ if chart.Title == "" {
271
+ chart.Title = "Untitled chart"
272
+ }
273
+ if chart.Units == "" {
274
+ chart.Units = "num"
275
+ }
276
+ if chart.Priority < module.Priority {
277
+ chart.Priority += module.Priority
278
+ }
279
+
280
+ seen := make(map[string]struct{})
281
+ var a string
282
+ for _, cfg := range cfg.Dimensions {
283
+ if cfg.Algorithm != "" {
284
+ seen[cfg.Algorithm] = struct{}{}
285
+ a = cfg.Algorithm
286
+ }
287
+ dim := &module.Dim{
288
+ ID: strings.TrimPrefix(cfg.OID, "."),
289
+ Name: cfg.Name,
290
+ Algo: module.DimAlgo(cfg.Algorithm),
291
+ Mul: cfg.Multiplier,
292
+ Div: cfg.Divisor,
293
+ }
294
+ if err := chart.AddDim(dim); err != nil {
295
+ return nil, err
296
+ }
297
+ }
298
+ if len(seen) == 1 && a != "" && len(chart.Dims) > 1 {
299
+ for _, d := range chart.Dims {
300
+ if d.Algo == "" {
301
+ d.Algo = module.DimAlgo(a)
302
+ }
303
+ }
304
+ }
305
+
306
+ return chart, nil
307
+}
src/go/plugin/go.d/collector/snmp/collect.go
+36
-10
@@ -3,6 +3,7 @@
3
package snmp
4
5
import (
6
+ "context"
7
"fmt"
8
"log/slog"
9
"maps"
@@ -13,6 +14,7 @@ import (
14
15
"github.com/google/uuid"
16
"github.com/gosnmp/gosnmp"
17
+ "golang.org/x/sync/errgroup"
18
19
"github.com/netdata/netdata/go/plugins/logger"
20
"github.com/netdata/netdata/go/plugins/plugin/go.d/agent/vnodes"
@@ -21,8 +23,6 @@ import (
23
"github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/snmputils"
24
)
25
24
-const oidSysUptime = "1.3.6.1.2.1.1.3.0"
25
-
26
func (c *Collector) collect() (map[string]int64, error) {
27
if c.snmpClient == nil {
28
snmpClient, err := c.initAndConnectSNMPClient()
@@ -59,19 +59,45 @@ func (c *Collector) collect() (map[string]int64, error) {
59
60
c.sysInfo = si
61
}
62
+ var (
63
+ snmpMx map[string]int64
64
+ pingMx map[string]int64
65
+ )
66
63
- mx := make(map[string]int64)
67
+ ctx := context.Background()
68
65
- if err := c.collectProfiles(mx); err != nil {
66
- c.Infof("failed to collect profiles: %v", err)
67
- }
69
+ g, _ := errgroup.WithContext(ctx)
70
69
- if !c.DisableLegacyCollection && len(c.customOids) > 0 {
70
- if err := c.collectOIDs(mx); err != nil {
71
- return nil, err
71
+ g.Go(func() error {
72
+ m := make(map[string]int64)
73
+ if err := c.collectSNMP(m); err != nil {
74
+ return err
75
}
76
+ snmpMx = m
77
+ return nil
78
+ })
79
+
80
+ if c.Ping.Enabled && c.prober != nil {
81
+ g.Go(func() error {
82
+ m := make(map[string]int64)
83
+ if err := c.collectPing(m); err != nil {
84
+ c.Debugf("ping: %v", err)
85
+ return nil
86
+ }
87
+ pingMx = m
88
+ return nil
89
+ })
90
}
91
92
+ if err := g.Wait(); err != nil {
93
+ return nil, err
94
+ }
95
+
96
+ mx := make(map[string]int64)
97
+
98
+ maps.Copy(mx, snmpMx)
99
+ maps.Copy(mx, pingMx)
100
+
101
return mx, nil
102
}
103
@@ -192,7 +218,7 @@ func (c *Collector) adjustMaxRepetitions(snmpClient gosnmp.Handler) (bool, error
218
}
219
220
if len(v) > 0 {
195
- //c.Config.Options.MaxRepetitions = maxReps
221
+ //c.Config.OptionsConfig.MaxRepetitions = maxReps
222
if orig != maxReps {
223
c.Infof("adjusted max_repetitions: %d → %d (took %d attempts)", orig, maxReps, attempts)
224
}
src/go/plugin/go.d/collector/snmp/collect_custom_oids.go
deleted
-45
@@ -1,45 +0,0 @@
1
-// SPDX-License-Identifier: GPL-3.0-or-later
2
-
3
-package snmp
4
-
5
-import (
6
- "github.com/gosnmp/gosnmp"
7
-)
8
-
9
-func (c *Collector) collectOIDs(mx map[string]int64) error {
10
- for i, end := 0, 0; i < len(c.customOids); i += c.Options.MaxOIDs {
11
- if end = i + c.Options.MaxOIDs; end > len(c.customOids) {
12
- end = len(c.customOids)
13
- }
14
-
15
- oids := c.customOids[i:end]
16
- resp, err := c.snmpClient.Get(oids)
17
- if err != nil {
18
- c.Errorf("cannot get SNMP data: %v", err)
19
- return err
20
- }
21
-
22
- for i, oid := range oids {
23
- if i >= len(resp.Variables) {
24
- continue
25
- }
26
-
27
- switch v := resp.Variables[i]; v.Type {
28
- case gosnmp.Boolean,
29
- gosnmp.Counter32,
30
- gosnmp.Counter64,
31
- gosnmp.Gauge32,
32
- gosnmp.TimeTicks,
33
- gosnmp.Uinteger32,
34
- gosnmp.OpaqueFloat,
35
- gosnmp.OpaqueDouble,
36
- gosnmp.Integer:
37
- mx[oid] = gosnmp.ToBigInt(v.Value).Int64()
38
- default:
39
- c.Debugf("skipping OID '%s' (unsupported type '%s')", oid, v.Type)
40
- }
41
- }
42
- }
43
-
44
- return nil
45
-}
src/go/plugin/go.d/collector/snmp/collect_ping.go
new
+26
@@ -0,0 +1,26 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package snmp
4
+
5
+func (c *Collector) collectPing(mx map[string]int64) error {
6
+ if c.prober == nil {
7
+ return nil
8
+ }
9
+
10
+ stats, err := c.prober.Ping(c.Hostname)
11
+ if err != nil {
12
+ return err
13
+ }
14
+
15
+ if stats.PacketsRecv == 0 {
16
+ // do not emit metrics if no replies
17
+ return nil
18
+ }
19
+
20
+ mx["ping_rtt_min"] = stats.MinRtt.Microseconds()
21
+ mx["ping_rtt_max"] = stats.MaxRtt.Microseconds()
22
+ mx["ping_rtt_avg"] = stats.AvgRtt.Microseconds()
23
+ mx["ping_rtt_stddev"] = stats.StdDevRtt.Microseconds()
24
+
25
+ return nil
26
+}
src/go/plugin/go.d/collector/snmp/collect_snmp.go
renamed
+54
@@ -7,9 +7,25 @@ import (
7
"sort"
8
"strings"
9
10
+ "github.com/gosnmp/gosnmp"
11
+
12
"github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
13
)
14
15
+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 {
31
return nil
@@ -124,3 +140,41 @@ func tableMetricKey(m ddsnmp.Metric) string {
140
141
return sb.String()
142
}
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
+28
-3
@@ -6,13 +6,17 @@ import (
6
"context"
7
_ "embed"
8
"fmt"
9
+ "time"
10
11
"github.com/gosnmp/gosnmp"
12
13
+ "github.com/netdata/netdata/go/plugins/logger"
14
"github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
15
"github.com/netdata/netdata/go/plugins/plugin/go.d/agent/vnodes"
16
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/ping"
17
"github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
18
"github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector"
19
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/confopt"
20
"github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/snmputils"
21
)
22
@@ -37,7 +41,7 @@ func New() *Collector {
41
VnodeDeviceDownThreshold: 3,
42
Community: "public",
43
DisableLegacyCollection: true,
40
- Options: Options{
44
+ Options: OptionsConfig{
45
Port: 161,
46
Retries: 1,
47
Timeout: 5,
@@ -45,17 +49,26 @@ func New() *Collector {
49
MaxOIDs: 60,
50
MaxRepetitions: 25,
51
},
48
- User: User{
52
+ User: UserConfig{
53
SecurityLevel: "authPriv",
54
AuthProto: "sha512",
55
PrivProto: "aes192c",
56
},
57
+ Ping: PingConfig{
58
+ ProberConfig: ping.ProberConfig{
59
+ Network: "ip",
60
+ Privileged: true,
61
+ Packets: 3,
62
+ Interval: confopt.Duration(time.Millisecond * 100),
63
+ },
64
+ },
65
},
66
67
charts: &module.Charts{},
68
seenScalarMetrics: make(map[string]bool),
69
seenTableMetrics: make(map[string]bool),
70
71
+ newProber: ping.NewProber,
72
newSnmpClient: gosnmp.NewHandler,
73
74
snmpBulkWalkOk: true,
@@ -73,6 +86,9 @@ type Collector struct {
86
seenScalarMetrics map[string]bool
87
seenTableMetrics map[string]bool
88
89
+ prober ping.Prober
90
+ newProber func(ping.ProberConfig, *logger.Logger) ping.Prober
91
+
92
newSnmpClient func() gosnmp.Handler
93
snmpClient gosnmp.Handler
94
ddSnmpColl *ddsnmpcollector.Collector
@@ -109,7 +125,16 @@ func (c *Collector) Init(context.Context) error {
125
}
126
c.charts = charts
127
112
- c.customOids = c.initOIDs()
128
+ if c.Ping.Enabled {
129
+ pr, err := c.initProber()
130
+ if err != nil {
131
+ return fmt.Errorf("failed to initialize ping prober: %v", err)
132
+ }
133
+ c.prober = pr
134
+ c.addPingCharts()
135
+ }
136
+
137
+ c.customOids = c.initCustomOIDs()
138
139
return nil
140
}
src/go/plugin/go.d/collector/snmp/collector_test.go
+2
-2
@@ -337,7 +337,7 @@ func mockInit(t *testing.T) (*snmpmock.MockHandler, func()) {
337
func prepareV3Config() Config {
338
cfg := prepareV2Config()
339
cfg.Options.Version = gosnmp.Version3.String()
340
- cfg.User = User{
340
+ cfg.User = UserConfig{
341
Name: "name",
342
SecurityLevel: "authPriv",
343
AuthProto: strings.ToLower(gosnmp.MD5.String()),
@@ -359,7 +359,7 @@ func prepareV1Config() Config {
359
UpdateEvery: 1,
360
Hostname: "192.0.2.1",
361
Community: "public",
362
- Options: Options{
362
+ Options: OptionsConfig{
363
Port: 161,
364
Retries: 1,
365
Timeout: 5,
src/go/plugin/go.d/collector/snmp/config.go
+20
-11
@@ -2,33 +2,39 @@
2
3
package snmp
4
5
-import "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/vnodes"
5
+import (
6
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/agent/vnodes"
7
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/ping"
8
+)
9
10
type (
11
Config struct {
12
UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
13
Hostname string `yaml:"hostname" json:"hostname"`
14
15
+ Community string `yaml:"community,omitempty" json:"community"`
16
+ User UserConfig `yaml:"user,omitempty" json:"user"`
17
+ Options OptionsConfig `yaml:"options,omitempty" json:"options"`
18
+
19
CreateVnode bool `yaml:"create_vnode,omitempty" json:"create_vnode"`
20
VnodeDeviceDownThreshold int `yaml:"vnode_device_down_threshold,omitempty" json:"vnode_device_down_threshold"`
21
Vnode vnodes.VirtualNode `yaml:"vnode,omitempty" json:"vnode"`
22
16
- Community string `yaml:"community,omitempty" json:"community"`
17
- User User `yaml:"user,omitempty" json:"user"`
18
-
19
- Options Options `yaml:"options,omitempty" json:"options"`
20
-
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"`
30
}
27
- NetworkInterfaceFilter struct {
28
- ByName string `yaml:"by_name,omitempty" json:"by_name"`
29
- ByType string `yaml:"by_type,omitempty" json:"by_type"`
31
+
32
+ PingConfig struct {
33
+ Enabled bool `yaml:"enabled" json:"enabled"`
34
+ ping.ProberConfig `yaml:",inline" json:",inline"`
35
}
31
- User struct {
36
+
37
+ UserConfig struct {
38
Name string `yaml:"name,omitempty" json:"name"`
39
SecurityLevel string `yaml:"level,omitempty" json:"level"`
40
AuthProto string `yaml:"auth_proto,omitempty" json:"auth_proto"`
@@ -36,7 +42,7 @@ type (
42
PrivProto string `yaml:"priv_proto,omitempty" json:"priv_proto"`
43
PrivKey string `yaml:"priv_key,omitempty" json:"priv_key"`
44
}
39
- Options struct {
45
+ OptionsConfig struct {
46
Port int `yaml:"port,omitempty" json:"port"`
47
Retries int `yaml:"retries,omitempty" json:"retries"`
48
Timeout int `yaml:"timeout,omitempty" json:"timeout"`
@@ -44,6 +50,9 @@ type (
50
MaxOIDs int `yaml:"max_request_size,omitempty" json:"max_request_size"`
51
MaxRepetitions int `yaml:"max_repetitions,omitempty" json:"max_repetitions"`
52
}
53
+)
54
+
55
+type (
56
ChartConfig struct {
57
ID string `yaml:"id" json:"id"`
58
Title string `yaml:"title" json:"title"`
src/go/plugin/go.d/collector/snmp/config_schema.json
+67
-6
@@ -348,6 +348,58 @@
348
"type": "string"
349
},
350
"uniqueItems": true
351
+ },
352
+ "ping": {
353
+ "title": "Ping",
354
+ "type": [
355
+ "object",
356
+ "null"
357
+ ],
358
+ "properties": {
359
+ "enabled": {
360
+ "title": "Enable ping",
361
+ "type": "boolean",
362
+ "default": false,
363
+ "description": "Collect ICMP round-trip time using pro-bing alongside SNMP."
364
+ },
365
+ "network": {
366
+ "title": "Network",
367
+ "description": "The protocol version used for resolving the specified hosts IP addresses.",
368
+ "type": "string",
369
+ "default": "ip",
370
+ "enum": [
371
+ "ip",
372
+ "ip4",
373
+ "ip6"
374
+ ]
375
+ },
376
+ "privileged": {
377
+ "title": "Privileged mode",
378
+ "description": "If set, sends raw ICMP ping packets; otherwise, sends unprivileged UDP ping packets (require [additional configuration](https://github.com/netdata/netdata/tree/master/src/go/plugin/go.d/collector/ping#overview)).",
379
+ "type": "boolean",
380
+ "default": true
381
+ },
382
+ "interface": {
383
+ "title": "Interface",
384
+ "description": "The network device name (e.g., `eth0`, `wlan0`) used as the source for ICMP echo requests.",
385
+ "type": "string",
386
+ "default": ""
387
+ },
388
+ "packets": {
389
+ "title": "Packets",
390
+ "description": "Number of echo requests sent each collection cycle.",
391
+ "type": "integer",
392
+ "minimum": 1,
393
+ "default": 3
394
+ },
395
+ "interval": {
396
+ "title": "Interval",
397
+ "description": "Timeout between sending ping packets, in seconds.",
398
+ "type": "number",
399
+ "minimum": 0.1,
400
+ "default": 0.1
401
+ }
402
+ }
403
}
404
},
405
"required": [
@@ -407,7 +459,16 @@
459
"ui:widget": "password"
460
}
461
},
462
+ "ping": {
463
+ "interface": {
464
+ "ui:widget": "hidden"
465
+ },
466
+ "network": {
467
+ "ui:widget": "hidden"
468
+ }
469
+ },
470
"charts": {
471
+ "ui:widget": "hidden",
472
"items": {
473
"ui:collapsible": true,
474
"type": {
@@ -443,6 +504,12 @@
504
"community"
505
]
506
},
507
+ {
508
+ "title": "Ping",
509
+ "fields": [
510
+ "ping"
511
+ ]
512
+ },
513
{
514
"title": "Vnode",
515
"fields": [
@@ -463,12 +530,6 @@
530
"user"
531
]
532
},
466
- {
467
- "title": "Charts",
468
- "fields": [
469
- "charts"
470
- ]
471
- },
533
{
534
"title": "Profiles",
535
"fields": [
src/go/plugin/go.d/collector/snmp/init.go
+24
-86
@@ -5,11 +5,13 @@ package snmp
5
import (
6
"errors"
7
"fmt"
8
- "strings"
8
"time"
9
10
"github.com/google/uuid"
11
"github.com/gosnmp/gosnmp"
12
+
13
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/ping"
14
+ "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/snmputils"
15
)
16
17
func (c *Collector) validateConfig() error {
@@ -34,7 +36,7 @@ func (c *Collector) initSNMPClient() (gosnmp.Handler, error) {
36
client.SetMaxOids(c.Options.MaxOIDs)
37
client.SetMaxRepetitions(uint32(c.Options.MaxRepetitions))
38
37
- ver := parseSNMPVersion(c.Options.Version)
39
+ ver := snmputils.ParseSNMPVersion(c.Options.Version)
40
comm := c.Community
41
42
switch ver {
@@ -50,108 +52,44 @@ func (c *Collector) initSNMPClient() (gosnmp.Handler, error) {
52
}
53
client.SetVersion(gosnmp.Version3)
54
client.SetSecurityModel(gosnmp.UserSecurityModel)
53
- client.SetMsgFlags(parseSNMPv3SecurityLevel(c.User.SecurityLevel))
55
+ client.SetMsgFlags(snmputils.ParseSNMPv3SecurityLevel(c.User.SecurityLevel))
56
client.SetSecurityParameters(&gosnmp.UsmSecurityParameters{
57
UserName: c.User.Name,
56
- AuthenticationProtocol: parseSNMPv3AuthProtocol(c.User.AuthProto),
58
+ AuthenticationProtocol: snmputils.ParseSNMPv3AuthProtocol(c.User.AuthProto),
59
AuthenticationPassphrase: c.User.AuthKey,
58
- PrivacyProtocol: parseSNMPv3PrivProtocol(c.User.PrivProto),
60
+ PrivacyProtocol: snmputils.ParseSNMPv3PrivProtocol(c.User.PrivProto),
61
PrivacyPassphrase: c.User.PrivKey,
62
})
63
default:
64
return nil, fmt.Errorf("invalid SNMP version: %s", c.Options.Version)
65
}
66
65
- c.Info(snmpClientConnInfo(client))
67
+ c.Info(snmputils.SnmpClientConnInfo(client))
68
69
return client, nil
70
}
71
70
-func (c *Collector) initOIDs() (oids []string) {
71
- for _, c := range *c.charts {
72
- for _, d := range c.Dims {
73
- oids = append(oids, d.ID)
74
- }
72
+func (c *Collector) initProber() (ping.Prober, error) {
73
+ mul := 0.9
74
+ if c.UpdateEvery > 1 {
75
+ mul = 0.8
76
}
76
- return oids
77
-}
78
-
79
-func parseSNMPVersion(version string) gosnmp.SnmpVersion {
80
- switch version {
81
- case "0", "1":
82
- return gosnmp.Version1
83
- case "2", "2c", "":
84
- return gosnmp.Version2c
85
- case "3":
86
- return gosnmp.Version3
87
- default:
88
- return gosnmp.Version2c
77
+ timeout := time.Millisecond * time.Duration(float64(c.UpdateEvery)*mul*1000)
78
+ if timeout.Milliseconds() == 0 {
79
+ return nil, errors.New("zero ping timeout")
80
}
90
-}
81
92
-func parseSNMPv3SecurityLevel(level string) gosnmp.SnmpV3MsgFlags {
93
- switch level {
94
- case "1", "none", "noAuthNoPriv", "":
95
- return gosnmp.NoAuthNoPriv
96
- case "2", "authNoPriv":
97
- return gosnmp.AuthNoPriv
98
- case "3", "authPriv":
99
- return gosnmp.AuthPriv
100
- default:
101
- return gosnmp.NoAuthNoPriv
102
- }
103
-}
82
+ conf := c.Ping.ProberConfig
83
+ conf.Timeout = timeout
84
105
-func parseSNMPv3AuthProtocol(protocol string) gosnmp.SnmpV3AuthProtocol {
106
- switch protocol {
107
- case "1", "none", "noAuth", "":
108
- return gosnmp.NoAuth
109
- case "2", "md5":
110
- return gosnmp.MD5
111
- case "3", "sha":
112
- return gosnmp.SHA
113
- case "4", "sha224":
114
- return gosnmp.SHA224
115
- case "5", "sha256":
116
- return gosnmp.SHA256
117
- case "6", "sha384":
118
- return gosnmp.SHA384
119
- case "7", "sha512":
120
- return gosnmp.SHA512
121
- default:
122
- return gosnmp.NoAuth
123
- }
124
-}
125
-
126
-func parseSNMPv3PrivProtocol(protocol string) gosnmp.SnmpV3PrivProtocol {
127
- switch protocol {
128
- case "1", "none", "noPriv", "":
129
- return gosnmp.NoPriv
130
- case "2", "des":
131
- return gosnmp.DES
132
- case "3", "aes":
133
- return gosnmp.AES
134
- case "4", "aes192":
135
- return gosnmp.AES192
136
- case "5", "aes256":
137
- return gosnmp.AES256
138
- case "6", "aes192c":
139
- return gosnmp.AES192C
140
- case "7", "aes256c":
141
- return gosnmp.AES256C
142
- default:
143
- return gosnmp.NoPriv
144
- }
85
+ return c.newProber(conf, c.Logger), nil
86
}
87
147
-func snmpClientConnInfo(c gosnmp.Handler) string {
148
- var info strings.Builder
149
- info.WriteString(fmt.Sprintf("hostname='%s',port='%d',snmp_version='%s'", c.Target(), c.Port(), c.Version()))
150
- switch c.Version() {
151
- case gosnmp.Version1, gosnmp.Version2c:
152
- info.WriteString(fmt.Sprintf(",community='%s'", c.Community()))
153
- case gosnmp.Version3:
154
- info.WriteString(fmt.Sprintf(",security_level='%d,%s'", c.MsgFlags(), c.SecurityParameters().Description()))
88
+func (c *Collector) initCustomOIDs() (oids []string) {
89
+ for _, c := range *c.charts {
90
+ for _, d := range c.Dims {
91
+ oids = append(oids, d.ID)
92
+ }
93
}
156
- return info.String()
94
+ return oids
95
}
src/go/plugin/go.d/collector/snmp/metadata.yaml
+22
@@ -199,6 +199,28 @@ modules:
199
default_value: 60
200
required: false
201
202
+ - name: ping.enabled
203
+ group: Ping
204
+ description: Enable ICMP round-trip measurements (runs alongside SNMP). When disabled, no ping metrics are collected.
205
+ default_value: false
206
+ required: false
207
+ - name: ping.privileged
208
+ group: Ping
209
+ description: Use raw ICMP (privileged). If false, unprivileged mode is used.
210
+ default_value: true
211
+ required: false
212
+ - name: ping.packets
213
+ group: Ping
214
+ description: Number of ping packets to send per iteration.
215
+ default_value: 3
216
+ required: false
217
+ - name: ping.interval
218
+ group: Ping
219
+ description: Interval between sending ping packets.
220
+ default_value: 100ms
221
+ required: false
222
+
223
+
224
- name: manual_profiles
225
group: Profiles
226
description: Profiles to apply if automatic detection cannot be used.
src/go/plugin/go.d/collector/snmp/netif.go
deleted
-412
@@ -1,412 +0,0 @@
1
-// SPDX-License-Identifier: GPL-3.0-or-later
2
-
3
-package snmp
4
-
5
-import (
6
- "fmt"
7
- "strings"
8
-)
9
-
10
-const (
11
- oidIfIndex = "1.3.6.1.2.1.2.2.1.1"
12
- oidIfDescr = "1.3.6.1.2.1.2.2.1.2"
13
- oidIfType = "1.3.6.1.2.1.2.2.1.3"
14
- oidIfMtu = "1.3.6.1.2.1.2.2.1.4"
15
- oidIfSpeed = "1.3.6.1.2.1.2.2.1.5"
16
- oidIfPhysAddress = "1.3.6.1.2.1.2.2.1.6"
17
- oidIfAdminStatus = "1.3.6.1.2.1.2.2.1.7"
18
- oidIfOperStatus = "1.3.6.1.2.1.2.2.1.8"
19
- oidIfLastChange = "1.3.6.1.2.1.2.2.1.9"
20
- oidIfInOctets = "1.3.6.1.2.1.2.2.1.10"
21
- oidIfInUcastPkts = "1.3.6.1.2.1.2.2.1.11"
22
- oidIfInNUcastPkts = "1.3.6.1.2.1.2.2.1.12"
23
- oidIfInDiscards = "1.3.6.1.2.1.2.2.1.13"
24
- oidIfInErrors = "1.3.6.1.2.1.2.2.1.14"
25
- oidIfInUnknownProtos = "1.3.6.1.2.1.2.2.1.15"
26
- oidIfOutOctets = "1.3.6.1.2.1.2.2.1.16"
27
- oidIfOutUcastPkts = "1.3.6.1.2.1.2.2.1.17"
28
- oidIfOutNUcastPkts = "1.3.6.1.2.1.2.2.1.18"
29
- oidIfOutDiscards = "1.3.6.1.2.1.2.2.1.19"
30
- oidIfOutErrors = "1.3.6.1.2.1.2.2.1.20"
31
-
32
- oidIfName = "1.3.6.1.2.1.31.1.1.1.1"
33
- oidIfInMulticastPkts = "1.3.6.1.2.1.31.1.1.1.2"
34
- oidIfInBroadcastPkts = "1.3.6.1.2.1.31.1.1.1.3"
35
- oidIfOutMulticastPkts = "1.3.6.1.2.1.31.1.1.1.4"
36
- oidIfOutBroadcastPkts = "1.3.6.1.2.1.31.1.1.1.5"
37
- oidIfHCInOctets = "1.3.6.1.2.1.31.1.1.1.6"
38
- oidIfHCInUcastPkts = "1.3.6.1.2.1.31.1.1.1.7"
39
- oidIfHCInMulticastPkts = "1.3.6.1.2.1.31.1.1.1.8"
40
- oidIfHCInBroadcastPkts = "1.3.6.1.2.1.31.1.1.1.9"
41
- oidIfHCOutOctets = "1.3.6.1.2.1.31.1.1.1.10"
42
- oidIfHCOutUcastPkts = "1.3.6.1.2.1.31.1.1.1.11"
43
- oidIfHCOutMulticastPkts = "1.3.6.1.2.1.31.1.1.1.12"
44
- oidIfHCOutBroadcastPkts = "1.3.6.1.2.1.31.1.1.1.13"
45
- oidIfHighSpeed = "1.3.6.1.2.1.31.1.1.1.15"
46
- oidIfAlias = "1.3.6.1.2.1.31.1.1.1.18"
47
-)
48
-
49
-type netInterface struct {
50
- updated bool
51
- hasCharts bool
52
- idx string
53
-
54
- ifIndex int64
55
- ifDescr string
56
- ifType int64
57
- ifMtu int64
58
- ifSpeed int64
59
- //ifPhysAddress string
60
- ifAdminStatus int64
61
- ifOperStatus int64
62
- //ifLastChange string
63
- ifInOctets int64
64
- ifInUcastPkts int64
65
- ifInNUcastPkts int64
66
- ifInDiscards int64
67
- ifInErrors int64
68
- ifInUnknownProtos int64
69
- ifOutOctets int64
70
- ifOutUcastPkts int64
71
- ifOutNUcastPkts int64
72
- ifOutDiscards int64
73
- ifOutErrors int64
74
- ifName string
75
- ifInMulticastPkts int64
76
- ifInBroadcastPkts int64
77
- ifOutMulticastPkts int64
78
- ifOutBroadcastPkts int64
79
- ifHCInOctets int64
80
- ifHCInUcastPkts int64
81
- ifHCInMulticastPkts int64
82
- ifHCInBroadcastPkts int64
83
- ifHCOutOctets int64
84
- ifHCOutUcastPkts int64
85
- ifHCOutMulticastPkts int64
86
- ifHCOutBroadcastPkts int64
87
- ifHighSpeed int64
88
- ifAlias string
89
-}
90
-
91
-func (n *netInterface) String() string {
92
- return fmt.Sprintf("iface index='%d',type='%s',name='%s',descr='%s',alias='%s'",
93
- n.ifIndex, ifTypeMapping[n.ifType], n.ifName, n.ifDescr, strings.ReplaceAll(n.ifAlias, "\n", "\\n"))
94
-}
95
-
96
-var ifAdminStatusMapping = map[int64]string{
97
- 1: "up",
98
- 2: "down",
99
- 3: "testing",
100
-}
101
-
102
-var ifOperStatusMapping = map[int64]string{
103
- 1: "up",
104
- 2: "down",
105
- 3: "testing",
106
- 4: "unknown",
107
- 5: "dormant",
108
- 6: "notPresent",
109
- 7: "lowerLayerDown",
110
-}
111
-
112
-var ifTypeMapping = map[int64]string{
113
- 1: "other",
114
- 2: "regular1822",
115
- 3: "hdh1822",
116
- 4: "ddnX25",
117
- 5: "rfc877x25",
118
- 6: "ethernetCsmacd",
119
- 7: "iso88023Csmacd",
120
- 8: "iso88024TokenBus",
121
- 9: "iso88025TokenRing",
122
- 10: "iso88026Man",
123
- 11: "starLan",
124
- 12: "proteon10Mbit",
125
- 13: "proteon80Mbit",
126
- 14: "hyperchannel",
127
- 15: "fddi",
128
- 16: "lapb",
129
- 17: "sdlc",
130
- 18: "ds1",
131
- 19: "e1",
132
- 20: "basicISDN",
133
- 21: "primaryISDN",
134
- 22: "propPointToPointSerial",
135
- 23: "ppp",
136
- 24: "softwareLoopback",
137
- 25: "eon",
138
- 26: "ethernet3Mbit",
139
- 27: "nsip",
140
- 28: "slip",
141
- 29: "ultra",
142
- 30: "ds3",
143
- 31: "sip",
144
- 32: "frameRelay",
145
- 33: "rs232",
146
- 34: "para",
147
- 35: "arcnet",
148
- 36: "arcnetPlus",
149
- 37: "atm",
150
- 38: "miox25",
151
- 39: "sonet",
152
- 40: "x25ple",
153
- 41: "iso88022llc",
154
- 42: "localTalk",
155
- 43: "smdsDxi",
156
- 44: "frameRelayService",
157
- 45: "v35",
158
- 46: "hssi",
159
- 47: "hippi",
160
- 48: "modem",
161
- 49: "aal5",
162
- 50: "sonetPath",
163
- 51: "sonetVT",
164
- 52: "smdsIcip",
165
- 53: "propVirtual",
166
- 54: "propMultiplexor",
167
- 55: "ieee80212",
168
- 56: "fibreChannel",
169
- 57: "hippiInterface",
170
- 58: "frameRelayInterconnect",
171
- 59: "aflane8023",
172
- 60: "aflane8025",
173
- 61: "cctEmul",
174
- 62: "fastEther",
175
- 63: "isdn",
176
- 64: "v11",
177
- 65: "v36",
178
- 66: "g703at64k",
179
- 67: "g703at2mb",
180
- 68: "qllc",
181
- 69: "fastEtherFX",
182
- 70: "channel",
183
- 71: "ieee80211",
184
- 72: "ibm370parChan",
185
- 73: "escon",
186
- 74: "dlsw",
187
- 75: "isdns",
188
- 76: "isdnu",
189
- 77: "lapd",
190
- 78: "ipSwitch",
191
- 79: "rsrb",
192
- 80: "atmLogical",
193
- 81: "ds0",
194
- 82: "ds0Bundle",
195
- 83: "bsc",
196
- 84: "async",
197
- 85: "cnr",
198
- 86: "iso88025Dtr",
199
- 87: "eplrs",
200
- 88: "arap",
201
- 89: "propCnls",
202
- 90: "hostPad",
203
- 91: "termPad",
204
- 92: "frameRelayMPI",
205
- 93: "x213",
206
- 94: "adsl",
207
- 95: "radsl",
208
- 96: "sdsl",
209
- 97: "vdsl",
210
- 98: "iso88025CRFPInt",
211
- 99: "myrinet",
212
- 100: "voiceEM",
213
- 101: "voiceFXO",
214
- 102: "voiceFXS",
215
- 103: "voiceEncap",
216
- 104: "voiceOverIp",
217
- 105: "atmDxi",
218
- 106: "atmFuni",
219
- 107: "atmIma",
220
- 108: "pppMultilinkBundle",
221
- 109: "ipOverCdlc",
222
- 110: "ipOverClaw",
223
- 111: "stackToStack",
224
- 112: "virtualIpAddress",
225
- 113: "mpc",
226
- 114: "ipOverAtm",
227
- 115: "iso88025Fiber",
228
- 116: "tdlc",
229
- 117: "gigabitEthernet",
230
- 118: "hdlc",
231
- 119: "lapf",
232
- 120: "v37",
233
- 121: "x25mlp",
234
- 122: "x25huntGroup",
235
- 123: "transpHdlc",
236
- 124: "interleave",
237
- 125: "fast",
238
- 126: "ip",
239
- 127: "docsCableMaclayer",
240
- 128: "docsCableDownstream",
241
- 129: "docsCableUpstream",
242
- 130: "a12MppSwitch",
243
- 131: "tunnel",
244
- 132: "coffee",
245
- 133: "ces",
246
- 134: "atmSubInterface",
247
- 135: "l2vlan",
248
- 136: "l3ipvlan",
249
- 137: "l3ipxvlan",
250
- 138: "digitalPowerline",
251
- 139: "mediaMailOverIp",
252
- 140: "dtm",
253
- 141: "dcn",
254
- 142: "ipForward",
255
- 143: "msdsl",
256
- 144: "ieee1394",
257
- 145: "if-gsn",
258
- 146: "dvbRccMacLayer",
259
- 147: "dvbRccDownstream",
260
- 148: "dvbRccUpstream",
261
- 149: "atmVirtual",
262
- 150: "mplsTunnel",
263
- 151: "srp",
264
- 152: "voiceOverAtm",
265
- 153: "voiceOverFrameRelay",
266
- 154: "idsl",
267
- 155: "compositeLink",
268
- 156: "ss7SigLink",
269
- 157: "propWirelessP2P",
270
- 158: "frForward",
271
- 159: "rfc1483",
272
- 160: "usb",
273
- 161: "ieee8023adLag",
274
- 162: "bgppolicyaccounting",
275
- 163: "frf16MfrBundle",
276
- 164: "h323Gatekeeper",
277
- 165: "h323Proxy",
278
- 166: "mpls",
279
- 167: "mfSigLink",
280
- 168: "hdsl2",
281
- 169: "shdsl",
282
- 170: "ds1FDL",
283
- 171: "pos",
284
- 172: "dvbAsiIn",
285
- 173: "dvbAsiOut",
286
- 174: "plc",
287
- 175: "nfas",
288
- 176: "tr008",
289
- 177: "gr303RDT",
290
- 178: "gr303IDT",
291
- 179: "isup",
292
- 180: "propDocsWirelessMaclayer",
293
- 181: "propDocsWirelessDownstream",
294
- 182: "propDocsWirelessUpstream",
295
- 183: "hiperlan2",
296
- 184: "propBWAp2Mp",
297
- 185: "sonetOverheadChannel",
298
- 186: "digitalWrapperOverheadChannel",
299
- 187: "aal2",
300
- 188: "radioMAC",
301
- 189: "atmRadio",
302
- 190: "imt",
303
- 191: "mvl",
304
- 192: "reachDSL",
305
- 193: "frDlciEndPt",
306
- 194: "atmVciEndPt",
307
- 195: "opticalChannel",
308
- 196: "opticalTransport",
309
- 197: "propAtm",
310
- 198: "voiceOverCable",
311
- 199: "infiniband",
312
- 200: "teLink",
313
- 201: "q2931",
314
- 202: "virtualTg",
315
- 203: "sipTg",
316
- 204: "sipSig",
317
- 205: "docsCableUpstreamChannel",
318
- 206: "econet",
319
- 207: "pon155",
320
- 208: "pon622",
321
- 209: "bridge",
322
- 210: "linegroup",
323
- 211: "voiceEMFGD",
324
- 212: "voiceFGDEANA",
325
- 213: "voiceDID",
326
- 214: "mpegTransport",
327
- 215: "sixToFour",
328
- 216: "gtp",
329
- 217: "pdnEtherLoop1",
330
- 218: "pdnEtherLoop2",
331
- 219: "opticalChannelGroup",
332
- 220: "homepna",
333
- 221: "gfp",
334
- 222: "ciscoISLvlan",
335
- 223: "actelisMetaLOOP",
336
- 224: "fcipLink",
337
- 225: "rpr",
338
- 226: "qam",
339
- 227: "lmp",
340
- 228: "cblVectaStar",
341
- 229: "docsCableMCmtsDownstream",
342
- 230: "adsl2",
343
- 231: "macSecControlledIF",
344
- 232: "macSecUncontrolledIF",
345
- 233: "aviciOpticalEther",
346
- 234: "atmbond",
347
- 235: "voiceFGDOS",
348
- 236: "mocaVersion1",
349
- 237: "ieee80216WMAN",
350
- 238: "adsl2plus",
351
- 239: "dvbRcsMacLayer",
352
- 240: "dvbTdm",
353
- 241: "dvbRcsTdma",
354
- 242: "x86Laps",
355
- 243: "wwanPP",
356
- 244: "wwanPP2",
357
- 245: "voiceEBS",
358
- 246: "ifPwType",
359
- 247: "ilan",
360
- 248: "pip",
361
- 249: "aluELP",
362
- 250: "gpon",
363
- 251: "vdsl2",
364
- 252: "capwapDot11Profile",
365
- 253: "capwapDot11Bss",
366
- 254: "capwapWtpVirtualRadio",
367
- 255: "bits",
368
- 256: "docsCableUpstreamRfPort",
369
- 257: "cableDownstreamRfPort",
370
- 258: "vmwareVirtualNic",
371
- 259: "ieee802154",
372
- 260: "otnOdu",
373
- 261: "otnOtu",
374
- 262: "ifVfiType",
375
- 263: "g9981",
376
- 264: "g9982",
377
- 265: "g9983",
378
- 266: "aluEpon",
379
- 267: "aluEponOnu",
380
- 268: "aluEponPhysicalUni",
381
- 269: "aluEponLogicalLink",
382
- 270: "aluGponOnu",
383
- 271: "aluGponPhysicalUni",
384
- 272: "vmwareNicTeam",
385
- 277: "docsOfdmDownstream",
386
- 278: "docsOfdmaUpstream",
387
- 279: "gfast",
388
- 280: "sdci",
389
- 281: "xboxWireless",
390
- 282: "fastdsl",
391
- 283: "docsCableScte55d1FwdOob",
392
- 284: "docsCableScte55d1RetOob",
393
- 285: "docsCableScte55d2DsOob",
394
- 286: "docsCableScte55d2UsOob",
395
- 287: "docsCableNdf",
396
- 288: "docsCableNdr",
397
- 289: "ptm",
398
- 290: "ghn",
399
- 291: "otnOtsi",
400
- 292: "otnOtuc",
401
- 293: "otnOduc",
402
- 294: "otnOtsig",
403
- 295: "microwaveCarrierTermination",
404
- 296: "microwaveRadioLinkTerminal",
405
- 297: "ieee8021axDrni",
406
- 298: "ax25",
407
- 299: "ieee19061nanocom",
408
- 300: "cpri",
409
- 301: "omni",
410
- 302: "roe",
411
- 303: "p2pOverLan",
412
-}
src/go/plugin/go.d/collector/snmp/testdata/config.json
+9
-1
@@ -53,5 +53,13 @@
53
"disable_legacy_collection": true,
54
"manual_profiles": [
55
"ok"
56
- ]
56
+ ],
57
+ "ping": {
58
+ "enabled": true,
59
+ "network": "ip",
60
+ "privileged": true,
61
+ "interface": "eth0",
62
+ "packets": 5,
63
+ "interval": 123.123
64
+ }
65
}
src/go/plugin/go.d/collector/snmp/testdata/config.yaml
+13
@@ -5,13 +5,16 @@ vnode_device_down_threshold: 123
5
disable_legacy_collection: yes
6
manual_profiles:
7
- "ok"
8
+
9
vnode:
10
name: "ok"
11
guid: "ok"
12
hostname: "ok"
13
labels:
14
ok: "ok"
15
+
16
community: "ok"
17
+
18
user:
19
name: "ok"
20
level: "ok"
@@ -19,6 +22,7 @@ user:
22
auth_key: "ok"
23
priv_proto: "ok"
24
priv_key: "ok"
25
+
26
options:
27
port: 123
28
retries: 123
@@ -26,6 +30,7 @@ options:
30
version: "ok"
31
max_request_size: 123
32
max_repetitions: 123
33
+
34
charts:
35
- id: "ok"
36
title: "ok"
@@ -41,3 +46,11 @@ charts:
46
algorithm: "ok"
47
multiplier: 123
48
divisor: 123
49
+
50
+ping:
51
+ enabled: yes
52
+ network: ip
53
+ privileged: yes
54
+ interface: eth0
55
+ packets: 5
56
+ interval: 123.123
src/go/plugin/go.d/pkg/snmputils/utils.go
new
+90
@@ -0,0 +1,90 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package snmputils
4
+
5
+import (
6
+ "fmt"
7
+ "strings"
8
+
9
+ "github.com/gosnmp/gosnmp"
10
+)
11
+
12
+func ParseSNMPVersion(version string) gosnmp.SnmpVersion {
13
+ switch version {
14
+ case "0", "1":
15
+ return gosnmp.Version1
16
+ case "2", "2c", "":
17
+ return gosnmp.Version2c
18
+ case "3":
19
+ return gosnmp.Version3
20
+ default:
21
+ return gosnmp.Version2c
22
+ }
23
+}
24
+
25
+func ParseSNMPv3SecurityLevel(level string) gosnmp.SnmpV3MsgFlags {
26
+ switch level {
27
+ case "1", "none", "noAuthNoPriv", "":
28
+ return gosnmp.NoAuthNoPriv
29
+ case "2", "authNoPriv":
30
+ return gosnmp.AuthNoPriv
31
+ case "3", "authPriv":
32
+ return gosnmp.AuthPriv
33
+ default:
34
+ return gosnmp.NoAuthNoPriv
35
+ }
36
+}
37
+
38
+func ParseSNMPv3AuthProtocol(protocol string) gosnmp.SnmpV3AuthProtocol {
39
+ switch protocol {
40
+ case "1", "none", "noAuth", "":
41
+ return gosnmp.NoAuth
42
+ case "2", "md5":
43
+ return gosnmp.MD5
44
+ case "3", "sha":
45
+ return gosnmp.SHA
46
+ case "4", "sha224":
47
+ return gosnmp.SHA224
48
+ case "5", "sha256":
49
+ return gosnmp.SHA256
50
+ case "6", "sha384":
51
+ return gosnmp.SHA384
52
+ case "7", "sha512":
53
+ return gosnmp.SHA512
54
+ default:
55
+ return gosnmp.NoAuth
56
+ }
57
+}
58
+
59
+func ParseSNMPv3PrivProtocol(protocol string) gosnmp.SnmpV3PrivProtocol {
60
+ switch protocol {
61
+ case "1", "none", "noPriv", "":
62
+ return gosnmp.NoPriv
63
+ case "2", "des":
64
+ return gosnmp.DES
65
+ case "3", "aes":
66
+ return gosnmp.AES
67
+ case "4", "aes192":
68
+ return gosnmp.AES192
69
+ case "5", "aes256":
70
+ return gosnmp.AES256
71
+ case "6", "aes192c":
72
+ return gosnmp.AES192C
73
+ case "7", "aes256c":
74
+ return gosnmp.AES256C
75
+ default:
76
+ return gosnmp.NoPriv
77
+ }
78
+}
79
+
80
+func SnmpClientConnInfo(c gosnmp.Handler) string {
81
+ var info strings.Builder
82
+ info.WriteString(fmt.Sprintf("hostname='%s',port='%d',snmp_version='%s'", c.Target(), c.Port(), c.Version()))
83
+ switch c.Version() {
84
+ case gosnmp.Version1, gosnmp.Version2c:
85
+ info.WriteString(fmt.Sprintf(",community='%s'", c.Community()))
86
+ case gosnmp.Version3:
87
+ info.WriteString(fmt.Sprintf(",security_level='%d,%s'", c.MsgFlags(), c.SecurityParameters().Description()))
88
+ }
89
+ return info.String()
90
+}