feat(go.d/snmp): add `ping_only` option (#21064)
Ilya Mashchenko committed
Sep 28, 2025 at 17:28 UTC
27dc6b9c5342e6f04078d55d2ad905a0ed7665f2
9 files changed
+99
-30
src/go/plugin/go.d/collector/ping/prober.go
+16
-4
@@ -47,14 +47,26 @@ func (p *pingProber) Ping(host string) (*probing.Statistics, error) {
47
return nil, fmt.Errorf("DNS lookup '%s' : %v", host, err)
48
}
49
50
+ pr.SetLogger(nil)
51
pr.RecordRtts = false
52
pr.RecordTTLs = false
52
- pr.Interval = p.conf.Interval.Duration()
53
- pr.Count = p.conf.Packets
54
- pr.Timeout = p.conf.Timeout
53
+
54
pr.InterfaceName = p.conf.Interface
55
pr.SetPrivileged(p.conf.Privileged)
57
- pr.SetLogger(nil)
56
+
57
+ pr.Interval = time.Millisecond * 100
58
+ pr.Count = 3
59
+ pr.Timeout = time.Second * 5
60
+
61
+ if p.conf.Interval.Duration().Milliseconds() > 0 {
62
+ pr.Interval = p.conf.Interval.Duration()
63
+ }
64
+ if p.conf.Packets > 0 {
65
+ pr.Count = p.conf.Packets
66
+ }
67
+ if p.conf.Timeout.Milliseconds() > 0 {
68
+ pr.Timeout = p.conf.Timeout
69
+ }
70
71
if err := pr.Run(); err != nil {
72
return nil, fmt.Errorf("pinging host '%s' (ip '%s' iface '%s'): %v",
src/go/plugin/go.d/collector/snmp/collect.go
+12
-4
@@ -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
@@ -121,7 +129,7 @@ func (c *Collector) ensureInitialized() error {
129
130
c.sysInfo = si
131
124
- if c.Ping.Enabled {
132
+ if c.PingOnly || c.Ping.Enabled {
133
c.addPingCharts()
134
}
135
src/go/plugin/go.d/collector/snmp/collector.go
+1
-1
@@ -119,7 +119,7 @@ func (c *Collector) Init(context.Context) error {
119
return fmt.Errorf("failed to initialize SNMP client: %v", err)
120
}
121
122
- if c.Ping.Enabled {
122
+ if c.PingOnly || c.Ping.Enabled {
123
pr, err := c.initProber()
124
if err != nil {
125
return fmt.Errorf("failed to initialize ping prober: %v", err)
src/go/plugin/go.d/collector/snmp/collector_test.go
+51
@@ -8,8 +8,13 @@ import (
8
"os"
9
"strings"
10
"testing"
11
+ "time"
12
13
+ probing "github.com/prometheus-community/pro-bing"
14
+
15
+ "github.com/netdata/netdata/go/plugins/logger"
16
"github.com/netdata/netdata/go/plugins/plugin/go.d/agent/module"
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/snmputils"
@@ -285,6 +290,27 @@ func TestCollector_Collect(t *testing.T) {
290
"snmp_device_prof_if_octets_eth0_out": 2,
291
},
292
},
293
+ "collects ping only metrics": {
294
+ prepare: func(m *snmpmock.MockHandler) *Collector {
295
+ setMockClientInitExpect(m)
296
+ setMockClientSysInfoExpect(m)
297
+
298
+ collr := New()
299
+ collr.Config = prepareV2Config()
300
+ collr.PingOnly = true
301
+ collr.CreateVnode = false
302
+ collr.newSnmpClient = func() gosnmp.Handler { return m }
303
+ collr.newProber = func(cfg ping.ProberConfig, log *logger.Logger) ping.Prober { return &mockProber{} }
304
+
305
+ return collr
306
+ },
307
+ want: map[string]int64{
308
+ "ping_rtt_min": (10 * time.Millisecond).Microseconds(),
309
+ "ping_rtt_max": (20 * time.Millisecond).Microseconds(),
310
+ "ping_rtt_avg": (15 * time.Millisecond).Microseconds(),
311
+ "ping_rtt_stddev": (5 * time.Millisecond).Microseconds(),
312
+ },
313
+ },
314
}
315
316
for name, tc := range tests {
@@ -305,6 +331,31 @@ func TestCollector_Collect(t *testing.T) {
331
}
332
}
333
334
+type mockProber struct {
335
+ errOnPing bool
336
+}
337
+
338
+func (m *mockProber) Ping(host string) (*probing.Statistics, error) {
339
+ if m.errOnPing {
340
+ return nil, errors.New("mock.Ping() error")
341
+ }
342
+
343
+ stats := probing.Statistics{
344
+ PacketsRecv: 5,
345
+ PacketsSent: 5,
346
+ PacketsRecvDuplicates: 0,
347
+ PacketLoss: 0,
348
+ Addr: host,
349
+ Rtts: nil,
350
+ MinRtt: time.Millisecond * 10,
351
+ MaxRtt: time.Millisecond * 20,
352
+ AvgRtt: time.Millisecond * 15,
353
+ StdDevRtt: time.Millisecond * 5,
354
+ }
355
+
356
+ return &stats, nil
357
+}
358
+
359
type mockDdSnmpCollector struct {
360
pms []*ddsnmp.ProfileMetrics
361
meta map[string]ddsnmp.MetaTag
src/go/plugin/go.d/collector/snmp/config.go
+2
-21
@@ -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 {
@@ -47,23 +48,3 @@ type (
48
MaxRepetitions int `yaml:"max_repetitions,omitempty" json:"max_repetitions"`
49
}
50
)
50
-
51
-type (
52
- ChartConfig struct {
53
- ID string `yaml:"id" json:"id"`
54
- Title string `yaml:"title" json:"title"`
55
- Units string `yaml:"units" json:"units"`
56
- Family string `yaml:"family" json:"family"`
57
- Type string `yaml:"type" json:"type"`
58
- Priority int `yaml:"priority" json:"priority"`
59
- IndexRange []int `yaml:"multiply_range,omitempty" json:"multiply_range"`
60
- Dimensions []DimensionConfig `yaml:"dimensions" json:"dimensions"`
61
- }
62
- DimensionConfig struct {
63
- OID string `yaml:"oid" json:"oid"`
64
- Name string `yaml:"name" json:"name"`
65
- Algorithm string `yaml:"algorithm" json:"algorithm"`
66
- Multiplier int `yaml:"multiplier" json:"multiplier"`
67
- Divisor int `yaml:"divisor" json:"divisor"`
68
- }
69
-)
src/go/plugin/go.d/collector/snmp/config_schema.json
+10
@@ -201,6 +201,12 @@
201
},
202
"uniqueItems": true
203
},
204
+ "ping_only": {
205
+ "title": "Ping only",
206
+ "description": "Collect only ICMP round-trip time and skip SNMP profile metrics.",
207
+ "type": "boolean",
208
+ "default": false
209
+ },
210
"ping": {
211
"title": "Ping",
212
"type": [
@@ -311,6 +317,9 @@
317
"ui:widget": "password"
318
}
319
},
320
+ "ping_only": {
321
+ "ui:help": "A minimal SNMP sysInfo query still runs during vnode setup for identification/metadata."
322
+ },
323
"ping": {
324
"interface": {
325
"ui:widget": "hidden"
@@ -334,6 +343,7 @@
343
{
344
"title": "Ping",
345
"fields": [
346
+ "ping_only",
347
"ping"
348
]
349
},
src/go/plugin/go.d/collector/snmp/metadata.yaml
+5
@@ -199,6 +199,11 @@ modules:
199
default_value: 60
200
required: false
201
202
+ - name: ping_only
203
+ group: Ping
204
+ description: Collect only ICMP round-trip metrics and skip periodic SNMP metrics. A minimal SNMP sysInfo request is still performed when setting up the Virtual Node (for naming/labels and metadata).
205
+ default_value: false
206
+ required: false
207
- name: ping.enabled
208
group: Ping
209
description: Enable ICMP round-trip measurements (runs alongside SNMP). When disabled, no ping metrics are collected.
src/go/plugin/go.d/collector/snmp/testdata/config.json
+1
@@ -31,6 +31,7 @@
31
"manual_profiles": [
32
"ok"
33
],
34
+ "ping_only": true,
35
"ping": {
36
"enabled": true,
37
"network": "ip",
src/go/plugin/go.d/collector/snmp/testdata/config.yaml
+1
@@ -30,6 +30,7 @@ options:
30
max_request_size: 123
31
max_repetitions: 123
32
33
+ping_only: yes
34
ping:
35
enabled: yes
36
network: ip