1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,199 @@
# Ping Input Plugin
Sends a ping message by executing the system ping command and reports the
results.
This plugin has two main methods of operation: `exec` and `native`. The
recommended method is `native`, which has greater system compatibility and
performance. However, for backwards compatibility the `exec` method is the
default.
When using `method = "exec"`, the systems ping utility is executed to send the
ping packets.
Most ping command implementations are supported, one notable exception being
that there is currently no support for GNU Inetutils ping. You may instead use
the iputils-ping implementation:
```sh
apt-get install iputils-ping
```
When using `method = "native"` a ping is sent and the results are reported in
native Go by the Telegraf process, eliminating the need to execute the system
`ping` command.
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Configuration
```toml @sample.conf
# Ping given url(s) and return statistics
[[inputs.ping]]
## Hosts to send ping packets to.
urls = ["example.org"]
## 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.
# count = 1
## Time to wait between sending ping packets in seconds. Operates like the
## "-i" option of the ping command.
# ping_interval = 1.0
## If set, the time to wait for a ping response in seconds. Operates like
## the "-W" option of the ping command.
# timeout = 1.0
## If set, the total ping deadline, in seconds. Operates like the -w option
## of the ping command.
# deadline = 10
## Interface or source address to send ping from. Operates like the -I or -S
## option of the ping command.
# interface = ""
## Percentiles to calculate. This only works with the native method.
# percentiles = [50, 95, 99]
## Specify the ping executable binary.
# binary = "ping"
## 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.
# arguments = ["-c", "3"]
## Use only IPv4 addresses when resolving a hostname. By default, both IPv4
## and IPv6 can be used.
# ipv4 = false
## Use only IPv6 addresses when resolving a hostname. By default, both IPv4
## and IPv6 can be used.
# ipv6 = false
## Number of data bytes to be sent. Corresponds to the "-s"
## option of the ping command. This only works with the native method.
# size = 56
```
### File Limit
Since this plugin runs the ping command, it may need to open multiple files per
host. The number of files used is lessened with the `native` option but still
many files are used. With a large host list you may receive a `too many open
files` error.
To increase this limit on platforms using systemd the recommended method is to
use the "drop-in directory", usually located at
`/etc/systemd/system/telegraf.service.d`.
You can create or edit a drop-in file in the correct location using:
```sh
systemctl edit telegraf
```
Increase the number of open files:
```ini
[Service]
LimitNOFILE=8192
```
Restart Telegraf:
```sh
systemctl restart telegraf
```
### Linux Permissions
When using `method = "native"`, Telegraf will attempt to use privileged raw ICMP
sockets. On most systems, doing so requires `CAP_NET_RAW` capabilities or for
Telegraf to be run as root.
With systemd:
```sh
systemctl edit telegraf
```
```ini
[Service]
CapabilityBoundingSet=CAP_NET_RAW
AmbientCapabilities=CAP_NET_RAW
```
```sh
systemctl restart telegraf
```
Without systemd:
```sh
setcap cap_net_raw=eip /usr/bin/telegraf
```
Reference [`man 7 capabilities`][man 7 capabilities] for more information about
setting capabilities.
[man 7 capabilities]: http://man7.org/linux/man-pages/man7/capabilities.7.html
### Other OS Permissions
When using `method = "native"`, you will need permissions similar to the
executable ping program for your OS.
## Metrics
- ping
- tags:
- url
- fields:
- packets_transmitted (integer)
- packets_received (integer)
- percent_packet_loss (float)
- ttl (integer, Not available on Windows)
- average_response_ms (float)
- minimum_response_ms (float)
- maximum_response_ms (float)
- standard_deviation_ms (float, Available on Windows only with method = "native")
- percentile\<N\>_ms (float, Where `<N>` is the percentile specified in `percentiles`. Available with method = "native" only)
- errors (float, Windows only)
- reply_received (integer, Windows with method = "exec" only)
- percent_reply_loss (float, Windows with method = "exec" only)
- result_code (int, success = 0, no such host = 1, ping error = 2)
### reply_received vs packets_received
On Windows systems with `method = "exec"`, the "Destination net unreachable"
reply will increment `packets_received` but not `reply_received`*.
### ttl
There is currently no support for TTL on windows with `"native"`; track
progress at <https://github.com/golang/go/issues/7175> and
<https://github.com/golang/go/issues/7174>
## Example Output
```text
ping,url=example.org average_response_ms=23.066,ttl=63,maximum_response_ms=24.64,minimum_response_ms=22.451,packets_received=5i,packets_transmitted=5i,percent_packet_loss=0,result_code=0i,standard_deviation_ms=0.809 1535747258000000000
```

313
plugins/inputs/ping/ping.go Normal file
View file

@ -0,0 +1,313 @@
//go:generate ../../../tools/readme_config_includer/generator
package ping
import (
_ "embed"
"errors"
"fmt"
"math"
"net"
"os/exec"
"runtime"
"sort"
"strings"
"sync"
"time"
ping "github.com/prometheus-community/pro-bing"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
const (
defaultPingDataBytesSize = 56
)
type Ping struct {
Urls []string `toml:"urls"` // URLs to ping
Method string `toml:"method"` // Method defines how to ping (native or exec)
Count int `toml:"count"` // Number of pings to send (ping -c <COUNT>)
PingInterval float64 `toml:"ping_interval"` // Interval at which to ping (ping -i <INTERVAL>)
Timeout float64 `toml:"timeout"` // Per-ping timeout, in seconds. 0 means no timeout (ping -W <TIMEOUT>)
Deadline int `toml:"deadline"` // Ping deadline, in seconds. 0 means no deadline. (ping -w <DEADLINE>)
Interface string `toml:"interface"` // Interface or source address to send ping from (ping -I/-S <INTERFACE/SRC_ADDR>)
Percentiles []int `toml:"percentiles"` // Calculate the given percentiles when using native method
Binary string `toml:"binary"` // Ping executable binary
// Arguments for ping command. When arguments are not empty, system binary will be used and other options (ping_interval, timeout, etc.) will be ignored
Arguments []string `toml:"arguments"`
IPv4 bool `toml:"ipv4"` // Whether to resolve addresses using ipv4 or not.
IPv6 bool `toml:"ipv6"` // Whether to resolve addresses using ipv6 or not.
Size *int `toml:"size"` // Packet size
Log telegraf.Logger `toml:"-"`
wg sync.WaitGroup // wg is used to wait for ping with multiple URLs
calcInterval time.Duration // Pre-calculated interval and timeout
calcTimeout time.Duration
sourceAddress string
pingHost hostPingerFunc // host ping function
nativePingFunc nativePingFunc
}
// hostPingerFunc 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)
type hostPingerFunc func(binary string, timeout float64, args ...string) (string, error)
type nativePingFunc func(destination string) (*pingStats, error)
type durationSlice []time.Duration
type pingStats struct {
ping.Statistics
ttl int
}
func (*Ping) SampleConfig() string {
return sampleConfig
}
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 (p *Ping) Gather(acc telegraf.Accumulator) error {
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)
}
p.wg.Wait()
return nil
}
func (p *Ping) nativePing(destination string) (*pingStats, error) {
ps := &pingStats{}
pinger, err := ping.NewPinger(destination)
if err != nil {
return nil, fmt.Errorf("failed to create new pinger: %w", err)
}
pinger.SetPrivileged(true)
if p.IPv4 && p.IPv6 {
pinger.SetNetwork("ip")
} else if p.IPv4 {
pinger.SetNetwork("ip4")
} else if p.IPv6 {
pinger.SetNetwork("ip6")
}
if p.Method == "native" {
pinger.Size = defaultPingDataBytesSize
if p.Size != nil {
pinger.Size = *p.Size
}
}
// Support either an IP address or interface name
if p.Interface != "" && p.sourceAddress == "" {
if addr := net.ParseIP(p.Interface); addr != nil {
p.sourceAddress = p.Interface
} else {
i, err := net.InterfaceByName(p.Interface)
if err != nil {
return nil, fmt.Errorf("failed to get interface: %w", err)
}
addrs, err := i.Addrs()
if err != nil {
return nil, fmt.Errorf("failed to get the address of interface: %w", err)
}
if len(addrs) == 0 {
return nil, fmt.Errorf("no address found for interface %s", p.Interface)
}
p.sourceAddress = addrs[0].(*net.IPNet).IP.String()
}
}
pinger.Source = p.sourceAddress
pinger.Interval = p.calcInterval
if p.Deadline > 0 {
pinger.Timeout = time.Duration(p.Deadline) * time.Second
}
// 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 strings.Contains(err.Error(), "operation not permitted") {
if runtime.GOOS == "linux" {
return nil, errors.New("permission changes required, enable CAP_NET_RAW capabilities (refer to the ping plugin's README.md for more info)")
}
return nil, errors.New("permission changes required, refer to the ping plugin's README.md for more info")
}
return nil, err
}
ps.Statistics = *pinger.Statistics()
return ps, nil
}
func (p *Ping) pingToURLNative(destination string, acc telegraf.Accumulator) {
tags := map[string]string{"url": destination}
stats, err := p.nativePingFunc(destination)
if err != nil {
p.Log.Errorf("ping failed: %s", err.Error())
fields := make(map[string]interface{}, 1)
if strings.Contains(err.Error(), "unknown") {
fields["result_code"] = 1
} else {
fields["result_code"] = 2
}
acc.AddFields("ping", fields, tags)
return
}
fields := map[string]interface{}{
"result_code": 0,
"packets_transmitted": stats.PacketsSent,
"packets_received": stats.PacketsRecv,
}
if stats.PacketsSent == 0 {
p.Log.Debug("no packets sent")
fields["result_code"] = 2
acc.AddFields("ping", fields, tags)
return
}
if stats.PacketsRecv == 0 {
p.Log.Debug("no packets received")
fields["result_code"] = 1
fields["percent_packet_loss"] = float64(100)
acc.AddFields("ping", fields, tags)
return
}
sort.Sort(durationSlice(stats.Rtts))
for _, perc := range p.Percentiles {
var value = percentile(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)
}
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 {
if len(values) == 0 {
return 0
}
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]
}
upper := values[rankInteger+1]
lower := values[rankInteger]
return lower + time.Duration(rankFraction*float64(upper-lower))
}
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
}
func init() {
inputs.Add("ping", func() telegraf.Input {
p := &Ping{
pingHost: hostPinger,
PingInterval: 1.0,
Count: 1,
Timeout: 1.0,
Deadline: 10,
Method: "exec",
Binary: "ping",
Arguments: make([]string, 0),
Percentiles: make([]int, 0),
}
p.nativePingFunc = p.nativePing
return p
})
}

View file

@ -0,0 +1,286 @@
//go:build !windows
package ping
import (
"errors"
"fmt"
"os/exec"
"regexp"
"runtime"
"strconv"
"strings"
"syscall"
"github.com/influxdata/telegraf"
)
type roundTripTimeStats struct {
min float64
avg float64
max float64
stddev float64
}
type statistics struct {
packetsTransmitted int
packetsReceived int
ttl int
roundTripTimeStats
}
func (p *Ping) pingToURL(u string, acc telegraf.Accumulator) {
tags := map[string]string{"url": u}
fields := map[string]interface{}{"result_code": 0}
out, err := p.pingHost(p.Binary, 60.0, p.args(u, runtime.GOOS)...)
if err != nil {
// Some implementations of ping return a non-zero exit code on
// timeout, if this occurs we will not exit and try to parse
// the output.
// Linux iputils-ping returns 1, BSD-derived ping returns 2.
status := -1
var exitError *exec.ExitError
if errors.As(err, &exitError) {
if ws, ok := exitError.Sys().(syscall.WaitStatus); ok {
status = ws.ExitStatus()
fields["result_code"] = status
}
}
var timeoutExitCode int
switch runtime.GOOS {
case "freebsd", "netbsd", "openbsd", "darwin":
timeoutExitCode = 2
case "linux":
timeoutExitCode = 1
default:
timeoutExitCode = 1
}
if status != timeoutExitCode {
// Combine go err + stderr output
out = strings.TrimSpace(out)
if len(out) > 0 {
acc.AddError(fmt.Errorf("host %q: %w - %s", u, err, out))
} else {
acc.AddError(fmt.Errorf("host %q: %w", u, err))
}
fields["result_code"] = 2
acc.AddFields("ping", fields, tags)
return
}
}
stats, err := processPingOutput(out)
if err != nil {
// fatal error
acc.AddError(fmt.Errorf("%q: %w", u, err))
fields["result_code"] = 2
acc.AddFields("ping", fields, tags)
return
}
// Calculate packet loss percentage
percentPacketLoss := float64(stats.packetsTransmitted-stats.packetsReceived) / float64(stats.packetsTransmitted) * 100.0
fields["packets_transmitted"] = stats.packetsTransmitted
fields["packets_received"] = stats.packetsReceived
fields["percent_packet_loss"] = percentPacketLoss
if stats.ttl >= 0 {
fields["ttl"] = stats.ttl
}
if stats.min >= 0 {
fields["minimum_response_ms"] = stats.min
}
if stats.avg >= 0 {
fields["average_response_ms"] = stats.avg
}
if stats.max >= 0 {
fields["maximum_response_ms"] = stats.max
}
if stats.stddev >= 0 {
fields["standard_deviation_ms"] = stats.stddev
}
acc.AddFields("ping", fields, tags)
}
// args returns the arguments for the 'ping' executable
func (p *Ping) args(url, system string) []string {
if len(p.Arguments) > 0 {
return append(p.Arguments, url)
}
// build the ping command args based on toml config
args := []string{"-c", strconv.Itoa(p.Count), "-n", "-s", "16"}
if p.PingInterval > 0 {
args = append(args, "-i", strconv.FormatFloat(p.PingInterval, 'f', -1, 64))
}
if p.Timeout > 0 {
switch system {
case "darwin":
args = append(args, "-W", strconv.FormatFloat(p.Timeout*1000, 'f', -1, 64))
case "freebsd":
if strings.Contains(p.Binary, "ping6") && freeBSDMajorVersion() <= 12 {
args = append(args, "-x", strconv.FormatFloat(p.Timeout*1000, 'f', -1, 64))
} else {
args = append(args, "-W", strconv.FormatFloat(p.Timeout*1000, 'f', -1, 64))
}
case "netbsd", "openbsd":
args = append(args, "-W", strconv.FormatFloat(p.Timeout*1000, 'f', -1, 64))
case "linux":
args = append(args, "-W", strconv.FormatFloat(p.Timeout, 'f', -1, 64))
default:
// Not sure the best option here, just assume GNU ping?
args = append(args, "-W", strconv.FormatFloat(p.Timeout, 'f', -1, 64))
}
}
if p.Deadline > 0 {
switch system {
case "freebsd":
if strings.Contains(p.Binary, "ping6") && freeBSDMajorVersion() <= 12 {
args = append(args, "-X", strconv.Itoa(p.Deadline))
} else {
args = append(args, "-t", strconv.Itoa(p.Deadline))
}
case "darwin", "netbsd", "openbsd":
args = append(args, "-t", strconv.Itoa(p.Deadline))
case "linux":
args = append(args, "-w", strconv.Itoa(p.Deadline))
default:
// not sure the best option here, just assume gnu ping?
args = append(args, "-w", strconv.Itoa(p.Deadline))
}
}
if p.Interface != "" {
switch system {
case "darwin":
args = append(args, "-I", p.Interface)
case "freebsd", "netbsd", "openbsd":
args = append(args, "-S", p.Interface)
case "linux":
args = append(args, "-I", p.Interface)
default:
// not sure the best option here, just assume gnu ping?
args = append(args, "-i", p.Interface)
}
}
args = append(args, url)
return args
}
// processPingOutput takes in a string output from the ping command, like:
//
// ping www.google.com (173.194.115.84): 56 data bytes
// 64 bytes from 173.194.115.84: icmp_seq=0 ttl=54 time=52.172 ms
// 64 bytes from 173.194.115.84: icmp_seq=1 ttl=54 time=34.843 ms
//
// --- www.google.com ping statistics ---
// 2 packets transmitted, 2 packets received, 0.0% packet loss
// round-trip min/avg/max/stddev = 34.843/43.508/52.172/8.664 ms
//
// It returns (<transmitted packets>, <received packets>, <average response>)
func processPingOutput(out string) (statistics, error) {
stats := statistics{
packetsTransmitted: 0,
packetsReceived: 0,
ttl: -1,
roundTripTimeStats: roundTripTimeStats{
min: -1.0,
avg: -1.0,
max: -1.0,
stddev: -1.0,
},
}
// Set this error to nil if we find a 'transmitted' line
err := errors.New("fatal error processing ping output")
lines := strings.Split(out, "\n")
for _, line := range lines {
// Reading only first TTL, ignoring other TTL messages
if stats.ttl == -1 && (strings.Contains(line, "ttl=") || strings.Contains(line, "hlim=")) {
stats.ttl, err = getTTL(line)
} else if strings.Contains(line, "transmitted") && strings.Contains(line, "received") {
stats.packetsTransmitted, stats.packetsReceived, err = getPacketStats(line)
if err != nil {
return stats, err
}
} else if strings.Contains(line, "min/avg/max") {
stats.roundTripTimeStats, err = checkRoundTripTimeStats(line)
if err != nil {
return stats, err
}
}
}
return stats, err
}
func getPacketStats(line string) (trans, recv int, err error) {
recv = 0
stats := strings.Split(line, ", ")
// Transmitted packets
trans, err = strconv.Atoi(strings.Split(stats[0], " ")[0])
if err != nil {
return trans, recv, err
}
// Received packets
recv, err = strconv.Atoi(strings.Split(stats[1], " ")[0])
return trans, recv, err
}
func getTTL(line string) (int, error) {
ttlLine := regexp.MustCompile(`(ttl|hlim)=(\d+)`)
ttlMatch := ttlLine.FindStringSubmatch(line)
return strconv.Atoi(ttlMatch[2])
}
func checkRoundTripTimeStats(line string) (roundTripTimeStats, error) {
roundTripTimeStats := roundTripTimeStats{
min: -1.0,
avg: -1.0,
max: -1.0,
stddev: -1.0,
}
stats := strings.Split(line, " ")[3]
data := strings.Split(stats, "/")
var err error
roundTripTimeStats.min, err = strconv.ParseFloat(data[0], 64)
if err != nil {
return roundTripTimeStats, err
}
roundTripTimeStats.avg, err = strconv.ParseFloat(data[1], 64)
if err != nil {
return roundTripTimeStats, err
}
roundTripTimeStats.max, err = strconv.ParseFloat(data[2], 64)
if err != nil {
return roundTripTimeStats, err
}
if len(data) == 4 {
roundTripTimeStats.stddev, err = strconv.ParseFloat(data[3], 64)
if err != nil {
return roundTripTimeStats, err
}
}
return roundTripTimeStats, err
}
// Due to different behavior in version of freebsd, get the major
// version number. In the event of an error we assume we return a low number
// to avoid changing behavior.
func freeBSDMajorVersion() int {
out, err := exec.Command("freebsd-version", "-u").Output()
if err != nil {
return -1
}
majorVersionStr := strings.Split(string(out), ".")[0]
majorVersion, err := strconv.Atoi(majorVersionStr)
if err != nil {
return -1
}
return majorVersion
}

View file

@ -0,0 +1,531 @@
//go:build !windows
package ping
import (
"errors"
"sort"
"testing"
"time"
ping "github.com/prometheus-community/pro-bing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/plugins/inputs"
"github.com/influxdata/telegraf/testutil"
)
// BSD/Darwin ping output
var bsdPingOutput = `
PING www.google.com (216.58.217.36): 56 data bytes
64 bytes from 216.58.217.36: icmp_seq=0 ttl=55 time=15.087 ms
64 bytes from 216.58.217.36: icmp_seq=1 ttl=55 time=21.564 ms
64 bytes from 216.58.217.36: icmp_seq=2 ttl=55 time=27.263 ms
64 bytes from 216.58.217.36: icmp_seq=3 ttl=55 time=18.828 ms
64 bytes from 216.58.217.36: icmp_seq=4 ttl=55 time=18.378 ms
--- www.google.com ping statistics ---
5 packets transmitted, 5 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 15.087/20.224/27.263/4.076 ms
`
// FreeBSD ping6 output
var freebsdPing6Output = `
PING6(64=40+8+16 bytes) 2001:db8::1 --> 2a00:1450:4001:824::2004
24 bytes from 2a00:1450:4001:824::2004, icmp_seq=0 hlim=117 time=93.870 ms
24 bytes from 2a00:1450:4001:824::2004, icmp_seq=1 hlim=117 time=40.278 ms
24 bytes from 2a00:1450:4001:824::2004, icmp_seq=2 hlim=120 time=59.077 ms
24 bytes from 2a00:1450:4001:824::2004, icmp_seq=3 hlim=117 time=37.102 ms
24 bytes from 2a00:1450:4001:824::2004, icmp_seq=4 hlim=117 time=35.727 ms
--- www.google.com ping6 statistics ---
5 packets transmitted, 5 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 35.727/53.211/93.870/22.000 ms
`
// Linux ping output
var linuxPingOutput = `
PING www.google.com (216.58.218.164) 56(84) bytes of data.
64 bytes from host.net (216.58.218.164): icmp_seq=1 ttl=63 time=35.2 ms
64 bytes from host.net (216.58.218.164): icmp_seq=2 ttl=63 time=42.3 ms
64 bytes from host.net (216.58.218.164): icmp_seq=3 ttl=63 time=45.1 ms
64 bytes from host.net (216.58.218.164): icmp_seq=4 ttl=63 time=43.5 ms
64 bytes from host.net (216.58.218.164): icmp_seq=5 ttl=63 time=51.8 ms
--- www.google.com ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4010ms
rtt min/avg/max/mdev = 35.225/43.628/51.806/5.325 ms
`
// BusyBox v1.24.1 (2017-02-28 03:28:13 CET) multi-call binary
var busyBoxPingOutput = `
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=56 time=22.559 ms
64 bytes from 8.8.8.8: seq=1 ttl=56 time=15.810 ms
64 bytes from 8.8.8.8: seq=2 ttl=56 time=16.262 ms
64 bytes from 8.8.8.8: seq=3 ttl=56 time=15.815 ms
--- 8.8.8.8 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 15.810/17.611/22.559 ms
`
// Fatal ping output (invalid argument)
var fatalPingOutput = `
ping: -i interval too short: Operation not permitted
`
// Test that ping command output is processed properly
func TestProcessPingOutput(t *testing.T) {
stats, err := processPingOutput(bsdPingOutput)
require.NoError(t, err)
require.Equal(t, 55, stats.ttl, "ttl value is 55")
require.Equal(t, 5, stats.packetsTransmitted, "5 packets were transmitted")
require.Equal(t, 5, stats.packetsReceived, "5 packets were received")
require.InDelta(t, 15.087, stats.min, testutil.DefaultDelta)
require.InDelta(t, 20.224, stats.avg, testutil.DefaultDelta)
require.InDelta(t, 27.263, stats.max, testutil.DefaultDelta)
require.InDelta(t, 4.076, stats.stddev, testutil.DefaultDelta)
stats, err = processPingOutput(freebsdPing6Output)
require.NoError(t, err)
require.Equal(t, 117, stats.ttl, "ttl value is 117")
require.Equal(t, 5, stats.packetsTransmitted, "5 packets were transmitted")
require.Equal(t, 5, stats.packetsReceived, "5 packets were received")
require.InDelta(t, 35.727, stats.min, testutil.DefaultDelta)
require.InDelta(t, 53.211, stats.avg, testutil.DefaultDelta)
require.InDelta(t, 93.870, stats.max, testutil.DefaultDelta)
require.InDelta(t, 22.000, stats.stddev, testutil.DefaultDelta)
stats, err = processPingOutput(linuxPingOutput)
require.NoError(t, err)
require.Equal(t, 63, stats.ttl, "ttl value is 63")
require.Equal(t, 5, stats.packetsTransmitted, "5 packets were transmitted")
require.Equal(t, 5, stats.packetsReceived, "5 packets were received")
require.InDelta(t, 35.225, stats.min, testutil.DefaultDelta)
require.InDelta(t, 43.628, stats.avg, testutil.DefaultDelta)
require.InDelta(t, 51.806, stats.max, testutil.DefaultDelta)
require.InDelta(t, 5.325, stats.stddev, testutil.DefaultDelta)
stats, err = processPingOutput(busyBoxPingOutput)
require.NoError(t, err)
require.Equal(t, 56, stats.ttl, "ttl value is 56")
require.Equal(t, 4, stats.packetsTransmitted, "4 packets were transmitted")
require.Equal(t, 4, stats.packetsReceived, "4 packets were received")
require.InDelta(t, 15.810, stats.min, testutil.DefaultDelta)
require.InDelta(t, 17.611, stats.avg, testutil.DefaultDelta)
require.InDelta(t, 22.559, stats.max, testutil.DefaultDelta)
require.InDelta(t, -1.0, stats.stddev, testutil.DefaultDelta)
}
// Linux ping output with varying TTL
var linuxPingOutputWithVaryingTTL = `
PING www.google.com (216.58.218.164) 56(84) bytes of data.
64 bytes from host.net (216.58.218.164): icmp_seq=1 ttl=63 time=35.2 ms
64 bytes from host.net (216.58.218.164): icmp_seq=2 ttl=255 time=42.3 ms
64 bytes from host.net (216.58.218.164): icmp_seq=3 ttl=64 time=45.1 ms
64 bytes from host.net (216.58.218.164): icmp_seq=4 ttl=64 time=43.5 ms
64 bytes from host.net (216.58.218.164): icmp_seq=5 ttl=255 time=51.8 ms
--- www.google.com ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4010ms
rtt min/avg/max/mdev = 35.225/43.628/51.806/5.325 ms
`
// Test that ping command output is processed properly
func TestProcessPingOutputWithVaryingTTL(t *testing.T) {
stats, err := processPingOutput(linuxPingOutputWithVaryingTTL)
require.NoError(t, err)
require.Equal(t, 63, stats.ttl, "ttl value is 63")
require.Equal(t, 5, stats.packetsTransmitted, "5 packets were transmitted")
require.Equal(t, 5, stats.packetsReceived, "5 packets were transmitted")
require.InDelta(t, 35.225, stats.min, testutil.DefaultDelta)
require.InDelta(t, 43.628, stats.avg, testutil.DefaultDelta)
require.InDelta(t, 51.806, stats.max, testutil.DefaultDelta)
require.InDelta(t, 5.325, stats.stddev, testutil.DefaultDelta)
}
// Test that processPingOutput returns an error when 'ping' fails to run, such
// as when an invalid argument is provided
func TestErrorProcessPingOutput(t *testing.T) {
_, err := processPingOutput(fatalPingOutput)
require.Error(t, err, "Error was expected from processPingOutput")
}
// Test that default arg lists are created correctly
func TestArgs(t *testing.T) {
p := Ping{
Count: 2,
Interface: "eth0",
Timeout: 12.0,
Deadline: 24,
PingInterval: 1.2,
}
var systemCases = []struct {
system string
output []string
}{
{"darwin", []string{"-c", "2", "-n", "-s", "16", "-i", "1.2", "-W", "12000", "-t", "24", "-I", "eth0", "www.google.com"}},
{"linux", []string{"-c", "2", "-n", "-s", "16", "-i", "1.2", "-W", "12", "-w", "24", "-I", "eth0", "www.google.com"}},
{"anything else", []string{"-c", "2", "-n", "-s", "16", "-i", "1.2", "-W", "12", "-w", "24", "-i", "eth0", "www.google.com"}},
}
for i := range systemCases {
actual := p.args("www.google.com", systemCases[i].system)
expected := systemCases[i].output
sort.Strings(actual)
sort.Strings(expected)
require.Equal(t, expected, actual)
}
}
// Test that default arg lists for ping6 are created correctly
func TestArgs6(t *testing.T) {
p := Ping{
Count: 2,
Interface: "eth0",
Timeout: 12.0,
Deadline: 24,
PingInterval: 1.2,
Binary: "ping6",
}
var systemCases = []struct {
system string
output []string
}{
{"freebsd", []string{"-c", "2", "-n", "-s", "16", "-i", "1.2", "-x", "12000", "-X", "24", "-S", "eth0", "www.google.com"}},
{"linux", []string{"-c", "2", "-n", "-s", "16", "-i", "1.2", "-W", "12", "-w", "24", "-I", "eth0", "www.google.com"}},
{"anything else", []string{"-c", "2", "-n", "-s", "16", "-i", "1.2", "-W", "12", "-w", "24", "-i", "eth0", "www.google.com"}},
}
for i := range systemCases {
actual := p.args("www.google.com", systemCases[i].system)
expected := systemCases[i].output
sort.Strings(actual)
sort.Strings(expected)
require.Equal(t, expected, actual)
}
}
func TestArguments(t *testing.T) {
arguments := []string{"-c", "3"}
expected := append(arguments, "www.google.com")
p := Ping{
Count: 2,
Interface: "eth0",
Timeout: 12.0,
Deadline: 24,
PingInterval: 1.2,
Arguments: arguments,
}
for _, system := range []string{"darwin", "linux", "anything else"} {
actual := p.args("www.google.com", system)
require.Equal(t, expected, actual)
}
}
func mockHostPinger(string, float64, ...string) (string, error) {
return linuxPingOutput, nil
}
// Test that Gather function works on a normal ping
func TestPingGather(t *testing.T) {
var acc testutil.Accumulator
p := Ping{
Urls: []string{"localhost", "influxdata.com"},
pingHost: mockHostPinger,
}
require.NoError(t, acc.GatherError(p.Gather))
tags := map[string]string{"url": "localhost"}
fields := map[string]interface{}{
"packets_transmitted": 5,
"packets_received": 5,
"percent_packet_loss": 0.0,
"ttl": 63,
"minimum_response_ms": 35.225,
"average_response_ms": 43.628,
"maximum_response_ms": 51.806,
"standard_deviation_ms": 5.325,
"result_code": 0,
}
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
tags = map[string]string{"url": "influxdata.com"}
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
}
func TestPingGatherIntegration(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode, retrieves systems ping utility")
}
var acc testutil.Accumulator
p, ok := inputs.Inputs["ping"]().(*Ping)
p.Log = testutil.Logger{}
require.True(t, ok)
p.Urls = []string{"localhost", "influxdata.com"}
require.NoError(t, acc.GatherError(p.Gather))
require.Equal(t, 0, acc.Metrics[0].Fields["result_code"])
require.Equal(t, 0, acc.Metrics[1].Fields["result_code"])
}
var lossyPingOutput = `
PING www.google.com (216.58.218.164) 56(84) bytes of data.
64 bytes from host.net (216.58.218.164): icmp_seq=1 ttl=63 time=35.2 ms
64 bytes from host.net (216.58.218.164): icmp_seq=3 ttl=63 time=45.1 ms
64 bytes from host.net (216.58.218.164): icmp_seq=5 ttl=63 time=51.8 ms
--- www.google.com ping statistics ---
5 packets transmitted, 3 received, 40% packet loss, time 4010ms
rtt min/avg/max/mdev = 35.225/44.033/51.806/5.325 ms
`
func mockLossyHostPinger(string, float64, ...string) (string, error) {
return lossyPingOutput, nil
}
// Test that Gather works on a ping with lossy packets
func TestLossyPingGather(t *testing.T) {
var acc testutil.Accumulator
p := Ping{
Urls: []string{"www.google.com"},
pingHost: mockLossyHostPinger,
}
require.NoError(t, acc.GatherError(p.Gather))
tags := map[string]string{"url": "www.google.com"}
fields := map[string]interface{}{
"packets_transmitted": 5,
"packets_received": 3,
"percent_packet_loss": 40.0,
"ttl": 63,
"minimum_response_ms": 35.225,
"average_response_ms": 44.033,
"maximum_response_ms": 51.806,
"standard_deviation_ms": 5.325,
"result_code": 0,
}
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
}
var errorPingOutput = `
PING www.amazon.com (176.32.98.166): 56 data bytes
Request timeout for icmp_seq 0
--- www.amazon.com ping statistics ---
2 packets transmitted, 0 packets received, 100.0% packet loss
`
func mockErrorHostPinger(string, float64, ...string) (string, error) {
// This error will not trigger correct error paths
return errorPingOutput, nil
}
// Test that Gather works on a ping with no transmitted packets, even though the
// command returns an error
func TestBadPingGather(t *testing.T) {
var acc testutil.Accumulator
p := Ping{
Urls: []string{"www.amazon.com"},
pingHost: mockErrorHostPinger,
}
require.NoError(t, acc.GatherError(p.Gather))
tags := map[string]string{"url": "www.amazon.com"}
fields := map[string]interface{}{
"packets_transmitted": 2,
"packets_received": 0,
"percent_packet_loss": 100.0,
"result_code": 0,
}
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
}
func mockFatalHostPinger(string, float64, ...string) (string, error) {
return fatalPingOutput, errors.New("so very bad")
}
// Test that a fatal ping command does not gather any statistics.
func TestFatalPingGather(t *testing.T) {
var acc testutil.Accumulator
p := Ping{
Urls: []string{"www.amazon.com"},
pingHost: mockFatalHostPinger,
}
err := acc.GatherError(p.Gather)
require.Error(t, err)
require.EqualValues(t, "host \"www.amazon.com\": so very bad - ping: -i interval too short: Operation not permitted", err.Error())
require.False(t, acc.HasMeasurement("packets_transmitted"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasMeasurement("packets_received"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasMeasurement("percent_packet_loss"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasMeasurement("ttl"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasMeasurement("minimum_response_ms"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasMeasurement("average_response_ms"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasMeasurement("maximum_response_ms"),
"Fatal ping should not have packet measurements")
}
func TestErrorWithHostNamePingGather(t *testing.T) {
params := []struct {
out string
error error
}{
{"", errors.New("host \"www.amazon.com\": so very bad")},
{"so bad", errors.New("host \"www.amazon.com\": so very bad")},
}
for _, param := range params {
var acc testutil.Accumulator
p := Ping{
Urls: []string{"www.amazon.com"},
pingHost: func(string, float64, ...string) (string, error) {
return param.out, errors.New("so very bad")
},
}
require.Error(t, acc.GatherError(p.Gather))
require.Len(t, acc.Errors, 1)
require.Contains(t, acc.Errors[0].Error(), param.error.Error())
}
}
func TestPingBinary(t *testing.T) {
var acc testutil.Accumulator
p := Ping{
Urls: []string{"www.google.com"},
Binary: "ping6",
pingHost: func(binary string, _ float64, _ ...string) (string, error) {
require.Equal(t, "ping6", binary)
return "", nil
},
}
err := acc.GatherError(p.Gather)
require.Error(t, err)
require.EqualValues(t, "\"www.google.com\": fatal error processing ping output", err.Error())
}
// Test that Gather function works using native ping
func TestPingGatherNative(t *testing.T) {
type test struct {
P *Ping
}
fakePingFunc := func(string) (*pingStats, error) {
s := &pingStats{
Statistics: ping.Statistics{
PacketsSent: 5,
PacketsRecv: 5,
Rtts: []time.Duration{
3 * time.Millisecond,
4 * time.Millisecond,
1 * time.Millisecond,
5 * time.Millisecond,
2 * 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
require.NoError(t, tc.P.Init())
require.NoError(t, acc.GatherError(tc.P.Gather))
require.True(t, acc.HasPoint("ping", map[string]string{"url": "localhost"}, "packets_transmitted", 5))
require.True(t, acc.HasPoint("ping", map[string]string{"url": "localhost"}, "packets_received", 5))
require.True(t, acc.HasField("ping", "percentile50_ms"))
require.InDelta(t, float64(3), acc.Metrics[0].Fields["percentile50_ms"], testutil.DefaultDelta)
require.True(t, acc.HasField("ping", "percentile95_ms"))
require.InDelta(t, float64(4.799999), acc.Metrics[0].Fields["percentile95_ms"], testutil.DefaultDelta)
require.True(t, acc.HasField("ping", "percentile99_ms"))
require.InDelta(t, float64(4.96), acc.Metrics[0].Fields["percentile99_ms"], testutil.DefaultDelta)
require.True(t, acc.HasField("ping", "percent_packet_loss"))
require.True(t, acc.HasField("ping", "minimum_response_ms"))
require.True(t, acc.HasField("ping", "average_response_ms"))
require.True(t, acc.HasField("ping", "maximum_response_ms"))
require.True(t, acc.HasField("ping", "standard_deviation_ms"))
}
}
func TestNoPacketsSent(t *testing.T) {
p := &Ping{
Log: testutil.Logger{},
Urls: []string{"localhost", "127.0.0.2"},
Method: "native",
Count: 5,
Percentiles: []int{50, 95, 99},
nativePingFunc: func(string) (*pingStats, error) {
s := &pingStats{
Statistics: ping.Statistics{
PacketsSent: 0,
PacketsRecv: 0,
},
}
return s, nil
},
}
var testAcc testutil.Accumulator
require.NoError(t, p.Init())
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) {
p := &Ping{
Count: 1,
Log: testutil.Logger{},
Urls: []string{"localhost"},
Method: "native",
IPv6: false,
nativePingFunc: func(string) (*pingStats, error) {
return nil, errors.New("unknown")
},
}
var testAcc testutil.Accumulator
require.NoError(t, p.Init())
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"])
}

View file

@ -0,0 +1,182 @@
//go:build windows
package ping
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/influxdata/telegraf"
)
type roundTripTimeStats struct {
min int
avg int
max int
}
type statistics struct {
packetsTransmitted int
replyReceived int
packetsReceived int
roundTripTimeStats
}
func (p *Ping) pingToURL(host string, acc telegraf.Accumulator) {
tags := map[string]string{"url": host}
fields := map[string]interface{}{"result_code": 0}
args := p.args(host)
totalTimeout := 60.0
if len(p.Arguments) == 0 {
totalTimeout = p.timeout() * float64(p.Count)
}
out, err := p.pingHost(p.Binary, totalTimeout, args...)
// ping host return exitcode != 0 also when there was no response from host but command was executed successfully
var pendingError error
if err != nil {
// Combine go err + stderr output
pendingError = errors.New(strings.TrimSpace(out) + ", " + err.Error())
}
stats, err := processPingOutput(out)
if err != nil {
// fatal error
if pendingError != nil {
acc.AddError(fmt.Errorf("%s: %w", host, pendingError))
} else {
acc.AddError(fmt.Errorf("%s: %w", host, err))
}
fields["result_code"] = 2
fields["errors"] = 100.0
acc.AddFields("ping", fields, tags)
return
}
// Calculate packet loss percentage
lossReply := float64(stats.packetsTransmitted-stats.replyReceived) / float64(stats.packetsTransmitted) * 100.0
lossPackets := float64(stats.packetsTransmitted-stats.packetsReceived) / float64(stats.packetsTransmitted) * 100.0
fields["packets_transmitted"] = stats.packetsTransmitted
fields["reply_received"] = stats.replyReceived
fields["packets_received"] = stats.packetsReceived
fields["percent_packet_loss"] = lossPackets
fields["percent_reply_loss"] = lossReply
if stats.avg >= 0 {
fields["average_response_ms"] = float64(stats.avg)
}
if stats.min >= 0 {
fields["minimum_response_ms"] = float64(stats.min)
}
if stats.max >= 0 {
fields["maximum_response_ms"] = float64(stats.max)
}
acc.AddFields("ping", fields, tags)
}
// args returns the arguments for the 'ping' executable
func (p *Ping) args(url string) []string {
if len(p.Arguments) > 0 {
return p.Arguments
}
args := []string{"-n", strconv.Itoa(p.Count)}
if p.Timeout > 0 {
args = append(args, "-w", strconv.FormatFloat(p.Timeout*1000, 'f', 0, 64))
}
args = append(args, url)
return args
}
// processPingOutput takes in a string output from the ping command
// based on linux implementation but using regex (multi-language support)
// It returns (<transmitted packets>, <received reply>, <received packet>, <average response>, <min response>, <max response>)
func processPingOutput(out string) (statistics, error) {
// So find a line contain 3 numbers except reply lines
var statsLine, approxs []string = nil, nil
err := errors.New("fatal error processing ping output")
stat := regexp.MustCompile(`=\W*(\d+)\D*=\W*(\d+)\D*=\W*(\d+)`)
approx := regexp.MustCompile(`=\W*(\d+)\D*ms\D*=\W*(\d+)\D*ms\D*=\W*(\d+)\D*ms`)
tttLine := regexp.MustCompile(`TTL=\d+`)
lines := strings.Split(out, "\n")
var replyReceived = 0
for _, line := range lines {
if tttLine.MatchString(line) {
replyReceived++
} else {
if statsLine == nil {
statsLine = stat.FindStringSubmatch(line)
}
if statsLine != nil && approxs == nil {
approxs = approx.FindStringSubmatch(line)
}
}
}
stats := statistics{
packetsTransmitted: 0,
replyReceived: 0,
packetsReceived: 0,
roundTripTimeStats: roundTripTimeStats{
min: -1,
avg: -1,
max: -1,
},
}
// statsLine data should contain 4 members: entireExpression + ( Send, Receive, Lost )
if len(statsLine) != 4 {
return stats, err
}
packetsTransmitted, err := strconv.Atoi(statsLine[1])
if err != nil {
return stats, err
}
packetsReceived, err := strconv.Atoi(statsLine[2])
if err != nil {
return stats, err
}
stats.packetsTransmitted = packetsTransmitted
stats.replyReceived = replyReceived
stats.packetsReceived = packetsReceived
// approxs data should contain 4 members: entireExpression + ( min, max, avg )
if len(approxs) != 4 {
return stats, err
}
low, err := strconv.Atoi(approxs[1])
if err != nil {
return stats, err
}
high, err := strconv.Atoi(approxs[2])
if err != nil {
return stats, err
}
avg, err := strconv.Atoi(approxs[3])
if err != nil {
return statistics{}, err
}
stats.avg = avg
stats.min = low
stats.max = high
return stats, err
}
func (p *Ping) timeout() float64 {
// According to MSDN, default ping timeout for windows is 4 second
// Add also one second interval
if p.Timeout > 0 {
return p.Timeout + 1
}
return 4 + 1
}

View file

@ -0,0 +1,378 @@
//go:build windows
package ping
import (
"errors"
"testing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/testutil"
)
// Windows ping format ( should support multilanguage ?)
var winPLPingOutput = `
Badanie 8.8.8.8 z 32 bajtami danych:
Odpowiedz z 8.8.8.8: bajtow=32 czas=49ms TTL=43
Odpowiedz z 8.8.8.8: bajtow=32 czas=46ms TTL=43
Odpowiedz z 8.8.8.8: bajtow=32 czas=48ms TTL=43
Odpowiedz z 8.8.8.8: bajtow=32 czas=57ms TTL=43
Statystyka badania ping dla 8.8.8.8:
Pakiety: Wyslane = 4, Odebrane = 4, Utracone = 0
(0% straty),
Szacunkowy czas bladzenia pakietww w millisekundach:
Minimum = 46 ms, Maksimum = 57 ms, Czas sredni = 50 ms
`
// Windows ping format ( should support multilanguage ?)
var winENPingOutput = `
Pinging 8.8.8.8 with 32 bytes of data:
Reply from 8.8.8.8: bytes=32 time=52ms TTL=43
Reply from 8.8.8.8: bytes=32 time=50ms TTL=43
Reply from 8.8.8.8: bytes=32 time=50ms TTL=43
Reply from 8.8.8.8: bytes=32 time=51ms TTL=43
Ping statistics for 8.8.8.8:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 50ms, Maximum = 52ms, Average = 50ms
`
func TestHost(t *testing.T) {
stats, err := processPingOutput(winPLPingOutput)
require.NoError(t, err)
require.Equal(t, 4, stats.packetsTransmitted, "4 packets were transmitted")
require.Equal(t, 4, stats.replyReceived, "4 packets were reply")
require.Equal(t, 4, stats.packetsReceived, "4 packets were received")
require.Equal(t, 50, stats.avg, "Average 50")
require.Equal(t, 46, stats.min, "Min 46")
require.Equal(t, 57, stats.max, "max 57")
stats, err = processPingOutput(winENPingOutput)
require.NoError(t, err)
require.Equal(t, 4, stats.packetsTransmitted, "4 packets were transmitted")
require.Equal(t, 4, stats.replyReceived, "4 packets were reply")
require.Equal(t, 4, stats.packetsReceived, "4 packets were received")
require.Equal(t, 50, stats.avg, "Average 50")
require.Equal(t, 50, stats.min, "Min 50")
require.Equal(t, 52, stats.max, "Max 52")
}
func mockHostPinger(string, float64, ...string) (string, error) {
return winENPingOutput, nil
}
// Test that Gather function works on a normal ping
func TestPingGather(t *testing.T) {
var acc testutil.Accumulator
p := Ping{
Urls: []string{"www.google.com", "www.reddit.com"},
pingHost: mockHostPinger,
}
require.NoError(t, acc.GatherError(p.Gather))
tags := map[string]string{"url": "www.google.com"}
fields := map[string]interface{}{
"packets_transmitted": 4,
"packets_received": 4,
"reply_received": 4,
"percent_packet_loss": 0.0,
"percent_reply_loss": 0.0,
"average_response_ms": 50.0,
"minimum_response_ms": 50.0,
"maximum_response_ms": 52.0,
"result_code": 0,
}
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
tags = map[string]string{"url": "www.reddit.com"}
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
}
var errorPingOutput = `
Badanie nask.pl [195.187.242.157] z 32 bajtami danych:
Upłynął limit czasu żądania.
Upłynął limit czasu żądania.
Upłynął limit czasu żądania.
Upłynął limit czasu żądania.
Statystyka badania ping dla 195.187.242.157:
Pakiety: Wysłane = 4, Odebrane = 0, Utracone = 4
(100% straty),
`
func mockErrorHostPinger(string, float64, ...string) (string, error) {
return errorPingOutput, errors.New("no packets received")
}
// Test that Gather works on a ping with no transmitted packets, even though the
// command returns an error
func TestBadPingGather(t *testing.T) {
var acc testutil.Accumulator
p := Ping{
Log: testutil.Logger{},
Urls: []string{"www.amazon.com"},
pingHost: mockErrorHostPinger,
}
err := acc.GatherError(p.Gather)
require.NoError(t, err)
tags := map[string]string{"url": "www.amazon.com"}
fields := map[string]interface{}{
"packets_transmitted": 4,
"packets_received": 0,
"reply_received": 0,
"percent_packet_loss": 100.0,
"percent_reply_loss": 100.0,
"result_code": 0,
}
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
}
func TestArguments(t *testing.T) {
arguments := []string{"-c", "3"}
p := Ping{
Log: testutil.Logger{},
Count: 2,
Timeout: 12.0,
Arguments: arguments,
}
actual := p.args("www.google.com")
require.Equal(t, actual, arguments)
}
var lossyPingOutput = `
Badanie thecodinglove.com [66.6.44.4] z 9800 bajtami danych:
Upłynął limit czasu żądania.
Odpowiedź z 66.6.44.4: bajtów=9800 czas=114ms TTL=48
Odpowiedź z 66.6.44.4: bajtów=9800 czas=114ms TTL=48
Odpowiedź z 66.6.44.4: bajtów=9800 czas=118ms TTL=48
Odpowiedź z 66.6.44.4: bajtów=9800 czas=114ms TTL=48
Odpowiedź z 66.6.44.4: bajtów=9800 czas=114ms TTL=48
Upłynął limit czasu żądania.
Odpowiedź z 66.6.44.4: bajtów=9800 czas=119ms TTL=48
Odpowiedź z 66.6.44.4: bajtów=9800 czas=116ms TTL=48
Statystyka badania ping dla 66.6.44.4:
Pakiety: Wysłane = 9, Odebrane = 7, Utracone = 2
(22% straty),
Szacunkowy czas błądzenia pakietów w millisekundach:
Minimum = 114 ms, Maksimum = 119 ms, Czas średni = 115 ms
`
func mockLossyHostPinger(string, float64, ...string) (string, error) {
return lossyPingOutput, nil
}
// Test that Gather works on a ping with lossy packets
func TestLossyPingGather(t *testing.T) {
var acc testutil.Accumulator
p := Ping{
Log: testutil.Logger{},
Urls: []string{"www.google.com"},
pingHost: mockLossyHostPinger,
}
err := acc.GatherError(p.Gather)
require.NoError(t, err)
tags := map[string]string{"url": "www.google.com"}
fields := map[string]interface{}{
"packets_transmitted": 9,
"packets_received": 7,
"reply_received": 7,
"percent_packet_loss": 22.22222222222222,
"percent_reply_loss": 22.22222222222222,
"average_response_ms": 115.0,
"minimum_response_ms": 114.0,
"maximum_response_ms": 119.0,
"result_code": 0,
}
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
}
// Fatal ping output (invalid argument)
var fatalPingOutput = `
Bad option -d.
Usage: ping [-t] [-a] [-n count] [-l size] [-f] [-i TTL] [-v TOS]
[-r count] [-s count] [[-j host-list] | [-k host-list]]
[-w timeout] [-R] [-S srcaddr] [-4] [-6] target_name
Options:
-t Ping the specified host until stopped.
To see statistics and continue - type Control-Break;
To stop - type Control-C.
-a Resolve addresses to hostnames.
-n count Number of echo requests to send.
-l size Send buffer size.
-f Set Don't Fragment flag in packet (IPv4-only).
-i TTL Time To Live.
-v TOS Type Of Service (IPv4-only. This setting has been deprecated
and has no effect on the type of service field in the IP Header).
-r count Record route for count hops (IPv4-only).
-s count Timestamp for count hops (IPv4-only).
-j host-list Loose source route along host-list (IPv4-only).
-k host-list Strict source route along host-list (IPv4-only).
-w timeout Timeout in milliseconds to wait for each reply.
-R Use routing header to test reverse route also (IPv6-only).
-S srcaddr Source address to use.
-4 Force using IPv4.
-6 Force using IPv6.
`
func mockFatalHostPinger(string, float64, ...string) (string, error) {
return fatalPingOutput, errors.New("so very bad")
}
// Test that a fatal ping command does not gather any statistics.
func TestFatalPingGather(t *testing.T) {
var acc testutil.Accumulator
p := Ping{
Log: testutil.Logger{},
Urls: []string{"www.amazon.com"},
pingHost: mockFatalHostPinger,
}
err := acc.GatherError(p.Gather)
require.Error(t, err)
require.True(t, acc.HasFloatField("ping", "errors"),
"Fatal ping should have packet measurements")
require.False(t, acc.HasInt64Field("ping", "packets_transmitted"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasInt64Field("ping", "packets_received"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasFloatField("ping", "percent_packet_loss"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasFloatField("ping", "percent_reply_loss"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasInt64Field("ping", "average_response_ms"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasInt64Field("ping", "maximum_response_ms"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasInt64Field("ping", "minimum_response_ms"),
"Fatal ping should not have packet measurements")
}
var unreachablePingOutput = `
Pinging www.google.pl [8.8.8.8] with 32 bytes of data:
Request timed out.
Request timed out.
Reply from 194.204.175.50: Destination net unreachable.
Request timed out.
Ping statistics for 8.8.8.8:
Packets: Sent = 4, Received = 1, Lost = 3 (75% loss),
`
func mockUnreachableHostPinger(string, float64, ...string) (string, error) {
return unreachablePingOutput, errors.New("so very bad")
}
// Reply from 185.28.251.217: TTL expired in transit.
// in case 'Destination net unreachable' ping app return receive packet which is not what we need
// it's not contain valid metric so treat it as lost one
func TestUnreachablePingGather(t *testing.T) {
var acc testutil.Accumulator
p := Ping{
Log: testutil.Logger{},
Urls: []string{"www.google.com"},
pingHost: mockUnreachableHostPinger,
}
err := acc.GatherError(p.Gather)
require.NoError(t, err)
tags := map[string]string{"url": "www.google.com"}
fields := map[string]interface{}{
"packets_transmitted": 4,
"packets_received": 1,
"reply_received": 0,
"percent_packet_loss": 75.0,
"percent_reply_loss": 100.0,
"result_code": 0,
}
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
require.False(t, acc.HasFloatField("ping", "errors"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasInt64Field("ping", "average_response_ms"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasInt64Field("ping", "maximum_response_ms"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasInt64Field("ping", "minimum_response_ms"),
"Fatal ping should not have packet measurements")
}
var ttlExpiredPingOutput = `
Pinging www.google.pl [8.8.8.8] with 32 bytes of data:
Request timed out.
Request timed out.
Reply from 185.28.251.217: TTL expired in transit.
Request timed out.
Ping statistics for 8.8.8.8:
Packets: Sent = 4, Received = 1, Lost = 3 (75% loss),
`
func mockTTLExpiredPinger(string, float64, ...string) (string, error) {
return ttlExpiredPingOutput, errors.New("so very bad")
}
// in case 'Destination net unreachable' ping app return receive packet which is not what we need
// it's not contain valid metric so treat it as lost one
func TestTTLExpiredPingGather(t *testing.T) {
var acc testutil.Accumulator
p := Ping{
Log: testutil.Logger{},
Urls: []string{"www.google.com"},
pingHost: mockTTLExpiredPinger,
}
err := acc.GatherError(p.Gather)
require.NoError(t, err)
tags := map[string]string{"url": "www.google.com"}
fields := map[string]interface{}{
"packets_transmitted": 4,
"packets_received": 1,
"reply_received": 0,
"percent_packet_loss": 75.0,
"percent_reply_loss": 100.0,
"result_code": 0,
}
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
require.False(t, acc.HasFloatField("ping", "errors"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasInt64Field("ping", "average_response_ms"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasInt64Field("ping", "maximum_response_ms"),
"Fatal ping should not have packet measurements")
require.False(t, acc.HasInt64Field("ping", "minimum_response_ms"),
"Fatal ping should not have packet measurements")
}
func TestPingBinary(t *testing.T) {
var acc testutil.Accumulator
p := Ping{
Log: testutil.Logger{},
Urls: []string{"www.google.com"},
Binary: "ping6",
pingHost: func(binary string, _ float64, _ ...string) (string, error) {
require.Equal(t, "ping6", binary)
return "", nil
},
}
err := acc.GatherError(p.Gather)
require.Error(t, err)
require.EqualValues(t, "www.google.com: fatal error processing ping output", err.Error())
}

View file

@ -0,0 +1,56 @@
# Ping given url(s) and return statistics
[[inputs.ping]]
## Hosts to send ping packets to.
urls = ["example.org"]
## 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.
# count = 1
## Time to wait between sending ping packets in seconds. Operates like the
## "-i" option of the ping command.
# ping_interval = 1.0
## If set, the time to wait for a ping response in seconds. Operates like
## the "-W" option of the ping command.
# timeout = 1.0
## If set, the total ping deadline, in seconds. Operates like the -w option
## of the ping command.
# deadline = 10
## Interface or source address to send ping from. Operates like the -I or -S
## option of the ping command.
# interface = ""
## Percentiles to calculate. This only works with the native method.
# percentiles = [50, 95, 99]
## Specify the ping executable binary.
# binary = "ping"
## 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.
# arguments = ["-c", "3"]
## Use only IPv4 addresses when resolving a hostname. By default, both IPv4
## and IPv6 can be used.
# ipv4 = false
## Use only IPv6 addresses when resolving a hostname. By default, both IPv4
## and IPv6 can be used.
# ipv6 = false
## Number of data bytes to be sent. Corresponds to the "-s"
## option of the ping command. This only works with the native method.
# size = 56