Use go-ping for "native" execution in Ping plugin (#8679)
* Use go-ping for "native" execution in Ping plugin * Check for ipv6 and deadline out of go func * ensure dns failure * Move interval and timeout calc to init Removed dns failure check, 3rd parties libary responsibility * Rename timeout to avoid conflict * Move native ping to interface Update tests * Check for zero length
This commit is contained in:
parent
d41569caed
commit
c237989631
|
|
@ -51,9 +51,9 @@ following works:
|
||||||
- github.com/eclipse/paho.mqtt.golang [Eclipse Public License - v 1.0](https://github.com/eclipse/paho.mqtt.golang/blob/master/LICENSE)
|
- github.com/eclipse/paho.mqtt.golang [Eclipse Public License - v 1.0](https://github.com/eclipse/paho.mqtt.golang/blob/master/LICENSE)
|
||||||
- github.com/ericchiang/k8s [Apache License 2.0](https://github.com/ericchiang/k8s/blob/master/LICENSE)
|
- github.com/ericchiang/k8s [Apache License 2.0](https://github.com/ericchiang/k8s/blob/master/LICENSE)
|
||||||
- github.com/ghodss/yaml [MIT License](https://github.com/ghodss/yaml/blob/master/LICENSE)
|
- github.com/ghodss/yaml [MIT License](https://github.com/ghodss/yaml/blob/master/LICENSE)
|
||||||
- github.com/glinton/ping [MIT License](https://github.com/glinton/ping/blob/master/LICENSE)
|
|
||||||
- github.com/go-logfmt/logfmt [MIT License](https://github.com/go-logfmt/logfmt/blob/master/LICENSE)
|
- github.com/go-logfmt/logfmt [MIT License](https://github.com/go-logfmt/logfmt/blob/master/LICENSE)
|
||||||
- github.com/go-ole/go-ole [MIT License](https://github.com/go-ole/go-ole/blob/master/LICENSE)
|
- github.com/go-ole/go-ole [MIT License](https://github.com/go-ole/go-ole/blob/master/LICENSE)
|
||||||
|
- github.com/go-ping/ping [MIT License](https://github.com/go-ping/ping/blob/master/LICENSE)
|
||||||
- github.com/go-redis/redis [BSD 2-Clause "Simplified" License](https://github.com/go-redis/redis/blob/master/LICENSE)
|
- github.com/go-redis/redis [BSD 2-Clause "Simplified" License](https://github.com/go-redis/redis/blob/master/LICENSE)
|
||||||
- github.com/go-sql-driver/mysql [Mozilla Public License 2.0](https://github.com/go-sql-driver/mysql/blob/master/LICENSE)
|
- github.com/go-sql-driver/mysql [Mozilla Public License 2.0](https://github.com/go-sql-driver/mysql/blob/master/LICENSE)
|
||||||
- github.com/goburrow/modbus [BSD 3-Clause "New" or "Revised" License](https://github.com/goburrow/modbus/blob/master/LICENSE)
|
- github.com/goburrow/modbus [BSD 3-Clause "New" or "Revised" License](https://github.com/goburrow/modbus/blob/master/LICENSE)
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -49,9 +49,9 @@ require (
|
||||||
github.com/eclipse/paho.mqtt.golang v1.2.0
|
github.com/eclipse/paho.mqtt.golang v1.2.0
|
||||||
github.com/ericchiang/k8s v1.2.0
|
github.com/ericchiang/k8s v1.2.0
|
||||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
|
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
|
||||||
github.com/glinton/ping v0.1.4-0.20200311211934-5ac87da8cd96
|
|
||||||
github.com/go-logfmt/logfmt v0.4.0
|
github.com/go-logfmt/logfmt v0.4.0
|
||||||
github.com/go-ole/go-ole v1.2.1 // indirect
|
github.com/go-ole/go-ole v1.2.1 // indirect
|
||||||
|
github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663
|
||||||
github.com/go-redis/redis v6.15.9+incompatible
|
github.com/go-redis/redis v6.15.9+incompatible
|
||||||
github.com/go-sql-driver/mysql v1.5.0
|
github.com/go-sql-driver/mysql v1.5.0
|
||||||
github.com/goburrow/modbus v0.1.0
|
github.com/goburrow/modbus v0.1.0
|
||||||
|
|
|
||||||
5
go.sum
5
go.sum
|
|
@ -208,8 +208,6 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2H
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew=
|
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||||
github.com/glinton/ping v0.1.4-0.20200311211934-5ac87da8cd96 h1:YpooqMW354GG47PXNBiaCv6yCQizyP3MXD9NUPrCEQ8=
|
|
||||||
github.com/glinton/ping v0.1.4-0.20200311211934-5ac87da8cd96/go.mod h1:uY+1eqFUyotrQxF1wYFNtMeHp/swbYRsoGzfcPZ8x3o=
|
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
|
@ -225,6 +223,8 @@ github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+
|
||||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||||
|
github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663 h1:jI2GiiRh+pPbey52EVmbU6kuLiXqwy4CXZ4gwUBj8Y0=
|
||||||
|
github.com/go-ping/ping v0.0.0-20201115131931-3300c582a663/go.mod h1:35JbSyV/BYqHwwRA6Zr1uVDm1637YlNOU61wI797NPI=
|
||||||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
||||||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||||
|
|
@ -698,7 +698,6 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,16 @@
|
||||||
package ping
|
package ping
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"math"
|
"math"
|
||||||
"net"
|
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/glinton/ping"
|
"github.com/go-ping/ping"
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/internal"
|
|
||||||
"github.com/influxdata/telegraf/plugins/inputs"
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -26,13 +19,16 @@ import (
|
||||||
// for unit test purposes (see ping_test.go)
|
// for unit test purposes (see ping_test.go)
|
||||||
type HostPinger func(binary string, timeout float64, args ...string) (string, error)
|
type HostPinger func(binary string, timeout float64, args ...string) (string, error)
|
||||||
|
|
||||||
type HostResolver func(ctx context.Context, ipv6 bool, host string) (*net.IPAddr, error)
|
|
||||||
|
|
||||||
type IsCorrectNetwork func(ip net.IPAddr) bool
|
|
||||||
|
|
||||||
type Ping struct {
|
type Ping struct {
|
||||||
|
// wg is used to wait for ping with multiple URLs
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
|
|
||||||
|
// Pre-calculated interval and timeout
|
||||||
|
calcInterval time.Duration
|
||||||
|
calcTimeout time.Duration
|
||||||
|
|
||||||
|
Log telegraf.Logger `toml:"-"`
|
||||||
|
|
||||||
// Interval at which to ping (ping -i <INTERVAL>)
|
// Interval at which to ping (ping -i <INTERVAL>)
|
||||||
PingInterval float64 `toml:"ping_interval"`
|
PingInterval float64 `toml:"ping_interval"`
|
||||||
|
|
||||||
|
|
@ -67,11 +63,7 @@ type Ping struct {
|
||||||
// host ping function
|
// host ping function
|
||||||
pingHost HostPinger
|
pingHost HostPinger
|
||||||
|
|
||||||
// resolve host function
|
nativePingFunc NativePingFunc
|
||||||
resolveHost HostResolver
|
|
||||||
|
|
||||||
// listenAddr is the address associated with the interface defined.
|
|
||||||
listenAddr string
|
|
||||||
|
|
||||||
// Calculate the given percentiles when using native method
|
// Calculate the given percentiles when using native method
|
||||||
Percentiles []int
|
Percentiles []int
|
||||||
|
|
@ -134,10 +126,6 @@ func (*Ping) SampleConfig() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Ping) Gather(acc telegraf.Accumulator) error {
|
func (p *Ping) Gather(acc telegraf.Accumulator) error {
|
||||||
if p.Interface != "" && p.listenAddr == "" {
|
|
||||||
p.listenAddr = getAddr(p.Interface)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, host := range p.Urls {
|
for _, host := range p.Urls {
|
||||||
p.wg.Add(1)
|
p.wg.Add(1)
|
||||||
go func(host string) {
|
go func(host string) {
|
||||||
|
|
@ -157,204 +145,113 @@ func (p *Ping) Gather(acc telegraf.Accumulator) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAddr(iface string) string {
|
type pingStats struct {
|
||||||
if addr := net.ParseIP(iface); addr != nil {
|
ping.Statistics
|
||||||
return addr.String()
|
ttl int
|
||||||
}
|
}
|
||||||
|
|
||||||
ifaces, err := net.Interfaces()
|
type NativePingFunc func(destination string) (*pingStats, error)
|
||||||
|
|
||||||
|
func (p *Ping) nativePing(destination string) (*pingStats, error) {
|
||||||
|
ps := &pingStats{}
|
||||||
|
|
||||||
|
pinger, err := ping.NewPinger(destination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return nil, fmt.Errorf("Failed to create new pinger: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ip net.IP
|
// Required for windows. Despite the method name, this should work without the need to elevate privileges and has been tested on Windows 10
|
||||||
for i := range ifaces {
|
if runtime.GOOS == "windows" {
|
||||||
if ifaces[i].Name == iface {
|
pinger.SetPrivileged(true)
|
||||||
addrs, err := ifaces[i].Addrs()
|
}
|
||||||
|
|
||||||
|
if p.IPv6 {
|
||||||
|
pinger.SetNetwork("ip6")
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pinger.Count = p.Count
|
||||||
|
err = pinger.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return nil, fmt.Errorf("Failed to run pinger: %w", err)
|
||||||
}
|
|
||||||
if len(addrs) > 0 {
|
|
||||||
switch v := addrs[0].(type) {
|
|
||||||
case *net.IPNet:
|
|
||||||
ip = v.IP
|
|
||||||
case *net.IPAddr:
|
|
||||||
ip = v.IP
|
|
||||||
}
|
|
||||||
if len(ip) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return ip.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
ps.Statistics = *pinger.Statistics()
|
||||||
}
|
|
||||||
|
|
||||||
func hostPinger(binary string, timeout float64, args ...string) (string, error) {
|
return ps, nil
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterIPs(addrs []net.IPAddr, filterFunc IsCorrectNetwork) []net.IPAddr {
|
|
||||||
n := 0
|
|
||||||
for _, x := range addrs {
|
|
||||||
if filterFunc(x) {
|
|
||||||
addrs[n] = x
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return addrs[:n]
|
|
||||||
}
|
|
||||||
|
|
||||||
func hostResolver(ctx context.Context, ipv6 bool, destination string) (*net.IPAddr, error) {
|
|
||||||
resolver := &net.Resolver{}
|
|
||||||
ips, err := resolver.LookupIPAddr(ctx, destination)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ipv6 {
|
|
||||||
ips = filterIPs(ips, isV6)
|
|
||||||
} else {
|
|
||||||
ips = filterIPs(ips, isV4)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ips) == 0 {
|
|
||||||
return nil, errors.New("Cannot resolve ip address")
|
|
||||||
}
|
|
||||||
return &ips[0], err
|
|
||||||
}
|
|
||||||
|
|
||||||
func isV4(ip net.IPAddr) bool {
|
|
||||||
return ip.IP.To4() != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isV6(ip net.IPAddr) bool {
|
|
||||||
return !isV4(ip)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Ping) pingToURLNative(destination string, acc telegraf.Accumulator) {
|
func (p *Ping) pingToURLNative(destination string, acc telegraf.Accumulator) {
|
||||||
ctx := context.Background()
|
|
||||||
interval := p.PingInterval
|
|
||||||
if interval < 0.2 {
|
|
||||||
interval = 0.2
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout := p.Timeout
|
tags := map[string]string{"url": destination}
|
||||||
if timeout == 0 {
|
fields := map[string]interface{}{}
|
||||||
timeout = 5
|
|
||||||
}
|
|
||||||
|
|
||||||
tick := time.NewTicker(time.Duration(interval * float64(time.Second)))
|
stats, err := p.nativePingFunc(destination)
|
||||||
defer tick.Stop()
|
|
||||||
|
|
||||||
if p.Deadline > 0 {
|
|
||||||
var cancel context.CancelFunc
|
|
||||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(p.Deadline)*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
host, err := p.resolveHost(ctx, p.IPv6, destination)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
acc.AddFields(
|
if strings.Contains(err.Error(), "unknown") {
|
||||||
"ping",
|
fields["result_code"] = 1
|
||||||
map[string]interface{}{"result_code": 1},
|
} else {
|
||||||
map[string]string{"url": destination},
|
fields["result_code"] = 2
|
||||||
)
|
}
|
||||||
acc.AddError(err)
|
acc.AddFields("ping", fields, tags)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resps := make(chan *ping.Response)
|
fields = map[string]interface{}{
|
||||||
rsps := []*ping.Response{}
|
"result_code": 0,
|
||||||
|
"packets_transmitted": stats.PacketsSent,
|
||||||
r := &sync.WaitGroup{}
|
"packets_received": stats.PacketsRecv,
|
||||||
r.Add(1)
|
|
||||||
go func() {
|
|
||||||
for res := range resps {
|
|
||||||
rsps = append(rsps, res)
|
|
||||||
}
|
}
|
||||||
r.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
if stats.PacketsSent == 0 {
|
||||||
c := ping.Client{}
|
fields["result_code"] = 2
|
||||||
|
acc.AddFields("ping", fields, tags)
|
||||||
var doErr error
|
|
||||||
var packetsSent int32
|
|
||||||
|
|
||||||
type sentReq struct {
|
|
||||||
err error
|
|
||||||
sent bool
|
|
||||||
}
|
|
||||||
sents := make(chan sentReq)
|
|
||||||
|
|
||||||
r.Add(1)
|
|
||||||
go func() {
|
|
||||||
for sent := range sents {
|
|
||||||
if sent.err != nil {
|
|
||||||
doErr = sent.err
|
|
||||||
}
|
|
||||||
if sent.sent {
|
|
||||||
atomic.AddInt32(&packetsSent, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
for i := 0; i < p.Count; i++ {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
goto finish
|
|
||||||
case <-tick.C:
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeout*float64(time.Second)))
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func(seq int) {
|
|
||||||
defer wg.Done()
|
|
||||||
resp, err := c.Do(ctx, &ping.Request{
|
|
||||||
Dst: net.ParseIP(host.String()),
|
|
||||||
Src: net.ParseIP(p.listenAddr),
|
|
||||||
Seq: seq,
|
|
||||||
})
|
|
||||||
|
|
||||||
sent := sentReq{err: err, sent: true}
|
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(err.Error(), "not permitted") {
|
|
||||||
sent.sent = false
|
|
||||||
}
|
|
||||||
sents <- sent
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
resps <- resp
|
if stats.PacketsRecv == 0 {
|
||||||
sents <- sent
|
fields["result_code"] = 1
|
||||||
}(i + 1)
|
fields["percent_packet_loss"] = float64(100)
|
||||||
}
|
acc.AddFields("ping", fields, tags)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
finish:
|
for _, perc := range p.Percentiles {
|
||||||
wg.Wait()
|
var value = percentile(durationSlice(stats.Rtts), perc)
|
||||||
close(resps)
|
var field = fmt.Sprintf("percentile%v_ms", perc)
|
||||||
close(sents)
|
fields[field] = float64(value.Nanoseconds()) / float64(time.Millisecond)
|
||||||
|
|
||||||
r.Wait()
|
|
||||||
|
|
||||||
if doErr != nil && strings.Contains(doErr.Error(), "not permitted") {
|
|
||||||
log.Printf("D! [inputs.ping] %s", doErr.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tags, fields := onFin(packetsSent, rsps, doErr, destination, p.Percentiles)
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
acc.AddFields("ping", fields, tags)
|
acc.AddFields("ping", fields, tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -366,6 +263,9 @@ func (p durationSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
|
|
||||||
// R7 from Hyndman and Fan (1996), which matches Excel
|
// R7 from Hyndman and Fan (1996), which matches Excel
|
||||||
func percentile(values durationSlice, perc int) time.Duration {
|
func percentile(values durationSlice, perc int) time.Duration {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
if perc < 0 {
|
if perc < 0 {
|
||||||
perc = 0
|
perc = 0
|
||||||
}
|
}
|
||||||
|
|
@ -388,101 +288,32 @@ func percentile(values durationSlice, perc int) time.Duration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func onFin(packetsSent int32, resps []*ping.Response, err error, destination string, percentiles []int) (map[string]string, map[string]interface{}) {
|
|
||||||
packetsRcvd := len(resps)
|
|
||||||
|
|
||||||
tags := map[string]string{"url": destination}
|
|
||||||
fields := map[string]interface{}{
|
|
||||||
"result_code": 0,
|
|
||||||
"packets_transmitted": packetsSent,
|
|
||||||
"packets_received": packetsRcvd,
|
|
||||||
}
|
|
||||||
|
|
||||||
if packetsSent == 0 {
|
|
||||||
if err != nil {
|
|
||||||
fields["result_code"] = 2
|
|
||||||
}
|
|
||||||
return tags, fields
|
|
||||||
}
|
|
||||||
|
|
||||||
if packetsRcvd == 0 {
|
|
||||||
if err != nil {
|
|
||||||
fields["result_code"] = 1
|
|
||||||
}
|
|
||||||
fields["percent_packet_loss"] = float64(100)
|
|
||||||
return tags, fields
|
|
||||||
}
|
|
||||||
|
|
||||||
fields["percent_packet_loss"] = float64(int(packetsSent)-packetsRcvd) / float64(packetsSent) * 100
|
|
||||||
ttl := resps[0].TTL
|
|
||||||
|
|
||||||
var min, max, avg, total time.Duration
|
|
||||||
|
|
||||||
if len(percentiles) > 0 {
|
|
||||||
var rtt []time.Duration
|
|
||||||
for _, resp := range resps {
|
|
||||||
rtt = append(rtt, resp.RTT)
|
|
||||||
total += resp.RTT
|
|
||||||
}
|
|
||||||
sort.Sort(durationSlice(rtt))
|
|
||||||
min = rtt[0]
|
|
||||||
max = rtt[len(rtt)-1]
|
|
||||||
|
|
||||||
for _, perc := range percentiles {
|
|
||||||
var value = percentile(durationSlice(rtt), perc)
|
|
||||||
var field = fmt.Sprintf("percentile%v_ms", perc)
|
|
||||||
fields[field] = float64(value.Nanoseconds()) / float64(time.Millisecond)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
min = resps[0].RTT
|
|
||||||
max = resps[0].RTT
|
|
||||||
|
|
||||||
for _, res := range resps {
|
|
||||||
if res.RTT < min {
|
|
||||||
min = res.RTT
|
|
||||||
}
|
|
||||||
if res.RTT > max {
|
|
||||||
max = res.RTT
|
|
||||||
}
|
|
||||||
total += res.RTT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
avg = total / time.Duration(packetsRcvd)
|
|
||||||
var sumsquares time.Duration
|
|
||||||
for _, res := range resps {
|
|
||||||
sumsquares += (res.RTT - avg) * (res.RTT - avg)
|
|
||||||
}
|
|
||||||
stdDev := time.Duration(math.Sqrt(float64(sumsquares / time.Duration(packetsRcvd))))
|
|
||||||
|
|
||||||
// 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"] = ttl
|
|
||||||
}
|
|
||||||
|
|
||||||
fields["minimum_response_ms"] = float64(min.Nanoseconds()) / float64(time.Millisecond)
|
|
||||||
fields["average_response_ms"] = float64(avg.Nanoseconds()) / float64(time.Millisecond)
|
|
||||||
fields["maximum_response_ms"] = float64(max.Nanoseconds()) / float64(time.Millisecond)
|
|
||||||
fields["standard_deviation_ms"] = float64(stdDev.Nanoseconds()) / float64(time.Millisecond)
|
|
||||||
|
|
||||||
return tags, fields
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init ensures the plugin is configured correctly.
|
// Init ensures the plugin is configured correctly.
|
||||||
func (p *Ping) Init() error {
|
func (p *Ping) Init() error {
|
||||||
if p.Count < 1 {
|
if p.Count < 1 {
|
||||||
return errors.New("bad number of packets to transmit")
|
return errors.New("bad number of packets to transmit")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
} else {
|
||||||
|
p.calcInterval = time.Duration(p.PingInterval * float64(time.Second))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
inputs.Add("ping", func() telegraf.Input {
|
inputs.Add("ping", func() telegraf.Input {
|
||||||
return &Ping{
|
p := &Ping{
|
||||||
pingHost: hostPinger,
|
|
||||||
resolveHost: hostResolver,
|
|
||||||
PingInterval: 1.0,
|
PingInterval: 1.0,
|
||||||
Count: 1,
|
Count: 1,
|
||||||
Timeout: 1.0,
|
Timeout: 1.0,
|
||||||
|
|
@ -492,5 +323,7 @@ func init() {
|
||||||
Arguments: []string{},
|
Arguments: []string{},
|
||||||
Percentiles: []int{},
|
Percentiles: []int{},
|
||||||
}
|
}
|
||||||
|
p.nativePingFunc = p.nativePing
|
||||||
|
return p
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,14 @@ package ping
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-ping/ping"
|
||||||
"github.com/influxdata/telegraf/testutil"
|
"github.com/influxdata/telegraf/testutil"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
@ -403,43 +406,115 @@ func mockHostResolver(ctx context.Context, ipv6 bool, host string) (*net.IPAddr,
|
||||||
|
|
||||||
// Test that Gather function works using native ping
|
// Test that Gather function works using native ping
|
||||||
func TestPingGatherNative(t *testing.T) {
|
func TestPingGatherNative(t *testing.T) {
|
||||||
t.Skip("Skipping test due to permission requirements.")
|
type test struct {
|
||||||
|
P *Ping
|
||||||
|
}
|
||||||
|
|
||||||
var acc testutil.Accumulator
|
fakePingFunc := func(destination string) (*pingStats, error) {
|
||||||
p := Ping{
|
s := &pingStats{
|
||||||
|
Statistics: ping.Statistics{
|
||||||
|
PacketsSent: 5,
|
||||||
|
PacketsRecv: 5,
|
||||||
|
Rtts: []time.Duration{
|
||||||
|
1 * time.Millisecond,
|
||||||
|
2 * time.Millisecond,
|
||||||
|
3 * time.Millisecond,
|
||||||
|
4 * time.Millisecond,
|
||||||
|
5 * time.Millisecond,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ttl: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []test{
|
||||||
|
{
|
||||||
|
P: &Ping{
|
||||||
Urls: []string{"localhost", "127.0.0.2"},
|
Urls: []string{"localhost", "127.0.0.2"},
|
||||||
Method: "native",
|
Method: "native",
|
||||||
Count: 5,
|
Count: 5,
|
||||||
resolveHost: mockHostResolver,
|
|
||||||
Percentiles: []int{50, 95, 99},
|
Percentiles: []int{50, 95, 99},
|
||||||
|
nativePingFunc: fakePingFunc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
P: &Ping{
|
||||||
|
Urls: []string{"localhost", "127.0.0.2"},
|
||||||
|
Method: "native",
|
||||||
|
Count: 5,
|
||||||
|
PingInterval: 1,
|
||||||
|
Percentiles: []int{50, 95, 99},
|
||||||
|
nativePingFunc: fakePingFunc,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.NoError(t, acc.GatherError(p.Gather))
|
for _, tc := range tests {
|
||||||
|
var acc testutil.Accumulator
|
||||||
|
err := tc.P.Init()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, acc.GatherError(tc.P.Gather))
|
||||||
assert.True(t, acc.HasPoint("ping", map[string]string{"url": "localhost"}, "packets_transmitted", 5))
|
assert.True(t, acc.HasPoint("ping", map[string]string{"url": "localhost"}, "packets_transmitted", 5))
|
||||||
assert.True(t, acc.HasPoint("ping", map[string]string{"url": "localhost"}, "packets_received", 5))
|
assert.True(t, acc.HasPoint("ping", map[string]string{"url": "localhost"}, "packets_received", 5))
|
||||||
assert.True(t, acc.HasField("ping", "percentile50_ms"))
|
assert.True(t, acc.HasField("ping", "percentile50_ms"))
|
||||||
assert.True(t, acc.HasField("ping", "percentile95_ms"))
|
assert.True(t, acc.HasField("ping", "percentile95_ms"))
|
||||||
assert.True(t, acc.HasField("ping", "percentile99_ms"))
|
assert.True(t, acc.HasField("ping", "percentile99_ms"))
|
||||||
|
assert.True(t, acc.HasField("ping", "percent_packet_loss"))
|
||||||
|
assert.True(t, acc.HasField("ping", "minimum_response_ms"))
|
||||||
|
assert.True(t, acc.HasField("ping", "average_response_ms"))
|
||||||
|
assert.True(t, acc.HasField("ping", "maximum_response_ms"))
|
||||||
|
assert.True(t, acc.HasField("ping", "standard_deviation_ms"))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mockHostResolverError(ctx context.Context, ipv6 bool, host string) (*net.IPAddr, error) {
|
func TestNoPacketsSent(t *testing.T) {
|
||||||
return nil, errors.New("myMock error")
|
p := &Ping{
|
||||||
|
Urls: []string{"localhost", "127.0.0.2"},
|
||||||
|
Method: "native",
|
||||||
|
Count: 5,
|
||||||
|
Percentiles: []int{50, 95, 99},
|
||||||
|
nativePingFunc: func(destination string) (*pingStats, error) {
|
||||||
|
s := &pingStats{
|
||||||
|
Statistics: ping.Statistics{
|
||||||
|
PacketsSent: 0,
|
||||||
|
PacketsRecv: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAcc testutil.Accumulator
|
||||||
|
err := p.Init()
|
||||||
|
require.NoError(t, err)
|
||||||
|
p.pingToURLNative("localhost", &testAcc)
|
||||||
|
require.Zero(t, testAcc.Errors)
|
||||||
|
require.True(t, testAcc.HasField("ping", "result_code"))
|
||||||
|
require.Equal(t, 2, testAcc.Metrics[0].Fields["result_code"])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test failed DNS resolutions
|
// Test failed DNS resolutions
|
||||||
func TestDNSLookupError(t *testing.T) {
|
func TestDNSLookupError(t *testing.T) {
|
||||||
if testing.Short() {
|
p := &Ping{
|
||||||
t.Skip("Skipping test due to permission requirements.")
|
Count: 1,
|
||||||
}
|
Log: testutil.Logger{},
|
||||||
|
|
||||||
var acc testutil.Accumulator
|
|
||||||
p := Ping{
|
|
||||||
Urls: []string{"localhost"},
|
Urls: []string{"localhost"},
|
||||||
Method: "native",
|
Method: "native",
|
||||||
IPv6: false,
|
IPv6: false,
|
||||||
resolveHost: mockHostResolverError,
|
nativePingFunc: func(destination string) (*pingStats, error) {
|
||||||
|
return nil, fmt.Errorf("unknown")
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
acc.GatherError(p.Gather)
|
var testAcc testutil.Accumulator
|
||||||
assert.True(t, len(acc.Errors) > 0)
|
err := p.Init()
|
||||||
|
require.NoError(t, err)
|
||||||
|
p.pingToURLNative("localhost", &testAcc)
|
||||||
|
require.Zero(t, testAcc.Errors)
|
||||||
|
require.True(t, testAcc.HasField("ping", "result_code"))
|
||||||
|
require.Equal(t, 1, testAcc.Metrics[0].Fields["result_code"])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue