@cryptotaxi247 / netdata-1 / commits / ca55ac041

feat(go.d/snmp): add `ping_only` option (#22180)

Ilya Mashchenko committed Apr 10, 2026 at 15:13 UTC ca55ac041958378b8fe50a04fa82d77176681b70
8 files changed +141 -8
src/go/plugin/go.d/collector/snmp/collect.go
+13 -5
@@ -30,15 +30,23 @@ func (c *Collector) collect() (map[string]int64, error) {
30 return nil, err
31 }
32
33 - mx, err := c.collectMetrics()
34 - if err != nil {
33 + if c.PingOnly {
34 + return c.collectPingOnly()
35 + }
36 + return c.collectDeviceMetrics()
37 +}
38 +
39 +func (c *Collector) collectPingOnly() (map[string]int64, error) {
40 + mx := make(map[string]int64)
41 +
42 + if err := c.collectPing(mx); err != nil {
43 return nil, err
44 }
45
46 return mx, nil
47 }
48
41 -func (c *Collector) collectMetrics() (map[string]int64, error) {
49 +func (c *Collector) collectDeviceMetrics() (map[string]int64, error) {
50 var (
51 snmpMx map[string]int64
52 pingMx map[string]int64
@@ -112,7 +120,7 @@ func (c *Collector) ensureInitialized() error {
120 })
121 }
122
115 - if c.ddSnmpColl == nil && !c.Ping.Enabled {
123 + if c.ddSnmpColl == nil && !c.PingOnly && !c.Ping.Enabled {
124 return errors.New("no profiles found and ping disabled")
125 }
126
@@ -130,7 +138,7 @@ func (c *Collector) ensureInitialized() error {
138
139 c.sysInfo = si
140
133 - if c.Ping.Enabled {
141 + if c.PingOnly || c.Ping.Enabled {
142 c.addPingCharts()
143 }
144
src/go/plugin/go.d/collector/snmp/collector.go
+7 -1
@@ -135,7 +135,7 @@ func (c *Collector) Init(context.Context) error {
135 return fmt.Errorf("failed to initialize SNMP client: %v", err)
136 }
137
138 - if c.Ping.Enabled {
138 + if c.PingOnly || c.Ping.Enabled {
139 pr, err := c.initProber()
140 if err != nil {
141 return fmt.Errorf("failed to initialize ping prober: %v", err)
@@ -159,6 +159,12 @@ func (c *Collector) Check(context.Context) error {
159 return err
160 }
161
162 + if c.PingOnly && c.prober != nil {
163 + if _, err := c.prober.Ping(c.Hostname); err != nil && isPingUnrecoverableError(err) {
164 + return fmt.Errorf("ping check failed: %v", err)
165 + }
166 + }
167 +
168 return nil
169 }
170
src/go/plugin/go.d/collector/snmp/collector_test.go
+106
@@ -7,8 +7,14 @@ import (
7 "errors"
8 "os"
9 "strings"
10 + "syscall"
11 "testing"
12 + "time"
13
14 + probing "github.com/prometheus-community/pro-bing"
15 +
16 + "github.com/netdata/netdata/go/plugins/logger"
17 + "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/ping"
18 "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp"
19 "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/snmp/ddsnmp/ddsnmpcollector"
20 "github.com/netdata/netdata/go/plugins/plugin/go.d/pkg/collecttest"
@@ -185,6 +191,58 @@ func TestCollector_Check(t *testing.T) {
191 return c
192 },
193 },
194 +
195 + "success: ping_only with successful ping": {
196 + wantErr: false,
197 + prepare: func(m *snmpmock.MockHandler) *Collector {
198 + setMockClientInitExpect(m)
199 + setMockClientSysInfoExpect(m)
200 +
201 + c := New()
202 + c.Config = prepareV2Config()
203 + c.PingOnly = true
204 + c.CreateVnode = false
205 + c.newSnmpClient = func() gosnmp.Handler { return m }
206 + c.newProber = func(cfg ping.ProberConfig, log *logger.Logger) ping.Prober { return &mockProber{} }
207 + return c
208 + },
209 + },
210 +
211 + "success: ping_only with recoverable ping error": {
212 + wantErr: false,
213 + prepare: func(m *snmpmock.MockHandler) *Collector {
214 + setMockClientInitExpect(m)
215 + setMockClientSysInfoExpect(m)
216 +
217 + c := New()
218 + c.Config = prepareV2Config()
219 + c.PingOnly = true
220 + c.CreateVnode = false
221 + c.newSnmpClient = func() gosnmp.Handler { return m }
222 + c.newProber = func(cfg ping.ProberConfig, log *logger.Logger) ping.Prober {
223 + return &mockProber{pingErr: errors.New("host unreachable")}
224 + }
225 + return c
226 + },
227 + },
228 +
229 + "failure: ping_only with unrecoverable ping error": {
230 + wantErr: true,
231 + prepare: func(m *snmpmock.MockHandler) *Collector {
232 + setMockClientInitExpect(m)
233 + setMockClientSysInfoExpect(m)
234 +
235 + c := New()
236 + c.Config = prepareV2Config()
237 + c.PingOnly = true
238 + c.CreateVnode = false
239 + c.newSnmpClient = func() gosnmp.Handler { return m }
240 + c.newProber = func(cfg ping.ProberConfig, log *logger.Logger) ping.Prober {
241 + return &mockProber{pingErr: syscall.EPERM}
242 + }
243 + return c
244 + },
245 + },
246 }
247
248 for name, tc := range tests {
@@ -326,6 +384,30 @@ func TestCollector_Collect(t *testing.T) {
384 },
385 },
386 }
387 + tests["collects ping only metrics"] = struct {
388 + prepare func(m *snmpmock.MockHandler) *Collector
389 + want map[string]int64
390 + }{
391 + prepare: func(m *snmpmock.MockHandler) *Collector {
392 + setMockClientInitExpect(m)
393 + setMockClientSysInfoExpect(m)
394 +
395 + collr := New()
396 + collr.Config = prepareV2Config()
397 + collr.PingOnly = true
398 + collr.CreateVnode = false
399 + collr.newSnmpClient = func() gosnmp.Handler { return m }
400 + collr.newProber = func(cfg ping.ProberConfig, log *logger.Logger) ping.Prober { return &mockProber{} }
401 +
402 + return collr
403 + },
404 + want: map[string]int64{
405 + "ping_rtt_min": (10 * time.Millisecond).Microseconds(),
406 + "ping_rtt_max": (20 * time.Millisecond).Microseconds(),
407 + "ping_rtt_avg": (15 * time.Millisecond).Microseconds(),
408 + "ping_rtt_stddev": (5 * time.Millisecond).Microseconds(),
409 + },
410 + }
411
412 for name, tc := range tests {
413 t.Run(name, func(t *testing.T) {
@@ -345,6 +427,30 @@ func TestCollector_Collect(t *testing.T) {
427 }
428 }
429
430 +type mockProber struct {
431 + pingErr error
432 +}
433 +
434 +func (m *mockProber) Ping(host string) (*probing.Statistics, error) {
435 + if m.pingErr != nil {
436 + return nil, m.pingErr
437 + }
438 +
439 + stats := probing.Statistics{
440 + PacketsRecv: 5,
441 + PacketsSent: 5,
442 + PacketsRecvDuplicates: 0,
443 + PacketLoss: 0,
444 + Addr: host,
445 + MinRtt: time.Millisecond * 10,
446 + MaxRtt: time.Millisecond * 20,
447 + AvgRtt: time.Millisecond * 15,
448 + StdDevRtt: time.Millisecond * 5,
449 + }
450 +
451 + return &stats, nil
452 +}
453 +
454 type mockDdSnmpCollector struct {
455 pms []*ddsnmp.ProfileMetrics
456 meta map[string]ddsnmp.MetaTag
src/go/plugin/go.d/collector/snmp/config.go
+2 -1
@@ -22,7 +22,8 @@ type (
22
23 ManualProfiles []string `yaml:"manual_profiles,omitempty" json:"manual_profiles"`
24
25 - Ping PingConfig `yaml:"ping,omitempty" json:"ping"`
25 + PingOnly bool `yaml:"ping_only,omitempty" json:"ping_only"`
26 + Ping PingConfig `yaml:"ping,omitempty" json:"ping"`
27 }
28
29 PingConfig struct {
src/go/plugin/go.d/collector/snmp/config_schema.json
+10
@@ -208,6 +208,12 @@
208 },
209 "uniqueItems": true
210 },
211 + "ping_only": {
212 + "title": "Ping only",
213 + "description": "Collect only ICMP round-trip time and skip SNMP profile metrics. Implies ping is enabled regardless of the ping.enabled setting.",
214 + "type": "boolean",
215 + "default": false
216 + },
217 "ping": {
218 "title": "Ping",
219 "type": [
@@ -321,6 +327,9 @@
327 "ui:help": "Advanced. Usually left empty. Required when targeting a specific context on multi-context agents or when driving snmpsim virtual devices."
328 }
329 },
330 + "ping_only": {
331 + "ui:help": "A minimal SNMP sysInfo query still runs at startup for device identification, profile matching, and metadata."
332 + },
333 "ping": {
334 "interface": {
335 "ui:widget": "hidden"
@@ -344,6 +353,7 @@
353 {
354 "title": "Ping",
355 "fields": [
356 + "ping_only",
357 "ping"
358 ]
359 },
src/go/plugin/go.d/collector/snmp/metadata.yaml
+1 -1
@@ -390,7 +390,7 @@ modules:
390
391 - name: ping_only
392 group: Ping
393 - description: Collect only ICMP round-trip metrics and skip periodic SNMP polling. A minimal SNMP sysInfo probe still runs at setup for naming/labels/metadata.
393 + description: Collect only ICMP round-trip metrics and skip periodic SNMP polling. Implies ping is enabled regardless of the `ping.enabled` setting. A minimal SNMP sysInfo probe still runs at setup for naming/labels/metadata.
394 default_value: false
395 required: false
396 - name: ping.enabled
src/go/plugin/go.d/collector/snmp/testdata/config.json
+1
@@ -32,6 +32,7 @@
32 "manual_profiles": [
33 "ok"
34 ],
35 + "ping_only": true,
36 "ping": {
37 "enabled": true,
38 "network": "ip",
src/go/plugin/go.d/collector/snmp/testdata/config.yaml
+1
@@ -31,6 +31,7 @@ options:
31 max_request_size: 123
32 max_repetitions: 123
33
34 +ping_only: yes
35 ping:
36 enabled: yes
37 network: ip