Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
199
plugins/inputs/ping/README.md
Normal file
199
plugins/inputs/ping/README.md
Normal 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
313
plugins/inputs/ping/ping.go
Normal 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
|
||||
})
|
||||
}
|
286
plugins/inputs/ping/ping_notwindows.go
Normal file
286
plugins/inputs/ping/ping_notwindows.go
Normal 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
|
||||
}
|
531
plugins/inputs/ping/ping_test.go
Normal file
531
plugins/inputs/ping/ping_test.go
Normal 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"])
|
||||
}
|
182
plugins/inputs/ping/ping_windows.go
Normal file
182
plugins/inputs/ping/ping_windows.go
Normal 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
|
||||
}
|
378
plugins/inputs/ping/ping_windows_test.go
Normal file
378
plugins/inputs/ping/ping_windows_test.go
Normal 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())
|
||||
}
|
56
plugins/inputs/ping/sample.conf
Normal file
56
plugins/inputs/ping/sample.conf
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue