Add percentiles to the ping plugin (#7345)
This commit is contained in:
parent
752a4b77e1
commit
97de3981bd
|
|
@ -57,6 +57,9 @@ native Go by the Telegraf process, eliminating the need to execute the system
|
||||||
## option of the ping command.
|
## option of the ping command.
|
||||||
# interface = ""
|
# interface = ""
|
||||||
|
|
||||||
|
## Percentiles to calculate. This only works with the native method.
|
||||||
|
# percentiles = [50, 95, 99]
|
||||||
|
|
||||||
## Specify the ping executable binary.
|
## Specify the ping executable binary.
|
||||||
# binary = "ping"
|
# binary = "ping"
|
||||||
|
|
||||||
|
|
@ -147,10 +150,11 @@ sockets and the `ping_group_range` setting.
|
||||||
- packets_received (integer)
|
- packets_received (integer)
|
||||||
- percent_packet_loss (float)
|
- percent_packet_loss (float)
|
||||||
- ttl (integer, Not available on Windows)
|
- ttl (integer, Not available on Windows)
|
||||||
- average_response_ms (integer)
|
- average_response_ms (float)
|
||||||
- minimum_response_ms (integer)
|
- minimum_response_ms (float)
|
||||||
- maximum_response_ms (integer)
|
- maximum_response_ms (float)
|
||||||
- standard_deviation_ms (integer, Available on Windows only with native ping)
|
- 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)
|
- errors (float, Windows only)
|
||||||
- reply_received (integer, Windows with method = "exec" only)
|
- reply_received (integer, Windows with method = "exec" only)
|
||||||
- percent_reply_loss (float, Windows with method = "exec" only)
|
- percent_reply_loss (float, Windows with method = "exec" only)
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@ package ping
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -69,6 +71,9 @@ type Ping struct {
|
||||||
|
|
||||||
// listenAddr is the address associated with the interface defined.
|
// listenAddr is the address associated with the interface defined.
|
||||||
listenAddr string
|
listenAddr string
|
||||||
|
|
||||||
|
// Calculate the given percentiles when using native method
|
||||||
|
Percentiles []int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Ping) Description() string {
|
func (*Ping) Description() string {
|
||||||
|
|
@ -108,6 +113,9 @@ const sampleConfig = `
|
||||||
## option of the ping command.
|
## option of the ping command.
|
||||||
# interface = ""
|
# interface = ""
|
||||||
|
|
||||||
|
## Percentiles to calculate. This only works with the native method.
|
||||||
|
# percentiles = [50, 95, 99]
|
||||||
|
|
||||||
## Specify the ping executable binary.
|
## Specify the ping executable binary.
|
||||||
# binary = "ping"
|
# binary = "ping"
|
||||||
|
|
||||||
|
|
@ -345,11 +353,41 @@ finish:
|
||||||
log.Printf("D! [inputs.ping] %s", doErr.Error())
|
log.Printf("D! [inputs.ping] %s", doErr.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
tags, fields := onFin(packetsSent, rsps, doErr, destination)
|
tags, fields := onFin(packetsSent, rsps, doErr, destination, p.Percentiles)
|
||||||
acc.AddFields("ping", fields, tags)
|
acc.AddFields("ping", fields, tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func onFin(packetsSent int, resps []*ping.Response, err error, destination string) (map[string]string, map[string]interface{}) {
|
type durationSlice []time.Duration
|
||||||
|
|
||||||
|
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 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]
|
||||||
|
} else {
|
||||||
|
upper := values[rankInteger+1]
|
||||||
|
lower := values[rankInteger]
|
||||||
|
return lower + time.Duration(rankFraction*float64(upper-lower))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func onFin(packetsSent int, resps []*ping.Response, err error, destination string, percentiles []int) (map[string]string, map[string]interface{}) {
|
||||||
packetsRcvd := len(resps)
|
packetsRcvd := len(resps)
|
||||||
|
|
||||||
tags := map[string]string{"url": destination}
|
tags := map[string]string{"url": destination}
|
||||||
|
|
@ -378,17 +416,35 @@ func onFin(packetsSent int, resps []*ping.Response, err error, destination strin
|
||||||
ttl := resps[0].TTL
|
ttl := resps[0].TTL
|
||||||
|
|
||||||
var min, max, avg, total time.Duration
|
var min, max, avg, total time.Duration
|
||||||
min = resps[0].RTT
|
|
||||||
max = resps[0].RTT
|
|
||||||
|
|
||||||
for _, res := range resps {
|
if len(percentiles) > 0 {
|
||||||
if res.RTT < min {
|
var rtt []time.Duration
|
||||||
min = res.RTT
|
for _, resp := range resps {
|
||||||
|
rtt = append(rtt, resp.RTT)
|
||||||
|
total += resp.RTT
|
||||||
}
|
}
|
||||||
if res.RTT > max {
|
sort.Sort(durationSlice(rtt))
|
||||||
max = res.RTT
|
min = rtt[0]
|
||||||
|
max = rtt[len(rtt)-1]
|
||||||
|
|
||||||
|
for _, perc := range percentiles {
|
||||||
|
var value = percentile(durationSlice(rtt), perc)
|
||||||
|
var field = fmt.Sprintf("percentile%v_ms", perc)
|
||||||
|
fields[field] = float64(value.Nanoseconds()) / float64(time.Millisecond)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
min = resps[0].RTT
|
||||||
|
max = resps[0].RTT
|
||||||
|
|
||||||
|
for _, res := range resps {
|
||||||
|
if res.RTT < min {
|
||||||
|
min = res.RTT
|
||||||
|
}
|
||||||
|
if res.RTT > max {
|
||||||
|
max = res.RTT
|
||||||
|
}
|
||||||
|
total += res.RTT
|
||||||
}
|
}
|
||||||
total += res.RTT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
avg = total / time.Duration(packetsRcvd)
|
avg = total / time.Duration(packetsRcvd)
|
||||||
|
|
@ -433,6 +489,7 @@ func init() {
|
||||||
Method: "exec",
|
Method: "exec",
|
||||||
Binary: "ping",
|
Binary: "ping",
|
||||||
Arguments: []string{},
|
Arguments: []string{},
|
||||||
|
Percentiles: []int{},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -413,11 +413,15 @@ func TestPingGatherNative(t *testing.T) {
|
||||||
Method: "native",
|
Method: "native",
|
||||||
Count: 5,
|
Count: 5,
|
||||||
resolveHost: mockHostResolver,
|
resolveHost: mockHostResolver,
|
||||||
|
Percentiles: []int{50, 95, 99},
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.NoError(t, acc.GatherError(p.Gather))
|
assert.NoError(t, acc.GatherError(p.Gather))
|
||||||
assert.True(t, acc.HasPoint("ping", map[string]string{"url": "localhost"}, "packets_transmitted", 5))
|
assert.True(t, acc.HasPoint("ping", map[string]string{"url": "localhost"}, "packets_transmitted", 5))
|
||||||
assert.True(t, acc.HasPoint("ping", map[string]string{"url": "localhost"}, "packets_received", 5))
|
assert.True(t, acc.HasPoint("ping", map[string]string{"url": "localhost"}, "packets_received", 5))
|
||||||
|
assert.True(t, acc.HasField("ping", "percentile50_ms"))
|
||||||
|
assert.True(t, acc.HasField("ping", "percentile95_ms"))
|
||||||
|
assert.True(t, acc.HasField("ping", "percentile99_ms"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func mockHostResolverError(ctx context.Context, ipv6 bool, host string) (*net.IPAddr, error) {
|
func mockHostResolverError(ctx context.Context, ipv6 bool, host string) (*net.IPAddr, error) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue