Fix problem with metrics when ping return Destination net unreachable ( windows ) (#1561)
* Fix problem with metrics when ping return Destination net unreachable Add test case TestUnreachablePingGather Add percent_reply_loss Fix some other tests * Add errors measurment * fir problem with ping reply "TTL expired in transit" ( use regex for more specific condition - TTL in line but it's a not valid replay ) add test case for "TTL expired in transit" - TestTTLExpiredPingGather
This commit is contained in:
parent
53e31cf1b5
commit
3853d0d065
|
@ -0,0 +1,36 @@
|
||||||
|
# Ping input plugin
|
||||||
|
|
||||||
|
This input plugin will measures the round-trip
|
||||||
|
|
||||||
|
## Windows:
|
||||||
|
### Configration:
|
||||||
|
```
|
||||||
|
## urls to ping
|
||||||
|
urls = ["www.google.com"] # required
|
||||||
|
|
||||||
|
## number of pings to send per collection (ping -n <COUNT>)
|
||||||
|
count = 4 # required
|
||||||
|
|
||||||
|
## Ping timeout, in seconds. 0 means default timeout (ping -w <TIMEOUT>)
|
||||||
|
Timeout = 0
|
||||||
|
```
|
||||||
|
### Measurements & Fields:
|
||||||
|
- packets_transmitted ( from ping output )
|
||||||
|
- reply_received ( increasing only on valid metric from echo replay, eg. 'Destination net unreachable' reply will increment packets_received but not reply_received )
|
||||||
|
- packets_received ( from ping output )
|
||||||
|
- percent_reply_loss ( compute from packets_transmitted and reply_received )
|
||||||
|
- percent_packets_loss ( compute from packets_transmitted and packets_received )
|
||||||
|
- errors ( when host can not be found or wrong prameters is passed to application )
|
||||||
|
- response time
|
||||||
|
- average_response_ms ( compute from minimum_response_ms and maximum_response_ms )
|
||||||
|
- minimum_response_ms ( from ping output )
|
||||||
|
- maximum_response_ms ( from ping output )
|
||||||
|
|
||||||
|
### Tags:
|
||||||
|
- server
|
||||||
|
|
||||||
|
### Example Output:
|
||||||
|
```
|
||||||
|
* Plugin: ping, Collection 1
|
||||||
|
ping,host=WIN-PBAPLP511R7,url=www.google.com average_response_ms=7i,maximum_response_ms=9i,minimum_response_ms=7i,packets_received=4i,packets_transmitted=4i,percent_packet_loss=0,percent_reply_loss=0,reply_received=4i 1469879119000000000
|
||||||
|
```
|
|
@ -65,16 +65,20 @@ func hostPinger(timeout float64, args ...string) (string, error) {
|
||||||
|
|
||||||
// processPingOutput takes in a string output from the ping command
|
// processPingOutput takes in a string output from the ping command
|
||||||
// based on linux implementation but using regex ( multilanguage support ) ( shouldn't affect the performance of the program )
|
// based on linux implementation but using regex ( multilanguage support ) ( shouldn't affect the performance of the program )
|
||||||
// It returns (<transmitted packets>, <received packets>, <average response>, <min response>, <max response>)
|
// It returns (<transmitted packets>, <received reply>, <received packet>, <average response>, <min response>, <max response>)
|
||||||
func processPingOutput(out string) (int, int, int, int, int, error) {
|
func processPingOutput(out string) (int, int, int, int, int, int, error) {
|
||||||
// So find a line contain 3 numbers except reply lines
|
// So find a line contain 3 numbers except reply lines
|
||||||
var stats, aproxs []string = nil, nil
|
var stats, aproxs []string = nil, nil
|
||||||
err := errors.New("Fatal error processing ping output")
|
err := errors.New("Fatal error processing ping output")
|
||||||
stat := regexp.MustCompile(`=\W*(\d+)\D*=\W*(\d+)\D*=\W*(\d+)`)
|
stat := regexp.MustCompile(`=\W*(\d+)\D*=\W*(\d+)\D*=\W*(\d+)`)
|
||||||
aprox := regexp.MustCompile(`=\W*(\d+)\D*ms\D*=\W*(\d+)\D*ms\D*=\W*(\d+)\D*ms`)
|
aprox := 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")
|
lines := strings.Split(out, "\n")
|
||||||
|
var receivedReply int = 0
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
if !strings.Contains(line, "TTL") {
|
if tttLine.MatchString(line) {
|
||||||
|
receivedReply++
|
||||||
|
} else {
|
||||||
if stats == nil {
|
if stats == nil {
|
||||||
stats = stat.FindStringSubmatch(line)
|
stats = stat.FindStringSubmatch(line)
|
||||||
}
|
}
|
||||||
|
@ -86,35 +90,35 @@ func processPingOutput(out string) (int, int, int, int, int, error) {
|
||||||
|
|
||||||
// stats data should contain 4 members: entireExpression + ( Send, Receive, Lost )
|
// stats data should contain 4 members: entireExpression + ( Send, Receive, Lost )
|
||||||
if len(stats) != 4 {
|
if len(stats) != 4 {
|
||||||
return 0, 0, 0, 0, 0, err
|
return 0, 0, 0, 0, 0, 0, err
|
||||||
}
|
}
|
||||||
trans, err := strconv.Atoi(stats[1])
|
trans, err := strconv.Atoi(stats[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, 0, 0, err
|
return 0, 0, 0, 0, 0, 0, err
|
||||||
}
|
}
|
||||||
rec, err := strconv.Atoi(stats[2])
|
receivedPacket, err := strconv.Atoi(stats[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, 0, 0, err
|
return 0, 0, 0, 0, 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// aproxs data should contain 4 members: entireExpression + ( min, max, avg )
|
// aproxs data should contain 4 members: entireExpression + ( min, max, avg )
|
||||||
if len(aproxs) != 4 {
|
if len(aproxs) != 4 {
|
||||||
return trans, rec, 0, 0, 0, err
|
return trans, receivedReply, receivedPacket, 0, 0, 0, err
|
||||||
}
|
}
|
||||||
min, err := strconv.Atoi(aproxs[1])
|
min, err := strconv.Atoi(aproxs[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return trans, rec, 0, 0, 0, err
|
return trans, receivedReply, receivedPacket, 0, 0, 0, err
|
||||||
}
|
}
|
||||||
max, err := strconv.Atoi(aproxs[2])
|
max, err := strconv.Atoi(aproxs[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return trans, rec, 0, 0, 0, err
|
return trans, receivedReply, receivedPacket, 0, 0, 0, err
|
||||||
}
|
}
|
||||||
avg, err := strconv.Atoi(aproxs[3])
|
avg, err := strconv.Atoi(aproxs[3])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, 0, 0, err
|
return 0, 0, 0, 0, 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return trans, rec, avg, min, max, err
|
return trans, receivedReply, receivedPacket, avg, min, max, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Ping) timeout() float64 {
|
func (p *Ping) timeout() float64 {
|
||||||
|
@ -159,21 +163,30 @@ func (p *Ping) Gather(acc telegraf.Accumulator) error {
|
||||||
pendingError = errors.New(strings.TrimSpace(out) + ", " + err.Error())
|
pendingError = errors.New(strings.TrimSpace(out) + ", " + err.Error())
|
||||||
}
|
}
|
||||||
tags := map[string]string{"url": u}
|
tags := map[string]string{"url": u}
|
||||||
trans, rec, avg, min, max, err := processPingOutput(out)
|
trans, recReply, receivePacket, avg, min, max, err := processPingOutput(out)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// fatal error
|
// fatal error
|
||||||
if pendingError != nil {
|
if pendingError != nil {
|
||||||
errorChannel <- pendingError
|
errorChannel <- pendingError
|
||||||
}
|
}
|
||||||
errorChannel <- err
|
errorChannel <- err
|
||||||
|
fields := map[string]interface{}{
|
||||||
|
"errors": 100.0,
|
||||||
|
}
|
||||||
|
|
||||||
|
acc.AddFields("ping", fields, tags)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Calculate packet loss percentage
|
// Calculate packet loss percentage
|
||||||
loss := float64(trans-rec) / float64(trans) * 100.0
|
lossReply := float64(trans-recReply) / float64(trans) * 100.0
|
||||||
|
lossPackets := float64(trans-receivePacket) / float64(trans) * 100.0
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"packets_transmitted": trans,
|
"packets_transmitted": trans,
|
||||||
"packets_received": rec,
|
"reply_received": recReply,
|
||||||
"percent_packet_loss": loss,
|
"packets_received": receivePacket,
|
||||||
|
"percent_packet_loss": lossPackets,
|
||||||
|
"percent_reply_loss": lossReply,
|
||||||
}
|
}
|
||||||
if avg > 0 {
|
if avg > 0 {
|
||||||
fields["average_response_ms"] = avg
|
fields["average_response_ms"] = avg
|
||||||
|
|
|
@ -38,18 +38,20 @@ Approximate round trip times in milli-seconds:
|
||||||
`
|
`
|
||||||
|
|
||||||
func TestHost(t *testing.T) {
|
func TestHost(t *testing.T) {
|
||||||
trans, rec, avg, min, max, err := processPingOutput(winPLPingOutput)
|
trans, recReply, recPacket, avg, min, max, err := processPingOutput(winPLPingOutput)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, 4, trans, "4 packets were transmitted")
|
assert.Equal(t, 4, trans, "4 packets were transmitted")
|
||||||
assert.Equal(t, 4, rec, "4 packets were received")
|
assert.Equal(t, 4, recReply, "4 packets were reply")
|
||||||
|
assert.Equal(t, 4, recPacket, "4 packets were received")
|
||||||
assert.Equal(t, 50, avg, "Average 50")
|
assert.Equal(t, 50, avg, "Average 50")
|
||||||
assert.Equal(t, 46, min, "Min 46")
|
assert.Equal(t, 46, min, "Min 46")
|
||||||
assert.Equal(t, 57, max, "max 57")
|
assert.Equal(t, 57, max, "max 57")
|
||||||
|
|
||||||
trans, rec, avg, min, max, err = processPingOutput(winENPingOutput)
|
trans, recReply, recPacket, avg, min, max, err = processPingOutput(winENPingOutput)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, 4, trans, "4 packets were transmitted")
|
assert.Equal(t, 4, trans, "4 packets were transmitted")
|
||||||
assert.Equal(t, 4, rec, "4 packets were received")
|
assert.Equal(t, 4, recReply, "4 packets were reply")
|
||||||
|
assert.Equal(t, 4, recPacket, "4 packets were received")
|
||||||
assert.Equal(t, 50, avg, "Average 50")
|
assert.Equal(t, 50, avg, "Average 50")
|
||||||
assert.Equal(t, 50, min, "Min 50")
|
assert.Equal(t, 50, min, "Min 50")
|
||||||
assert.Equal(t, 52, max, "Max 52")
|
assert.Equal(t, 52, max, "Max 52")
|
||||||
|
@ -72,7 +74,9 @@ func TestPingGather(t *testing.T) {
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"packets_transmitted": 4,
|
"packets_transmitted": 4,
|
||||||
"packets_received": 4,
|
"packets_received": 4,
|
||||||
|
"reply_received": 4,
|
||||||
"percent_packet_loss": 0.0,
|
"percent_packet_loss": 0.0,
|
||||||
|
"percent_reply_loss": 0.0,
|
||||||
"average_response_ms": 50,
|
"average_response_ms": 50,
|
||||||
"minimum_response_ms": 50,
|
"minimum_response_ms": 50,
|
||||||
"maximum_response_ms": 52,
|
"maximum_response_ms": 52,
|
||||||
|
@ -113,7 +117,9 @@ func TestBadPingGather(t *testing.T) {
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"packets_transmitted": 4,
|
"packets_transmitted": 4,
|
||||||
"packets_received": 0,
|
"packets_received": 0,
|
||||||
|
"reply_received": 0,
|
||||||
"percent_packet_loss": 100.0,
|
"percent_packet_loss": 100.0,
|
||||||
|
"percent_reply_loss": 100.0,
|
||||||
}
|
}
|
||||||
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
||||||
}
|
}
|
||||||
|
@ -154,7 +160,9 @@ func TestLossyPingGather(t *testing.T) {
|
||||||
fields := map[string]interface{}{
|
fields := map[string]interface{}{
|
||||||
"packets_transmitted": 9,
|
"packets_transmitted": 9,
|
||||||
"packets_received": 7,
|
"packets_received": 7,
|
||||||
|
"reply_received": 7,
|
||||||
"percent_packet_loss": 22.22222222222222,
|
"percent_packet_loss": 22.22222222222222,
|
||||||
|
"percent_reply_loss": 22.22222222222222,
|
||||||
"average_response_ms": 115,
|
"average_response_ms": 115,
|
||||||
"minimum_response_ms": 114,
|
"minimum_response_ms": 114,
|
||||||
"maximum_response_ms": 119,
|
"maximum_response_ms": 119,
|
||||||
|
@ -207,12 +215,114 @@ func TestFatalPingGather(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Gather(&acc)
|
p.Gather(&acc)
|
||||||
assert.False(t, acc.HasMeasurement("packets_transmitted"),
|
assert.True(t, acc.HasFloatField("ping", "errors"),
|
||||||
|
"Fatal ping should have packet measurements")
|
||||||
|
assert.False(t, acc.HasIntField("ping", "packets_transmitted"),
|
||||||
"Fatal ping should not have packet measurements")
|
"Fatal ping should not have packet measurements")
|
||||||
assert.False(t, acc.HasMeasurement("packets_received"),
|
assert.False(t, acc.HasIntField("ping", "packets_received"),
|
||||||
"Fatal ping should not have packet measurements")
|
"Fatal ping should not have packet measurements")
|
||||||
assert.False(t, acc.HasMeasurement("percent_packet_loss"),
|
assert.False(t, acc.HasFloatField("ping", "percent_packet_loss"),
|
||||||
"Fatal ping should not have packet measurements")
|
"Fatal ping should not have packet measurements")
|
||||||
assert.False(t, acc.HasMeasurement("average_response_ms"),
|
assert.False(t, acc.HasFloatField("ping", "percent_reply_loss"),
|
||||||
|
"Fatal ping should not have packet measurements")
|
||||||
|
assert.False(t, acc.HasIntField("ping", "average_response_ms"),
|
||||||
|
"Fatal ping should not have packet measurements")
|
||||||
|
assert.False(t, acc.HasIntField("ping", "maximum_response_ms"),
|
||||||
|
"Fatal ping should not have packet measurements")
|
||||||
|
assert.False(t, acc.HasIntField("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(timeout float64, args ...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{
|
||||||
|
Urls: []string{"www.google.com"},
|
||||||
|
pingHost: mockUnreachableHostPinger,
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Gather(&acc)
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
||||||
|
|
||||||
|
assert.False(t, acc.HasFloatField("ping", "errors"),
|
||||||
|
"Fatal ping should not have packet measurements")
|
||||||
|
assert.False(t, acc.HasIntField("ping", "average_response_ms"),
|
||||||
|
"Fatal ping should not have packet measurements")
|
||||||
|
assert.False(t, acc.HasIntField("ping", "maximum_response_ms"),
|
||||||
|
"Fatal ping should not have packet measurements")
|
||||||
|
assert.False(t, acc.HasIntField("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(timeout float64, args ...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{
|
||||||
|
Urls: []string{"www.google.com"},
|
||||||
|
pingHost: mockTTLExpiredPinger,
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Gather(&acc)
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
acc.AssertContainsTaggedFields(t, "ping", fields, tags)
|
||||||
|
|
||||||
|
assert.False(t, acc.HasFloatField("ping", "errors"),
|
||||||
|
"Fatal ping should not have packet measurements")
|
||||||
|
assert.False(t, acc.HasIntField("ping", "average_response_ms"),
|
||||||
|
"Fatal ping should not have packet measurements")
|
||||||
|
assert.False(t, acc.HasIntField("ping", "maximum_response_ms"),
|
||||||
|
"Fatal ping should not have packet measurements")
|
||||||
|
assert.False(t, acc.HasIntField("ping", "minimum_response_ms"),
|
||||||
"Fatal ping should not have packet measurements")
|
"Fatal ping should not have packet measurements")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue