Skip to content

Commit

Permalink
Fix influxdata#7344: Add percentiles to the ping plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
Frank Riley committed Apr 16, 2020
1 parent 3380471 commit 7e9bfaf
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 10 deletions.
77 changes: 67 additions & 10 deletions plugins/inputs/ping/ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package ping
import (
"context"
"errors"
"fmt"
"log"
"math"
"net"
"os/exec"
"runtime"
"sort"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -69,6 +71,9 @@ type Ping struct {

// listenAddr is the address associated with the interface defined.
listenAddr string

// Calculate the given percentiles when using native method
Percentiles []int
}

func (*Ping) Description() string {
Expand Down Expand Up @@ -108,6 +113,9 @@ const sampleConfig = `
## 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"
Expand Down Expand Up @@ -345,11 +353,41 @@ finish:
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)
}

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)

tags := map[string]string{"url": destination}
Expand Down Expand Up @@ -378,17 +416,35 @@ func onFin(packetsSent int, resps []*ping.Response, err error, destination strin
ttl := resps[0].TTL

var min, max, avg, total time.Duration
min = resps[0].RTT
max = resps[0].RTT

for _, res := range resps {
if res.RTT < min {
min = res.RTT
if len(percentiles) > 0 {
var rtt []time.Duration
for _, resp := range resps {
rtt = append(rtt, resp.RTT)
total += resp.RTT
}
sort.Sort(durationSlice(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)
}
if res.RTT > max {
max = res.RTT
} 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)
Expand Down Expand Up @@ -433,6 +489,7 @@ func init() {
Method: "exec",
Binary: "ping",
Arguments: []string{},
Percentiles: []int{},
}
})
}
7 changes: 7 additions & 0 deletions plugins/inputs/ping/ping_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package ping
import (
"context"
"errors"
"fmt"
"net"
"reflect"
"sort"
Expand Down Expand Up @@ -360,11 +361,17 @@ func TestPingGatherNative(t *testing.T) {
Method: "native",
Count: 5,
resolveHost: mockHostResolver,
Percentiles: []int{50, 95, 99},
}

assert.NoError(t, acc.GatherError(p.Gather))
value, ok := acc.FloatField("ping", "percentile50_ms")
fmt.Printf("%v %v\n", value, ok)
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.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) {
Expand Down

0 comments on commit 7e9bfaf

Please sign in to comment.