diff --git a/README.md b/README.md index 2099e0fa6..30b73bf87 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ Go.d.plugin is shipped with Netdata. | [phpfpm](https://github.com/netdata/go.d.plugin/tree/master/modules/phpfpm) | PHP-FPM | | [pihole](https://github.com/netdata/go.d.plugin/tree/master/modules/pihole) | Pi-hole | | [pika](https://github.com/netdata/go.d.plugin/tree/master/modules/pika) | Pika | +| [ping](https://github.com/netdata/go.d.plugin/tree/master/modules/ping) | Any network host | | [prometheus](https://github.com/netdata/go.d.plugin/tree/master/modules/prometheus) | Any Prometheus Endpoint | | [portcheck](https://github.com/netdata/go.d.plugin/tree/master/modules/portcheck) | Any TCP Endpoint | | [postgres](https://github.com/netdata/go.d.plugin/tree/master/modules/postgres) | PostgreSQL | diff --git a/config/go.d.conf b/config/go.d.conf index 1189c5ff1..c5aa70395 100644 --- a/config/go.d.conf +++ b/config/go.d.conf @@ -53,6 +53,7 @@ modules: # nvidia_smi: no # openvpn: no # openvpn_status_log: yes +# ping: yes # pgbouncer: yes # phpdaemon: yes # phpfpm: yes diff --git a/config/go.d/ping.conf b/config/go.d/ping.conf new file mode 100644 index 000000000..080440147 --- /dev/null +++ b/config/go.d/ping.conf @@ -0,0 +1,105 @@ +# netdata go.d.plugin configuration for ping +# +# This file is in YAML format. Generally the format is: +# +# name: value +# +# There are 2 sections: +# - GLOBAL +# - JOBS +# +# +# [ GLOBAL ] +# These variables set the defaults for all JOBs, however each JOB may define its own, overriding the defaults. +# +# The GLOBAL section format: +# param1: value1 +# param2: value2 +# +# Currently supported global parameters: +# - update_every +# Data collection frequency in seconds. Default: 1. +# +# - autodetection_retry +# Re-check interval in seconds. Attempts to start the job are made once every interval. +# Zero means not to schedule re-check. Default: 0. +# +# - priority +# Priority is the relative priority of the charts as rendered on the web page, +# lower numbers make the charts appear before the ones with higher numbers. Default: 70000. +# +# +# [ JOBS ] +# JOBS allow you to collect values from multiple sources. +# Each source will have its own set of charts. +# +# IMPORTANT: +# - Parameter 'name' is mandatory. +# - Jobs with the same name are mutually exclusive. Only one of them will be allowed running at any time. +# +# This allows autodetection to try several alternatives and pick the one that works. +# Any number of jobs is supported. +# +# The JOBS section format: +# +# jobs: +# - name: job1 +# param1: value1 +# param2: value2 +# +# - name: job2 +# param1: value1 +# param2: value2 +# +# - name: job2 +# param1: value1 +# +# +# [ List of JOB specific parameters ]: +# - hosts +# A list of hosts to send ping packets to. +# Syntax: +# hosts: +# - 192.0.2.0 +# - 192.0.2.1 +# - example.com +# +# - privileged +# Sets the type of ping packets. +# "no" means send an "unprivileged" UDP ping. "yes" means send a "privileged" raw ICMP ping. +# Syntax: +# privileged: yes/no +# +# - packets +# Number of ping packets to send. +# Syntax: +# packets: 3 +# +# - interval +# Time to wait between sending ping packets. +# Syntax: +# interval: 100ms +# +# +# [ JOB defaults ]: +# privileged: yes +# packets: 5 +# interval: 100ms +# +# +# [ JOB mandatory parameters ]: +# No parameters +# +# ------------------------------------------------MODULE-CONFIGURATION-------------------------------------------------- + +# update_every: 5 +# autodetection_retry: 0 +# priority: 70000 + +## Uncomment the following lines to create a data collection config: + +# jobs: +# - name: example +# hosts: +# - 192.0.2.0 +# - 192.0.2.1 diff --git a/go.mod b/go.mod index 22c726a31..41684c775 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/mattn/go-xmlrpc v0.0.3 github.com/miekg/dns v1.1.50 github.com/mitchellh/go-homedir v1.1.0 + github.com/prometheus-community/pro-bing v0.1.0 github.com/prometheus/prometheus v0.36.2 github.com/stretchr/testify v1.8.1 github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19 @@ -85,7 +86,7 @@ require ( github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/go-cmp v0.5.8 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.2.0 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/grafana/regexp v0.0.0-20220304095617-2e8d9baf4ac2 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect diff --git a/go.sum b/go.sum index 87b7b6b49..abf2d7c37 100644 --- a/go.sum +++ b/go.sum @@ -453,8 +453,9 @@ github.com/google/uuid v0.0.0-20170306145142-6a5e28554805/go.mod h1:TIyPZe4Mgqvf github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s= github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -810,6 +811,8 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus-community/pro-bing v0.1.0 h1:zjzLGhfNPP0bP1OlzGB+SJcguOViw7df12LPg2vUJh8= +github.com/prometheus-community/pro-bing v0.1.0/go.mod h1:BpWlHurD9flHtzq8wrh8QGWYz9ka9z9ZJAyOel8ej58= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= diff --git a/modules/init.go b/modules/init.go index 2081a33ff..ab409d0fd 100644 --- a/modules/init.go +++ b/modules/init.go @@ -50,6 +50,7 @@ import ( _ "github.com/netdata/go.d.plugin/modules/phpfpm" _ "github.com/netdata/go.d.plugin/modules/pihole" _ "github.com/netdata/go.d.plugin/modules/pika" + _ "github.com/netdata/go.d.plugin/modules/ping" _ "github.com/netdata/go.d.plugin/modules/portcheck" _ "github.com/netdata/go.d.plugin/modules/postgres" _ "github.com/netdata/go.d.plugin/modules/powerdns" diff --git a/modules/ping/README.md b/modules/ping/README.md new file mode 100644 index 000000000..e7edfb19e --- /dev/null +++ b/modules/ping/README.md @@ -0,0 +1,89 @@ + + +# Ping monitoring with Netdata + +This module measures round-tripe time and packet loss by sending ping messages to network hosts. + +## Requirements + +There are two operational modes: + +- privileged (send UDP ping, default). Requires + CAP_NET_RAW [capability](https://man7.org/linux/man-pages/man7/capabilities.7.html) or root privileges: + > **Note**: set automatically during Netdata installation. + + ```bash + sudo setcap CAP_NET_RAW=eip /usr/libexec/netdata/plugins.d/go.d.plugin + ``` + +- unprivileged (send raw ICMP ping, Linux only). + Requires configuring [ping_group_range](https://www.man7.org/linux/man-pages/man7/icmp.7.html): + + ```bash + sudo sysctl -w net.ipv4.ping_group_range="0 2147483647" + ``` + To persist the change add `net.ipv4.ping_group_range="0 2147483647"` to `/etc/sysctl.conf` and + execute `sudo sysctl -p`. + +The mode can be changed in the module [configuration file](#Configuration). + +## Metrics + +All metrics have "ping." prefix. + +| Metric | Scope | Dimensions | Units | +|------------------|:-----:|:--------------:|:------------:| +| host_rtt | host | min, max, avg | milliseconds | +| host_packet_loss | host | loss | percentage | +| host_packets | host | received, sent | packets | + +## Configuration + +Edit the `go.d/ping.conf` configuration file using `edit-config` from the +Netdata [config directory](https://learn.netdata.cloud/docs/configure/nodes), which is typically at `/etc/netdata`. + +```bash +cd /etc/netdata # Replace this path with your Netdata config directory +sudo ./edit-config go.d/ping.conf +``` + +Here is an example configuration: + +```yaml +jobs: + - name: example + hosts: + - 192.0.2.0 + - 192.0.2.1 + - example.com + packets: 5 # number of ping packets to send. + interval: 200ms # time to wait between sending ping packets. +``` + +For all available options please see +module [configuration file](https://github.com/netdata/go.d.plugin/blob/master/config/go.d/ping.conf). + +## Troubleshooting + +To troubleshoot issues with the `ping` collector, run the `go.d.plugin` with the debug option enabled. The output +should give you clues as to why the collector isn't working. + +First, navigate to your plugins' directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on your +system, open `netdata.conf` and look for the setting `plugins directory`. Once you're in the plugin's directory, switch +to the `netdata` user. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo -u netdata -s +``` + +You can now run the `go.d.plugin` to debug the collector: + +```bash +./go.d.plugin -d -m ping +``` diff --git a/modules/ping/charts.go b/modules/ping/charts.go new file mode 100644 index 000000000..2d52cfa3a --- /dev/null +++ b/modules/ping/charts.go @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package ping + +import ( + "fmt" + "strings" + + "github.com/netdata/go.d.plugin/agent/module" +) + +const ( + prioHostRTTLatency = module.Priority + iota + prioHostPingPacketLoss + prioHostPingPackets +) + +var hostChartsTmpl = module.Charts{ + hostRTTChartTmpl.Copy(), + hostPacketLossChartTmpl.Copy(), + hostPacketsChartTmpl.Copy(), +} + +var hostRTTChartTmpl = module.Chart{ + ID: "ping_host_%s_rtt", + Title: "Ping round-trip time", + Units: "milliseconds", + Fam: "latency", + Ctx: "ping.host_rtt", + Priority: prioHostRTTLatency, + Type: module.Area, + Dims: module.Dims{ + {ID: "host_%s_min_rtt", Name: "min", Div: 1e3}, + {ID: "host_%s_max_rtt", Name: "max", Div: 1e3}, + {ID: "host_%s_avg_rtt", Name: "avg", Div: 1e3}, + }, +} + +var hostPacketLossChartTmpl = module.Chart{ + ID: "host_host_%s_packet_loss", + Title: "Ping packet loss", + Units: "percentage", + Fam: "packet loss", + Ctx: "ping.host_packet_loss", + Priority: prioHostPingPacketLoss, + Dims: module.Dims{ + {ID: "host_%s_packet_loss", Name: "loss", Div: 1000}, + }, +} + +var hostPacketsChartTmpl = module.Chart{ + ID: "host_host_%s_packets", + Title: "Ping packets transferred", + Units: "packets", + Fam: "packets", + Ctx: "ping.host_packets", + Priority: prioHostPingPackets, + Dims: module.Dims{ + {ID: "host_%s_packets_recv", Name: "received"}, + {ID: "host_%s_packets_sent", Name: "sent"}, + }, +} + +func newHostCharts(host string) *module.Charts { + charts := hostChartsTmpl.Copy() + + for _, chart := range *charts { + chart.ID = fmt.Sprintf(chart.ID, strings.ReplaceAll(host, ".", "_")) + chart.Labels = []module.Label{ + {Key: "host", Value: host}, + } + for _, dim := range chart.Dims { + dim.ID = fmt.Sprintf(dim.ID, host) + } + } + + return charts +} + +func (p *Ping) addHostCharts(host string) { + charts := newHostCharts(host) + + if err := p.Charts().Add(*charts...); err != nil { + p.Warning(err) + } +} diff --git a/modules/ping/collect.go b/modules/ping/collect.go new file mode 100644 index 000000000..c162a2b15 --- /dev/null +++ b/modules/ping/collect.go @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package ping + +import ( + "fmt" + "sync" +) + +func (p *Ping) collect() (map[string]int64, error) { + mu := &sync.Mutex{} + mx := make(map[string]int64) + var wg sync.WaitGroup + + for _, v := range p.Hosts { + wg.Add(1) + go func(v string) { defer wg.Done(); p.pingHost(v, mx, mu) }(v) + } + wg.Wait() + + return mx, nil +} + +func (p *Ping) pingHost(host string, mx map[string]int64, mu *sync.Mutex) { + stats, err := p.prober.ping(host) + if err != nil { + p.Error(err) + return + } + + mu.Lock() + defer mu.Unlock() + + if !p.hosts[host] { + p.hosts[host] = true + p.addHostCharts(host) + } + + px := fmt.Sprintf("host_%s_", host) + if stats.PacketsRecv != 0 { + mx[px+"min_rtt"] = stats.MinRtt.Microseconds() + mx[px+"max_rtt"] = stats.MaxRtt.Microseconds() + mx[px+"avg_rtt"] = stats.AvgRtt.Microseconds() + mx[px+"std_dev_rtt"] = stats.StdDevRtt.Microseconds() + } + mx[px+"packets_recv"] = int64(stats.PacketsRecv) + mx[px+"packets_sent"] = int64(stats.PacketsSent) + mx[px+"packet_loss"] = int64(stats.PacketLoss * 1000) +} diff --git a/modules/ping/init.go b/modules/ping/init.go new file mode 100644 index 000000000..1a56bcc6c --- /dev/null +++ b/modules/ping/init.go @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package ping + +import ( + "errors" + "time" +) + +func (p *Ping) validateConfig() error { + if len(p.Hosts) == 0 { + return errors.New("'hosts' can't be empty") + } + if p.SendPackets <= 0 { + return errors.New("'send_packets' can't be <= 0") + } + return nil +} + +func (p *Ping) initProber() (prober, error) { + mul := 0.9 + if p.UpdateEvery > 1 { + mul = 0.95 + } + deadline := time.Millisecond * time.Duration(float64(p.UpdateEvery)*mul*1000) + if deadline.Milliseconds() == 0 { + return nil, errors.New("zero ping deadline") + } + + conf := pingProberConfig{ + privileged: p.Privileged, + packets: p.SendPackets, + interval: p.Interval.Duration, + deadline: deadline, + } + + return p.newProber(conf, p.Logger), nil +} diff --git a/modules/ping/ping.go b/modules/ping/ping.go new file mode 100644 index 000000000..335e61694 --- /dev/null +++ b/modules/ping/ping.go @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package ping + +import ( + "time" + + "github.com/netdata/go.d.plugin/agent/module" + "github.com/netdata/go.d.plugin/logger" + "github.com/netdata/go.d.plugin/pkg/web" + + probing "github.com/prometheus-community/pro-bing" +) + +func init() { + module.Register("ping", module.Creator{ + Defaults: module.Defaults{ + UpdateEvery: 5, + }, + Create: func() module.Module { return New() }, + }) +} + +func New() *Ping { + return &Ping{ + Config: Config{ + Privileged: true, + SendPackets: 5, + Interval: web.Duration{Duration: time.Millisecond * 100}, + }, + + charts: &module.Charts{}, + hosts: make(map[string]bool), + newProber: newPingProber, + } +} + +type ( + Config struct { + UpdateEvery int `yaml:"update_every"` + Hosts []string `yaml:"hosts"` + Privileged bool `yaml:"privileged"` + SendPackets int `yaml:"packets"` + Interval web.Duration `yaml:"interval"` + } +) + +type ( + Ping struct { + module.Base + Config `yaml:",inline"` + + charts *module.Charts + + hosts map[string]bool + + newProber func(pingProberConfig, *logger.Logger) prober + prober prober + } + prober interface { + ping(host string) (*probing.Statistics, error) + } +) + +func (p *Ping) Init() bool { + err := p.validateConfig() + if err != nil { + p.Errorf("config validation: %v", err) + return false + } + + pr, err := p.initProber() + if err != nil { + p.Errorf("init prober: %v", err) + return false + } + p.prober = pr + + return true +} + +func (p *Ping) Check() bool { + return len(p.Collect()) > 0 +} + +func (p *Ping) Charts() *module.Charts { + return p.charts +} + +func (p *Ping) Collect() map[string]int64 { + mx, err := p.collect() + if err != nil { + p.Error(err) + } + + if len(mx) == 0 { + return nil + } + return mx +} + +func (p *Ping) Cleanup() {} diff --git a/modules/ping/ping_test.go b/modules/ping/ping_test.go new file mode 100644 index 000000000..70ce45630 --- /dev/null +++ b/modules/ping/ping_test.go @@ -0,0 +1,184 @@ +package ping + +import ( + "errors" + "testing" + "time" + + "github.com/netdata/go.d.plugin/logger" + + probing "github.com/prometheus-community/pro-bing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPing_Init(t *testing.T) { + tests := map[string]struct { + wantFail bool + config Config + }{ + "fail with default": { + wantFail: true, + config: New().Config, + }, + "success when 'hosts' set": { + wantFail: false, + config: Config{ + SendPackets: 1, + Hosts: []string{"192.0.2.0"}, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ping := New() + ping.Config = test.config + ping.UpdateEvery = 1 + + if test.wantFail { + assert.False(t, ping.Init()) + } else { + assert.True(t, ping.Init()) + } + }) + } +} + +func TestPing_Charts(t *testing.T) { + assert.NotNil(t, New().Charts()) +} + +func TestPing_Cleanup(t *testing.T) { + assert.NotPanics(t, New().Cleanup) +} + +func TestPing_Check(t *testing.T) { + tests := map[string]struct { + wantFail bool + prepare func(t *testing.T) *Ping + }{ + "success when ping does not return an error": { + wantFail: false, + prepare: casePingSuccess, + }, + "fail when ping returns an error": { + wantFail: true, + prepare: casePingError, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ping := test.prepare(t) + + if test.wantFail { + assert.False(t, ping.Check()) + } else { + assert.True(t, ping.Check()) + } + }) + } +} + +func TestPing_Collect(t *testing.T) { + tests := map[string]struct { + prepare func(t *testing.T) *Ping + wantMetrics map[string]int64 + wantNumCharts int + }{ + "success when ping does not return an error": { + prepare: casePingSuccess, + wantMetrics: map[string]int64{ + "host_192.0.2.1_avg_rtt": 15000, + "host_192.0.2.1_max_rtt": 20000, + "host_192.0.2.1_min_rtt": 10000, + "host_192.0.2.1_packet_loss": 0, + "host_192.0.2.1_packets_recv": 5, + "host_192.0.2.1_packets_sent": 5, + "host_192.0.2.1_std_dev_rtt": 5000, + "host_192.0.2.2_avg_rtt": 15000, + "host_192.0.2.2_max_rtt": 20000, + "host_192.0.2.2_min_rtt": 10000, + "host_192.0.2.2_packet_loss": 0, + "host_192.0.2.2_packets_recv": 5, + "host_192.0.2.2_packets_sent": 5, + "host_192.0.2.2_std_dev_rtt": 5000, + "host_example.com_avg_rtt": 15000, + "host_example.com_max_rtt": 20000, + "host_example.com_min_rtt": 10000, + "host_example.com_packet_loss": 0, + "host_example.com_packets_recv": 5, + "host_example.com_packets_sent": 5, + "host_example.com_std_dev_rtt": 5000, + }, + wantNumCharts: 3 * len(hostChartsTmpl), + }, + "fail when ping returns an error": { + prepare: casePingError, + wantMetrics: nil, + wantNumCharts: 0, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ping := test.prepare(t) + + mx := ping.Collect() + + require.Equal(t, test.wantMetrics, mx) + + if len(test.wantMetrics) > 0 { + assert.Len(t, *ping.Charts(), test.wantNumCharts) + } + }) + } +} + +func casePingSuccess(t *testing.T) *Ping { + ping := New() + ping.UpdateEvery = 1 + ping.Hosts = []string{"192.0.2.1", "192.0.2.2", "example.com"} + ping.newProber = func(_ pingProberConfig, _ *logger.Logger) prober { + return &mockProber{} + } + require.True(t, ping.Init()) + return ping +} + +func casePingError(t *testing.T) *Ping { + ping := New() + ping.UpdateEvery = 1 + ping.Hosts = []string{"192.0.2.1", "192.0.2.2", "example.com"} + ping.newProber = func(_ pingProberConfig, _ *logger.Logger) prober { + return &mockProber{errOnPing: true} + } + require.True(t, ping.Init()) + return ping +} + +type mockProber struct { + errOnPing bool +} + +func (m *mockProber) ping(host string) (*probing.Statistics, error) { + if m.errOnPing { + return nil, errors.New("mock.ping() error") + } + + stats := probing.Statistics{ + PacketsRecv: 5, + PacketsSent: 5, + PacketsRecvDuplicates: 0, + PacketLoss: 0, + Addr: host, + Rtts: nil, + MinRtt: time.Millisecond * 10, + MaxRtt: time.Millisecond * 20, + AvgRtt: time.Millisecond * 15, + StdDevRtt: time.Millisecond * 5, + } + + return &stats, nil +} diff --git a/modules/ping/prober.go b/modules/ping/prober.go new file mode 100644 index 000000000..1b6a18451 --- /dev/null +++ b/modules/ping/prober.go @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package ping + +import ( + "fmt" + "time" + + "github.com/netdata/go.d.plugin/logger" + + probing "github.com/prometheus-community/pro-bing" +) + +func newPingProber(conf pingProberConfig, log *logger.Logger) prober { + return &pingProber{ + privileged: conf.privileged, + packets: conf.packets, + interval: conf.interval, + deadline: conf.deadline, + Logger: log, + } +} + +type pingProberConfig struct { + privileged bool + packets int + interval time.Duration + deadline time.Duration +} + +type pingProber struct { + privileged bool + packets int + interval time.Duration + deadline time.Duration + *logger.Logger +} + +func (p *pingProber) ping(host string) (*probing.Statistics, error) { + pr := probing.New(host) + + if err := pr.Resolve(); err != nil { + return nil, fmt.Errorf("DNS lookup '%s' : %v", host, err) + } + + pr.RecordRtts = false + pr.Interval = p.interval + pr.Count = p.packets + pr.Timeout = p.deadline + pr.SetPrivileged(p.privileged) + pr.SetLogger(nil) + + if err := pr.Run(); err != nil { + return nil, fmt.Errorf("pinging host '%s' (ip %s): %v", pr.Addr(), pr.IPAddr(), err) + } + + stats := pr.Statistics() + + p.Debugf("ping stats for host '%s' (ip '%s'): %+v", pr.Addr(), pr.IPAddr(), stats) + + return stats, nil +}