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:
Sebastian Spaink 2021-01-26 16:02:43 -06:00 committed by GitHub
parent d41569caed
commit c237989631
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 217 additions and 310 deletions

View File

@ -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/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/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-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-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)

2
go.mod
View File

@ -49,9 +49,9 @@ require (
github.com/eclipse/paho.mqtt.golang v1.2.0
github.com/ericchiang/k8s v1.2.0
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-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-sql-driver/mysql v1.5.0
github.com/goburrow/modbus v0.1.0

5
go.sum
View File

@ -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.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/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/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=
@ -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/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-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/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
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-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-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-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=

View File

@ -1,23 +1,16 @@
package ping
import (
"context"
"errors"
"fmt"
"log"
"math"
"net"
"os/exec"
"runtime"
"sort"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/glinton/ping"
"github.com/go-ping/ping"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs"
)
@ -26,13 +19,16 @@ import (
// for unit test purposes (see ping_test.go)
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 {
// wg is used to wait for ping with multiple URLs
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>)
PingInterval float64 `toml:"ping_interval"`
@ -67,11 +63,7 @@ type Ping struct {
// host ping function
pingHost HostPinger
// resolve host function
resolveHost HostResolver
// listenAddr is the address associated with the interface defined.
listenAddr string
nativePingFunc NativePingFunc
// Calculate the given percentiles when using native method
Percentiles []int
@ -134,10 +126,6 @@ func (*Ping) SampleConfig() string {
}
func (p *Ping) Gather(acc telegraf.Accumulator) error {
if p.Interface != "" && p.listenAddr == "" {
p.listenAddr = getAddr(p.Interface)
}
for _, host := range p.Urls {
p.wg.Add(1)
go func(host string) {
@ -157,204 +145,113 @@ func (p *Ping) Gather(acc telegraf.Accumulator) error {
return nil
}
func getAddr(iface string) string {
if addr := net.ParseIP(iface); addr != nil {
return addr.String()
}
type pingStats struct {
ping.Statistics
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 {
return ""
return nil, fmt.Errorf("Failed to create new pinger: %w", err)
}
var ip net.IP
for i := range ifaces {
if ifaces[i].Name == iface {
addrs, err := ifaces[i].Addrs()
if err != nil {
return ""
}
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()
}
}
// 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)
}
return ""
}
if p.IPv6 {
pinger.SetNetwork("ip6")
}
func hostPinger(binary string, timeout float64, args ...string) (string, error) {
bin, err := exec.LookPath(binary)
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 {
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
return nil, fmt.Errorf("Failed to run pinger: %w", err)
}
if ipv6 {
ips = filterIPs(ips, isV6)
} else {
ips = filterIPs(ips, isV4)
}
ps.Statistics = *pinger.Statistics()
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)
return ps, nil
}
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
if timeout == 0 {
timeout = 5
}
tags := map[string]string{"url": destination}
fields := map[string]interface{}{}
tick := time.NewTicker(time.Duration(interval * float64(time.Second)))
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)
stats, err := p.nativePingFunc(destination)
if err != nil {
acc.AddFields(
"ping",
map[string]interface{}{"result_code": 1},
map[string]string{"url": destination},
)
acc.AddError(err)
if strings.Contains(err.Error(), "unknown") {
fields["result_code"] = 1
} else {
fields["result_code"] = 2
}
acc.AddFields("ping", fields, tags)
return
}
resps := make(chan *ping.Response)
rsps := []*ping.Response{}
r := &sync.WaitGroup{}
r.Add(1)
go func() {
for res := range resps {
rsps = append(rsps, res)
}
r.Done()
}()
wg := &sync.WaitGroup{}
c := ping.Client{}
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
}
resps <- resp
sents <- sent
}(i + 1)
}
fields = map[string]interface{}{
"result_code": 0,
"packets_transmitted": stats.PacketsSent,
"packets_received": stats.PacketsRecv,
}
finish:
wg.Wait()
close(resps)
close(sents)
r.Wait()
if doErr != nil && strings.Contains(doErr.Error(), "not permitted") {
log.Printf("D! [inputs.ping] %s", doErr.Error())
if stats.PacketsSent == 0 {
fields["result_code"] = 2
acc.AddFields("ping", fields, tags)
return
}
tags, fields := onFin(packetsSent, rsps, doErr, destination, p.Percentiles)
if stats.PacketsRecv == 0 {
fields["result_code"] = 1
fields["percent_packet_loss"] = float64(100)
acc.AddFields("ping", fields, tags)
return
}
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)
}
// 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)
}
@ -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
func percentile(values durationSlice, perc int) time.Duration {
if len(values) == 0 {
return 0
}
if 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.
func (p *Ping) Init() error {
if p.Count < 1 {
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
}
func init() {
inputs.Add("ping", func() telegraf.Input {
return &Ping{
pingHost: hostPinger,
resolveHost: hostResolver,
p := &Ping{
PingInterval: 1.0,
Count: 1,
Timeout: 1.0,
@ -492,5 +323,7 @@ func init() {
Arguments: []string{},
Percentiles: []int{},
}
p.nativePingFunc = p.nativePing
return p
})
}

View File

@ -5,11 +5,14 @@ package ping
import (
"context"
"errors"
"fmt"
"net"
"reflect"
"sort"
"testing"
"time"
"github.com/go-ping/ping"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert"
"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
func TestPingGatherNative(t *testing.T) {
t.Skip("Skipping test due to permission requirements.")
type test struct {
P *Ping
}
var acc testutil.Accumulator
p := Ping{
fakePingFunc := func(destination string) (*pingStats, error) {
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"},
Method: "native",
Count: 5,
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,
},
},
}
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_received", 5))
assert.True(t, acc.HasField("ping", "percentile50_ms"))
assert.True(t, acc.HasField("ping", "percentile95_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 TestNoPacketsSent(t *testing.T) {
p := &Ping{
Urls: []string{"localhost", "127.0.0.2"},
Method: "native",
Count: 5,
resolveHost: mockHostResolver,
Percentiles: []int{50, 95, 99},
nativePingFunc: func(destination string) (*pingStats, error) {
s := &pingStats{
Statistics: ping.Statistics{
PacketsSent: 0,
PacketsRecv: 0,
},
}
return s, nil
},
}
assert.NoError(t, acc.GatherError(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_received", 5))
assert.True(t, acc.HasField("ping", "percentile50_ms"))
assert.True(t, acc.HasField("ping", "percentile95_ms"))
assert.True(t, acc.HasField("ping", "percentile99_ms"))
}
func mockHostResolverError(ctx context.Context, ipv6 bool, host string) (*net.IPAddr, error) {
return nil, errors.New("myMock error")
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
func TestDNSLookupError(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test due to permission requirements.")
p := &Ping{
Count: 1,
Log: testutil.Logger{},
Urls: []string{"localhost"},
Method: "native",
IPv6: false,
nativePingFunc: func(destination string) (*pingStats, error) {
return nil, fmt.Errorf("unknown")
},
}
var acc testutil.Accumulator
p := Ping{
Urls: []string{"localhost"},
Method: "native",
IPv6: false,
resolveHost: mockHostResolverError,
}
acc.GatherError(p.Gather)
assert.True(t, len(acc.Errors) > 0)
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, 1, testAcc.Metrics[0].Fields["result_code"])
}