diff --git a/plugins/inputs/ping/README.md b/plugins/inputs/ping/README.md index 1083c0074..f59a6c947 100644 --- a/plugins/inputs/ping/README.md +++ b/plugins/inputs/ping/README.md @@ -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) @@ -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 ``` diff --git a/plugins/inputs/ping/ping.go b/plugins/inputs/ping/ping.go index 69db140ae..28e967a85 100644 --- a/plugins/inputs/ping/ping.go +++ b/plugins/inputs/ping/ping.go @@ -7,6 +7,7 @@ import ( "fmt" "net" "os/exec" + "regexp" "runtime" "strconv" "strings" @@ -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)) @@ -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 } @@ -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 (, , ) -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]) + trans, recv, err = getPacketStats(line, trans, recv) if err != nil { - return trans, recv, min, avg, max, stddev, err - } - // Received packets - recv, err = strconv.Atoi(strings.Split(stats[1], " ")[0]) - 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) + min, avg, max, stddev, err = checkRoundTripTimeStats(line, min, avg, max, stddev) 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) - 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() { diff --git a/plugins/inputs/ping/ping_test.go b/plugins/inputs/ping/ping_test.go index ad6fa306a..8870d4156 100644 --- a/plugins/inputs/ping/ping_test.go +++ b/plugins/inputs/ping/ping_test.go @@ -61,8 +61,9 @@ 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) @@ -70,8 +71,9 @@ func TestProcessPingOutput(t *testing.T) { 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) @@ -79,8 +81,9 @@ func TestProcessPingOutput(t *testing.T) { 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) @@ -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") } @@ -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, @@ -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, @@ -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"),