@cryptotaxi247 / netdata-1 / commits / 5f07bb33f

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 +}