2015-09-03 07:16:52 +08:00
|
|
|
package ping
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
2020-12-10 01:54:51 +08:00
|
|
|
"fmt"
|
2019-07-12 06:07:58 +08:00
|
|
|
"math"
|
2021-01-29 01:30:00 +08:00
|
|
|
"net"
|
2021-01-28 23:54:29 +08:00
|
|
|
"os/exec"
|
2016-02-03 09:43:03 +08:00
|
|
|
"runtime"
|
2019-10-23 07:46:57 +08:00
|
|
|
"strings"
|
2015-09-03 07:16:52 +08:00
|
|
|
"sync"
|
2016-04-29 09:23:45 +08:00
|
|
|
"time"
|
2015-09-03 07:16:52 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
"github.com/go-ping/ping"
|
2016-01-28 05:21:36 +08:00
|
|
|
"github.com/influxdata/telegraf"
|
2021-01-28 23:54:29 +08:00
|
|
|
"github.com/influxdata/telegraf/internal"
|
2016-01-21 02:57:35 +08:00
|
|
|
"github.com/influxdata/telegraf/plugins/inputs"
|
2015-09-03 07:16:52 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// HostPinger is a function that runs the "ping" function using a list of
|
|
|
|
|
// passed arguments. This can be easily switched with a mocked ping function
|
|
|
|
|
// for unit test purposes (see ping_test.go)
|
2018-10-02 08:38:13 +08:00
|
|
|
type HostPinger func(binary string, timeout float64, args ...string) (string, error)
|
2015-09-03 07:16:52 +08:00
|
|
|
|
|
|
|
|
type Ping struct {
|
2021-01-27 06:02:43 +08:00
|
|
|
// wg is used to wait for ping with multiple URLs
|
2018-10-02 08:38:13 +08:00
|
|
|
wg sync.WaitGroup
|
|
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
// Pre-calculated interval and timeout
|
|
|
|
|
calcInterval time.Duration
|
|
|
|
|
calcTimeout time.Duration
|
|
|
|
|
|
2021-01-29 01:30:00 +08:00
|
|
|
sourceAddress string
|
|
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
Log telegraf.Logger `toml:"-"`
|
|
|
|
|
|
2015-09-03 07:16:52 +08:00
|
|
|
// Interval at which to ping (ping -i <INTERVAL>)
|
|
|
|
|
PingInterval float64 `toml:"ping_interval"`
|
|
|
|
|
|
|
|
|
|
// Number of pings to send (ping -c <COUNT>)
|
|
|
|
|
Count int
|
|
|
|
|
|
2019-07-12 06:07:58 +08:00
|
|
|
// Per-ping timeout, in seconds. 0 means no timeout (ping -W <TIMEOUT>)
|
2015-09-03 07:16:52 +08:00
|
|
|
Timeout float64
|
|
|
|
|
|
2018-02-16 12:11:07 +08:00
|
|
|
// Ping deadline, in seconds. 0 means no deadline. (ping -w <DEADLINE>)
|
|
|
|
|
Deadline int
|
|
|
|
|
|
2018-01-30 06:01:00 +08:00
|
|
|
// Interface or source address to send ping from (ping -I/-S <INTERFACE/SRC_ADDR>)
|
2015-09-03 07:16:52 +08:00
|
|
|
Interface string
|
|
|
|
|
|
|
|
|
|
// URLs to ping
|
|
|
|
|
Urls []string
|
|
|
|
|
|
2019-07-12 06:07:58 +08:00
|
|
|
// Method defines how to ping (native or exec)
|
|
|
|
|
Method string
|
|
|
|
|
|
2018-10-02 08:38:13 +08:00
|
|
|
// Ping executable binary
|
|
|
|
|
Binary string
|
|
|
|
|
|
2019-07-12 06:07:58 +08:00
|
|
|
// Arguments for ping command. When arguments is not empty, system binary will be used and
|
|
|
|
|
// other options (ping_interval, timeout, etc) will be ignored
|
2018-10-02 08:38:13 +08:00
|
|
|
Arguments []string
|
|
|
|
|
|
2019-07-12 06:07:58 +08:00
|
|
|
// Whether to resolve addresses using ipv6 or not.
|
|
|
|
|
IPv6 bool
|
|
|
|
|
|
2015-09-03 07:16:52 +08:00
|
|
|
// host ping function
|
|
|
|
|
pingHost HostPinger
|
2019-07-12 06:07:58 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
nativePingFunc NativePingFunc
|
2020-12-10 01:54:51 +08:00
|
|
|
|
|
|
|
|
// Calculate the given percentiles when using native method
|
|
|
|
|
Percentiles []int
|
2015-09-03 07:16:52 +08:00
|
|
|
}
|
|
|
|
|
|
2019-07-12 06:07:58 +08:00
|
|
|
func (*Ping) Description() string {
|
2015-09-03 07:16:52 +08:00
|
|
|
return "Ping given url(s) and return statistics"
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-28 05:08:38 +08:00
|
|
|
const sampleConfig = `
|
2019-09-21 07:49:14 +08:00
|
|
|
## Hosts to send ping packets to.
|
2018-09-01 04:59:30 +08:00
|
|
|
urls = ["example.org"]
|
|
|
|
|
|
2019-09-21 07:49:14 +08:00
|
|
|
## Method used for sending pings, can be either "exec" or "native". When set
|
|
|
|
|
## to "exec" the systems ping command will be executed. When set to "native"
|
|
|
|
|
## the plugin will send pings directly.
|
|
|
|
|
##
|
|
|
|
|
## While the default is "exec" for backwards compatibility, new deployments
|
|
|
|
|
## are encouraged to use the "native" method for improved compatibility and
|
|
|
|
|
## performance.
|
|
|
|
|
# method = "exec"
|
|
|
|
|
|
|
|
|
|
## Number of ping packets to send per interval. Corresponds to the "-c"
|
|
|
|
|
## option of the ping command.
|
2016-10-12 19:12:07 +08:00
|
|
|
# count = 1
|
2018-09-01 04:59:30 +08:00
|
|
|
|
2019-09-21 07:49:14 +08:00
|
|
|
## Time to wait between sending ping packets in seconds. Operates like the
|
|
|
|
|
## "-i" option of the ping command.
|
2016-10-12 19:12:07 +08:00
|
|
|
# ping_interval = 1.0
|
2018-09-01 04:59:30 +08:00
|
|
|
|
2019-09-21 07:49:14 +08:00
|
|
|
## If set, the time to wait for a ping response in seconds. Operates like
|
|
|
|
|
## the "-W" option of the ping command.
|
2016-10-12 19:12:07 +08:00
|
|
|
# timeout = 1.0
|
2018-09-01 04:59:30 +08:00
|
|
|
|
2019-09-21 07:49:14 +08:00
|
|
|
## If set, the total ping deadline, in seconds. Operates like the -w option
|
|
|
|
|
## of the ping command.
|
2018-02-16 12:11:07 +08:00
|
|
|
# deadline = 10
|
2018-09-01 04:59:30 +08:00
|
|
|
|
2019-09-21 07:49:14 +08:00
|
|
|
## Interface or source address to send ping from. Operates like the -I or -S
|
|
|
|
|
## option of the ping command.
|
2016-10-12 19:12:07 +08:00
|
|
|
# interface = ""
|
2018-10-02 08:38:13 +08:00
|
|
|
|
2020-12-10 01:54:51 +08:00
|
|
|
## Percentiles to calculate. This only works with the native method.
|
|
|
|
|
# percentiles = [50, 95, 99]
|
|
|
|
|
|
2019-09-21 07:49:14 +08:00
|
|
|
## Specify the ping executable binary.
|
|
|
|
|
# binary = "ping"
|
2018-10-02 08:38:13 +08:00
|
|
|
|
2019-09-21 07:49:14 +08:00
|
|
|
## Arguments for ping command. When arguments is not empty, the command from
|
|
|
|
|
## the binary option will be used and other options (ping_interval, timeout,
|
|
|
|
|
## etc) will be ignored.
|
2018-10-02 08:38:13 +08:00
|
|
|
# arguments = ["-c", "3"]
|
2019-07-12 06:07:58 +08:00
|
|
|
|
2019-09-21 07:49:14 +08:00
|
|
|
## Use only IPv6 addresses when resolving a hostname.
|
2019-07-12 06:07:58 +08:00
|
|
|
# ipv6 = false
|
2015-09-03 07:16:52 +08:00
|
|
|
`
|
|
|
|
|
|
2019-07-12 06:07:58 +08:00
|
|
|
func (*Ping) SampleConfig() string {
|
2015-09-03 07:16:52 +08:00
|
|
|
return sampleConfig
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-28 05:21:36 +08:00
|
|
|
func (p *Ping) Gather(acc telegraf.Accumulator) error {
|
2019-12-04 03:27:33 +08:00
|
|
|
for _, host := range p.Urls {
|
|
|
|
|
p.wg.Add(1)
|
|
|
|
|
go func(host string) {
|
|
|
|
|
defer p.wg.Done()
|
|
|
|
|
|
|
|
|
|
switch p.Method {
|
|
|
|
|
case "native":
|
|
|
|
|
p.pingToURLNative(host, acc)
|
|
|
|
|
default:
|
|
|
|
|
p.pingToURL(host, acc)
|
|
|
|
|
}
|
|
|
|
|
}(host)
|
2018-10-02 08:38:13 +08:00
|
|
|
}
|
2017-10-27 04:35:37 +08:00
|
|
|
|
2018-10-02 08:38:13 +08:00
|
|
|
p.wg.Wait()
|
2017-10-27 04:35:37 +08:00
|
|
|
|
2018-10-02 08:38:13 +08:00
|
|
|
return nil
|
|
|
|
|
}
|
2017-08-17 02:59:41 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
type pingStats struct {
|
|
|
|
|
ping.Statistics
|
|
|
|
|
ttl int
|
|
|
|
|
}
|
2017-08-17 02:59:41 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
type NativePingFunc func(destination string) (*pingStats, error)
|
2018-10-02 08:38:13 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
func (p *Ping) nativePing(destination string) (*pingStats, error) {
|
|
|
|
|
ps := &pingStats{}
|
2015-09-03 07:16:52 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
pinger, err := ping.NewPinger(destination)
|
2016-02-02 07:36:08 +08:00
|
|
|
if err != nil {
|
2021-01-27 06:02:43 +08:00
|
|
|
return nil, fmt.Errorf("Failed to create new pinger: %w", err)
|
2016-02-02 07:36:08 +08:00
|
|
|
}
|
2015-09-03 07:16:52 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
// Required for windows. Despite the method name, this should work without the need to elevate privileges and has been tested on Windows 10
|
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
|
pinger.SetPrivileged(true)
|
2018-10-02 08:38:13 +08:00
|
|
|
}
|
2020-03-25 08:02:23 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
if p.IPv6 {
|
|
|
|
|
pinger.SetNetwork("ip6")
|
|
|
|
|
}
|
2018-10-02 08:38:13 +08:00
|
|
|
|
2021-01-29 01:30:00 +08:00
|
|
|
pinger.Source = p.sourceAddress
|
2021-01-27 06:02:43 +08:00
|
|
|
pinger.Interval = p.calcInterval
|
|
|
|
|
pinger.Timeout = p.calcTimeout
|
|
|
|
|
|
|
|
|
|
if p.Deadline > 0 {
|
|
|
|
|
// If deadline is set ping exits regardless of how many packets have been sent or received
|
|
|
|
|
timer := time.AfterFunc(time.Duration(p.Deadline)*time.Second, func() {
|
|
|
|
|
pinger.Stop()
|
|
|
|
|
})
|
|
|
|
|
defer timer.Stop()
|
2020-03-25 08:02:23 +08:00
|
|
|
}
|
|
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
// Get Time to live (TTL) of first response, matching original implementation
|
|
|
|
|
once := &sync.Once{}
|
|
|
|
|
pinger.OnRecv = func(pkt *ping.Packet) {
|
|
|
|
|
once.Do(func() {
|
|
|
|
|
ps.ttl = pkt.Ttl
|
|
|
|
|
})
|
2020-03-25 08:02:23 +08:00
|
|
|
}
|
|
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
pinger.Count = p.Count
|
|
|
|
|
err = pinger.Run()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("Failed to run pinger: %w", err)
|
2015-09-03 07:16:52 +08:00
|
|
|
}
|
2020-03-25 08:02:23 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
ps.Statistics = *pinger.Statistics()
|
2020-03-25 08:02:23 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
return ps, nil
|
2020-03-25 08:02:23 +08:00
|
|
|
}
|
2019-07-12 06:07:58 +08:00
|
|
|
|
2020-03-25 08:02:23 +08:00
|
|
|
func (p *Ping) pingToURLNative(destination string, acc telegraf.Accumulator) {
|
2019-07-12 06:07:58 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
tags := map[string]string{"url": destination}
|
|
|
|
|
fields := map[string]interface{}{}
|
2019-07-12 06:07:58 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
stats, err := p.nativePingFunc(destination)
|
2020-03-25 08:02:23 +08:00
|
|
|
if err != nil {
|
2021-01-27 06:02:43 +08:00
|
|
|
if strings.Contains(err.Error(), "unknown") {
|
|
|
|
|
fields["result_code"] = 1
|
|
|
|
|
} else {
|
|
|
|
|
fields["result_code"] = 2
|
|
|
|
|
}
|
|
|
|
|
acc.AddFields("ping", fields, tags)
|
2020-03-25 08:02:23 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
fields = map[string]interface{}{
|
|
|
|
|
"result_code": 0,
|
|
|
|
|
"packets_transmitted": stats.PacketsSent,
|
|
|
|
|
"packets_received": stats.PacketsRecv,
|
2019-10-23 07:46:57 +08:00
|
|
|
}
|
|
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
if stats.PacketsSent == 0 {
|
|
|
|
|
fields["result_code"] = 2
|
|
|
|
|
acc.AddFields("ping", fields, tags)
|
|
|
|
|
return
|
2015-09-03 07:16:52 +08:00
|
|
|
}
|
2019-07-12 06:07:58 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
if stats.PacketsRecv == 0 {
|
|
|
|
|
fields["result_code"] = 1
|
|
|
|
|
fields["percent_packet_loss"] = float64(100)
|
|
|
|
|
acc.AddFields("ping", fields, tags)
|
|
|
|
|
return
|
|
|
|
|
}
|
2019-07-12 06:07:58 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
for _, perc := range p.Percentiles {
|
|
|
|
|
var value = percentile(durationSlice(stats.Rtts), perc)
|
|
|
|
|
var field = fmt.Sprintf("percentile%v_ms", perc)
|
|
|
|
|
fields[field] = float64(value.Nanoseconds()) / float64(time.Millisecond)
|
|
|
|
|
}
|
2019-10-23 07:46:57 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
// Set TTL only on supported platform. See golang.org/x/net/ipv4/payload_cmsg.go
|
|
|
|
|
switch runtime.GOOS {
|
|
|
|
|
case "aix", "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris":
|
|
|
|
|
fields["ttl"] = stats.ttl
|
2019-10-23 07:46:57 +08:00
|
|
|
}
|
|
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
fields["percent_packet_loss"] = float64(stats.PacketLoss)
|
|
|
|
|
fields["minimum_response_ms"] = float64(stats.MinRtt) / float64(time.Millisecond)
|
|
|
|
|
fields["average_response_ms"] = float64(stats.AvgRtt) / float64(time.Millisecond)
|
|
|
|
|
fields["maximum_response_ms"] = float64(stats.MaxRtt) / float64(time.Millisecond)
|
|
|
|
|
fields["standard_deviation_ms"] = float64(stats.StdDevRtt) / float64(time.Millisecond)
|
|
|
|
|
|
2019-07-12 06:07:58 +08:00
|
|
|
acc.AddFields("ping", fields, tags)
|
2015-09-03 07:16:52 +08:00
|
|
|
}
|
|
|
|
|
|
2020-12-10 01:54:51 +08:00
|
|
|
type durationSlice []time.Duration
|
|
|
|
|
|
|
|
|
|
func (p durationSlice) Len() int { return len(p) }
|
|
|
|
|
func (p durationSlice) Less(i, j int) bool { return p[i] < p[j] }
|
|
|
|
|
func (p durationSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
|
|
|
|
|
|
|
|
// R7 from Hyndman and Fan (1996), which matches Excel
|
|
|
|
|
func percentile(values durationSlice, perc int) time.Duration {
|
2021-01-27 06:02:43 +08:00
|
|
|
if len(values) == 0 {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
2020-12-10 01:54:51 +08:00
|
|
|
if perc < 0 {
|
|
|
|
|
perc = 0
|
|
|
|
|
}
|
|
|
|
|
if perc > 100 {
|
|
|
|
|
perc = 100
|
|
|
|
|
}
|
|
|
|
|
var percFloat = float64(perc) / 100.0
|
|
|
|
|
|
|
|
|
|
var count = len(values)
|
|
|
|
|
var rank = percFloat * float64(count-1)
|
|
|
|
|
var rankInteger = int(rank)
|
|
|
|
|
var rankFraction = rank - math.Floor(rank)
|
|
|
|
|
|
|
|
|
|
if rankInteger >= count-1 {
|
|
|
|
|
return values[count-1]
|
|
|
|
|
} else {
|
|
|
|
|
upper := values[rankInteger+1]
|
|
|
|
|
lower := values[rankInteger]
|
|
|
|
|
return lower + time.Duration(rankFraction*float64(upper-lower))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
// Init ensures the plugin is configured correctly.
|
|
|
|
|
func (p *Ping) Init() error {
|
|
|
|
|
if p.Count < 1 {
|
|
|
|
|
return errors.New("bad number of packets to transmit")
|
2019-07-12 06:07:58 +08:00
|
|
|
}
|
2019-03-09 06:30:38 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
// The interval cannot be below 0.2 seconds, matching ping implementation: https://linux.die.net/man/8/ping
|
|
|
|
|
if p.PingInterval < 0.2 {
|
|
|
|
|
p.calcInterval = time.Duration(.2 * float64(time.Second))
|
2020-12-10 01:54:51 +08:00
|
|
|
} else {
|
2021-01-27 06:02:43 +08:00
|
|
|
p.calcInterval = time.Duration(p.PingInterval * float64(time.Second))
|
2019-03-09 06:30:38 +08:00
|
|
|
}
|
2019-07-12 06:07:58 +08:00
|
|
|
|
2021-01-27 06:02:43 +08:00
|
|
|
// If no timeout is given default to 5 seconds, matching original implementation
|
|
|
|
|
if p.Timeout == 0 {
|
|
|
|
|
p.calcTimeout = time.Duration(5) * time.Second
|
|
|
|
|
} else {
|
|
|
|
|
p.calcTimeout = time.Duration(p.Timeout) * time.Second
|
2019-03-09 06:30:38 +08:00
|
|
|
}
|
2019-07-12 06:07:58 +08:00
|
|
|
|
2021-01-29 01:30:00 +08:00
|
|
|
// Support either an IP address or interface name
|
|
|
|
|
if p.Interface != "" {
|
|
|
|
|
if addr := net.ParseIP(p.Interface); addr != nil {
|
|
|
|
|
p.sourceAddress = p.Interface
|
|
|
|
|
} else {
|
|
|
|
|
i, err := net.InterfaceByName(p.Interface)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Failed to get interface: %w", err)
|
|
|
|
|
}
|
|
|
|
|
addrs, err := i.Addrs()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("Failed to get the address of interface: %w", err)
|
|
|
|
|
}
|
|
|
|
|
p.sourceAddress = addrs[0].(*net.IPNet).IP.String()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-12 06:07:58 +08:00
|
|
|
return nil
|
2015-09-03 07:16:52 +08:00
|
|
|
}
|
|
|
|
|
|
2021-01-28 23:54:29 +08:00
|
|
|
func hostPinger(binary string, timeout float64, args ...string) (string, error) {
|
|
|
|
|
bin, err := exec.LookPath(binary)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
c := exec.Command(bin, args...)
|
|
|
|
|
out, err := internal.CombinedOutputTimeout(c,
|
|
|
|
|
time.Second*time.Duration(timeout+5))
|
|
|
|
|
return string(out), err
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-03 07:16:52 +08:00
|
|
|
func init() {
|
2016-01-28 05:21:36 +08:00
|
|
|
inputs.Add("ping", func() telegraf.Input {
|
2021-01-27 06:02:43 +08:00
|
|
|
p := &Ping{
|
2021-01-28 23:54:29 +08:00
|
|
|
pingHost: hostPinger,
|
2016-10-12 19:12:07 +08:00
|
|
|
PingInterval: 1.0,
|
|
|
|
|
Count: 1,
|
|
|
|
|
Timeout: 1.0,
|
2018-02-16 12:11:07 +08:00
|
|
|
Deadline: 10,
|
2019-07-12 06:07:58 +08:00
|
|
|
Method: "exec",
|
2018-10-02 08:38:13 +08:00
|
|
|
Binary: "ping",
|
|
|
|
|
Arguments: []string{},
|
2020-12-10 01:54:51 +08:00
|
|
|
Percentiles: []int{},
|
2016-10-12 19:12:07 +08:00
|
|
|
}
|
2021-01-27 06:02:43 +08:00
|
|
|
p.nativePingFunc = p.nativePing
|
|
|
|
|
return p
|
2015-09-03 07:16:52 +08:00
|
|
|
})
|
|
|
|
|
}
|