Skip to content

Commit

Permalink
Add TTL field to ping input (#5556)
Browse files Browse the repository at this point in the history
  • Loading branch information
dheerajdwivedi authored and danielnelson committed Mar 8, 2019
1 parent 7da57fe commit 8015502
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 37 deletions.
3 changes: 2 additions & 1 deletion plugins/inputs/ping/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ LimitNOFILE=4096
- packets_transmitted (integer)
- packets_received (integer)
- percent_packets_loss (float)
- ttl (integer, Not available on Windows)
- average_response_ms (integer)
- minimum_response_ms (integer)
- maximum_response_ms (integer)
Expand All @@ -92,5 +93,5 @@ ping,url=example.org result_code=0i,average_response_ms=7i,maximum_response_ms=9

**Linux:**
```
ping,url=example.org average_response_ms=23.066,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
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
```
92 changes: 60 additions & 32 deletions plugins/inputs/ping/ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"net"
"os/exec"
"regexp"
"runtime"
"strconv"
"strings"
Expand Down Expand Up @@ -151,7 +152,7 @@ func (p *Ping) pingToURL(u string, acc telegraf.Accumulator) {
}
}

trans, rec, min, avg, max, stddev, err := processPingOutput(out)
trans, rec, ttl, min, avg, max, stddev, err := processPingOutput(out)
if err != nil {
// fatal error
acc.AddError(fmt.Errorf("%s: %s", err, u))
Expand All @@ -164,6 +165,9 @@ func (p *Ping) pingToURL(u string, acc telegraf.Accumulator) {
fields["packets_transmitted"] = trans
fields["packets_received"] = rec
fields["percent_packet_loss"] = loss
if ttl >= 0 {
fields["ttl"] = ttl
}
if min >= 0 {
fields["minimum_response_ms"] = min
}
Expand Down Expand Up @@ -253,50 +257,74 @@ func (p *Ping) args(url string, system string) []string {
// 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) (int, int, float64, float64, float64, float64, error) {
var trans, recv int
func processPingOutput(out string) (int, int, int, float64, float64, float64, float64, error) {
var trans, recv, ttl int = 0, 0, -1
var min, avg, max, stddev float64 = -1.0, -1.0, -1.0, -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 {
if strings.Contains(line, "transmitted") &&
// Reading only first TTL, ignoring other TTL messages
if ttl == -1 && strings.Contains(line, "ttl=") {
ttl, err = getTTL(line)
} else if strings.Contains(line, "transmitted") &&
strings.Contains(line, "received") {
stats := strings.Split(line, ", ")
// Transmitted packets
trans, err = strconv.Atoi(strings.Split(stats[0], " ")[0])
if err != nil {
return trans, recv, min, avg, max, stddev, err
}
// Received packets
recv, err = strconv.Atoi(strings.Split(stats[1], " ")[0])
trans, recv, err = getPacketStats(line, trans, recv)
if err != nil {
return trans, recv, min, avg, max, stddev, err
return trans, recv, ttl, min, avg, max, stddev, err
}
} else if strings.Contains(line, "min/avg/max") {
stats := strings.Split(line, " ")[3]
data := strings.Split(stats, "/")
min, err = strconv.ParseFloat(data[0], 64)
if err != nil {
return trans, recv, min, avg, max, stddev, err
}
avg, err = strconv.ParseFloat(data[1], 64)
if err != nil {
return trans, recv, min, avg, max, stddev, err
}
max, err = strconv.ParseFloat(data[2], 64)
min, avg, max, stddev, err = checkRoundTripTimeStats(line, min, avg, max, stddev)
if err != nil {
return trans, recv, min, avg, max, stddev, err
}
if len(data) == 4 {
stddev, err = strconv.ParseFloat(data[3], 64)
if err != nil {
return trans, recv, min, avg, max, stddev, err
}
return trans, recv, ttl, min, avg, max, stddev, err
}
}
}
return trans, recv, min, avg, max, stddev, err
return trans, recv, ttl, min, avg, max, stddev, err
}

func getPacketStats(line string, trans, recv int) (int, int, error) {
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=(\d+)`)
ttlMatch := ttlLine.FindStringSubmatch(line)
return strconv.Atoi(ttlMatch[1])
}

func checkRoundTripTimeStats(line string, min, avg, max,
stddev float64) (float64, float64, float64, float64, error) {
stats := strings.Split(line, " ")[3]
data := strings.Split(stats, "/")

min, err := strconv.ParseFloat(data[0], 64)
if err != nil {
return min, avg, max, stddev, err
}
avg, err = strconv.ParseFloat(data[1], 64)
if err != nil {
return min, avg, max, stddev, err
}
max, err = strconv.ParseFloat(data[2], 64)
if err != nil {
return min, avg, max, stddev, err
}
if len(data) == 4 {
stddev, err = strconv.ParseFloat(data[3], 64)
if err != nil {
return min, avg, max, stddev, err
}
}
return min, avg, max, stddev, err
}

func init() {
Expand Down
42 changes: 38 additions & 4 deletions plugins/inputs/ping/ping_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,26 +61,29 @@ ping: -i interval too short: Operation not permitted

// Test that ping command output is processed properly
func TestProcessPingOutput(t *testing.T) {
trans, rec, min, avg, max, stddev, err := processPingOutput(bsdPingOutput)
trans, rec, ttl, min, avg, max, stddev, err := processPingOutput(bsdPingOutput)
assert.NoError(t, err)
assert.Equal(t, 55, ttl, "ttl value is 55")
assert.Equal(t, 5, trans, "5 packets were transmitted")
assert.Equal(t, 5, rec, "5 packets were transmitted")
assert.InDelta(t, 15.087, min, 0.001)
assert.InDelta(t, 20.224, avg, 0.001)
assert.InDelta(t, 27.263, max, 0.001)
assert.InDelta(t, 4.076, stddev, 0.001)

trans, rec, min, avg, max, stddev, err = processPingOutput(linuxPingOutput)
trans, rec, ttl, min, avg, max, stddev, err = processPingOutput(linuxPingOutput)
assert.NoError(t, err)
assert.Equal(t, 63, ttl, "ttl value is 63")
assert.Equal(t, 5, trans, "5 packets were transmitted")
assert.Equal(t, 5, rec, "5 packets were transmitted")
assert.InDelta(t, 35.225, min, 0.001)
assert.InDelta(t, 43.628, avg, 0.001)
assert.InDelta(t, 51.806, max, 0.001)
assert.InDelta(t, 5.325, stddev, 0.001)

trans, rec, min, avg, max, stddev, err = processPingOutput(busyBoxPingOutput)
trans, rec, ttl, min, avg, max, stddev, err = processPingOutput(busyBoxPingOutput)
assert.NoError(t, err)
assert.Equal(t, 56, ttl, "ttl value is 56")
assert.Equal(t, 4, trans, "4 packets were transmitted")
assert.Equal(t, 4, rec, "4 packets were transmitted")
assert.InDelta(t, 15.810, min, 0.001)
Expand All @@ -89,10 +92,37 @@ func TestProcessPingOutput(t *testing.T) {
assert.InDelta(t, -1.0, stddev, 0.001)
}

// 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) {
trans, rec, ttl, min, avg, max, stddev, err := processPingOutput(linuxPingOutputWithVaryingTTL)
assert.NoError(t, err)
assert.Equal(t, 63, ttl, "ttl value is 63")
assert.Equal(t, 5, trans, "5 packets were transmitted")
assert.Equal(t, 5, rec, "5 packets were transmitted")
assert.InDelta(t, 35.225, min, 0.001)
assert.InDelta(t, 43.628, avg, 0.001)
assert.InDelta(t, 51.806, max, 0.001)
assert.InDelta(t, 5.325, stddev, 0.001)
}

// 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)
_, _, _, _, _, _, _, err := processPingOutput(fatalPingOutput)
assert.Error(t, err, "Error was expected from processPingOutput")
}

Expand Down Expand Up @@ -160,6 +190,7 @@ func TestPingGather(t *testing.T) {
"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,
Expand Down Expand Up @@ -201,6 +232,7 @@ func TestLossyPingGather(t *testing.T) {
"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,
Expand Down Expand Up @@ -262,6 +294,8 @@ func TestFatalPingGather(t *testing.T) {
"Fatal ping should not have packet measurements")
assert.False(t, acc.HasMeasurement("percent_packet_loss"),
"Fatal ping should not have packet measurements")
assert.False(t, acc.HasMeasurement("ttl"),
"Fatal ping should not have packet measurements")
assert.False(t, acc.HasMeasurement("minimum_response_ms"),
"Fatal ping should not have packet measurements")
assert.False(t, acc.HasMeasurement("average_response_ms"),
Expand Down

0 comments on commit 8015502

Please sign in to comment.