-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes #167
- Loading branch information
Showing
6 changed files
with
269 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
package ping | ||
|
||
import ( | ||
"errors" | ||
"os/exec" | ||
"strconv" | ||
"strings" | ||
"sync" | ||
|
||
"github.com/influxdb/telegraf/plugins" | ||
) | ||
|
||
type Ping struct { | ||
// Interval at which to ping (ping -i <INTERVAL>) | ||
PingInterval float64 `toml:"ping_interval"` | ||
|
||
// Number of pings to send (ping -c <COUNT>) | ||
Count int | ||
|
||
// Ping timeout, in seconds. 0 means no timeout (ping -t <TIMEOUT>) | ||
Timeout float64 | ||
|
||
// Interface to send ping from (ping -I <INTERFACE>) | ||
Interface string | ||
|
||
// URLs to ping | ||
Urls []string | ||
} | ||
|
||
func (_ *Ping) Description() string { | ||
return "Ping given url(s) and return statistics" | ||
} | ||
|
||
var sampleConfig = ` | ||
# urls to ping | ||
urls = ["www.google.com"] # required | ||
# number of pings to send (ping -c <COUNT>) | ||
count = 1 # required | ||
# interval, in s, at which to ping. 0 == default (ping -i <PING_INTERVAL>) | ||
ping_interval = 0.0 | ||
# ping timeout, in s. 0 == no timeout (ping -t <TIMEOUT>) | ||
timeout = 0.0 | ||
# interface to send ping from (ping -I <INTERFACE>) | ||
interface = "" | ||
` | ||
|
||
func (_ *Ping) SampleConfig() string { | ||
return sampleConfig | ||
} | ||
|
||
func (p *Ping) Gather(acc plugins.Accumulator) error { | ||
|
||
var wg sync.WaitGroup | ||
errorChannel := make(chan error, len(p.Urls)*2) | ||
|
||
// Spin off a go routine for each url to ping | ||
for _, url := range p.Urls { | ||
wg.Add(1) | ||
go func(url string, acc plugins.Accumulator) { | ||
defer wg.Done() | ||
args := p.args(url) | ||
c := exec.Command("ping", args...) | ||
out, err := c.CombinedOutput() | ||
if err != nil { | ||
// Combine go err + stderr output | ||
errorChannel <- errors.New( | ||
strings.TrimSpace(string(out)) + ", " + err.Error()) | ||
} | ||
tags := map[string]string{"url": url} | ||
trans, rec, avg, err := processPingOutput(string(out)) | ||
if err != nil { | ||
// fatal error | ||
errorChannel <- err | ||
return | ||
} | ||
// Calculate packet loss percentage | ||
loss := float64(trans-rec) / float64(trans) * 100.0 | ||
acc.Add("packets_transmitted", trans, tags) | ||
acc.Add("packets_received", rec, tags) | ||
acc.Add("percent_packet_loss", loss, tags) | ||
if avg > 0 { | ||
acc.Add("average_response_ms", avg, tags) | ||
} | ||
}(url, acc) | ||
} | ||
|
||
wg.Wait() | ||
close(errorChannel) | ||
|
||
// Get all errors and return them as one giant error | ||
errorStrings := []string{} | ||
for err := range errorChannel { | ||
errorStrings = append(errorStrings, err.Error()) | ||
} | ||
|
||
if len(errorStrings) == 0 { | ||
return nil | ||
} | ||
return errors.New(strings.Join(errorStrings, "\n")) | ||
} | ||
|
||
// args returns the arguments for the 'ping' executable | ||
func (p *Ping) args(url string) []string { | ||
// Build the ping command args based on toml config | ||
args := []string{"-c", strconv.Itoa(p.Count)} | ||
if p.PingInterval > 0 { | ||
args = append(args, "-i", strconv.FormatFloat(p.PingInterval, 'f', 1, 64)) | ||
} | ||
if p.Timeout > 0 { | ||
args = append(args, "-t", strconv.FormatFloat(p.Timeout, 'f', 1, 64)) | ||
} | ||
if p.Interface != "" { | ||
args = append(args, "-I", p.Interface) | ||
} | ||
args = append(args, url) | ||
return args | ||
} | ||
|
||
// processPingOutput takes in a string output from the ping command, like: | ||
// | ||
// PING www.google.com (173.194.115.84): 56 data bytes | ||
// 64 bytes from 173.194.115.84: icmp_seq=0 ttl=54 time=52.172 ms | ||
// 64 bytes from 173.194.115.84: icmp_seq=1 ttl=54 time=34.843 ms | ||
// | ||
// --- www.google.com ping statistics --- | ||
// 2 packets transmitted, 2 packets received, 0.0% packet loss | ||
// 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, error) { | ||
var trans, recv int | ||
var avg float64 | ||
// 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") && | ||
strings.Contains(line, "received") { | ||
err = nil | ||
stats := strings.Split(line, ", ") | ||
// Transmitted packets | ||
trans, err = strconv.Atoi(strings.Split(stats[0], " ")[0]) | ||
if err != nil { | ||
return trans, recv, avg, err | ||
} | ||
// Received packets | ||
recv, err = strconv.Atoi(strings.Split(stats[1], " ")[0]) | ||
if err != nil { | ||
return trans, recv, avg, err | ||
} | ||
} else if strings.Contains(line, "min/avg/max") { | ||
stats := strings.Split(line, " = ")[1] | ||
avg, err = strconv.ParseFloat(strings.Split(stats, "/")[1], 64) | ||
if err != nil { | ||
return trans, recv, avg, err | ||
} | ||
} | ||
} | ||
return trans, recv, avg, err | ||
} | ||
|
||
func init() { | ||
plugins.Add("ping", func() plugins.Plugin { | ||
return &Ping{} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package ping | ||
|
||
import ( | ||
"reflect" | ||
"sort" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
// BSD/Darwin ping output | ||
var bsdPingOutput = ` | ||
PING www.google.com (216.58.217.36): 56 data bytes | ||
64 bytes from 216.58.217.36: icmp_seq=0 ttl=55 time=15.087 ms | ||
64 bytes from 216.58.217.36: icmp_seq=1 ttl=55 time=21.564 ms | ||
64 bytes from 216.58.217.36: icmp_seq=2 ttl=55 time=27.263 ms | ||
64 bytes from 216.58.217.36: icmp_seq=3 ttl=55 time=18.828 ms | ||
64 bytes from 216.58.217.36: icmp_seq=4 ttl=55 time=18.378 ms | ||
--- www.google.com ping statistics --- | ||
5 packets transmitted, 5 packets received, 0.0% packet loss | ||
round-trip min/avg/max/stddev = 15.087/20.224/27.263/4.076 ms | ||
` | ||
|
||
// Linux ping output | ||
var linuxPingOutput = ` | ||
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=63 time=42.3 ms | ||
64 bytes from host.net (216.58.218.164): icmp_seq=3 ttl=63 time=45.1 ms | ||
64 bytes from host.net (216.58.218.164): icmp_seq=4 ttl=63 time=43.5 ms | ||
64 bytes from host.net (216.58.218.164): icmp_seq=5 ttl=63 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 TestProcessPingOutput(t *testing.T) { | ||
trans, rec, avg, err := processPingOutput(bsdPingOutput) | ||
assert.NoError(t, err) | ||
assert.Equal(t, 5, trans, "5 packets were transmitted") | ||
assert.Equal(t, 5, rec, "5 packets were transmitted") | ||
assert.InDelta(t, 20.224, avg, 0.001) | ||
|
||
trans, rec, avg, err = processPingOutput(linuxPingOutput) | ||
assert.NoError(t, err) | ||
assert.Equal(t, 5, trans, "5 packets were transmitted") | ||
assert.Equal(t, 5, rec, "5 packets were transmitted") | ||
assert.InDelta(t, 43.628, avg, 0.001) | ||
} | ||
|
||
// Test that arg lists and created correctly | ||
func TestArgs(t *testing.T) { | ||
p := Ping{ | ||
Count: 2, | ||
} | ||
|
||
// Actual and Expected arg lists must be sorted for reflect.DeepEqual | ||
|
||
actual := p.args("www.google.com") | ||
expected := []string{"-c", "2", "www.google.com"} | ||
sort.Strings(actual) | ||
sort.Strings(expected) | ||
assert.True(t, reflect.DeepEqual(expected, actual), | ||
"Expected: %s Actual: %s", expected, actual) | ||
|
||
p.Interface = "eth0" | ||
actual = p.args("www.google.com") | ||
expected = []string{"-c", "2", "-I", "eth0", "www.google.com"} | ||
sort.Strings(actual) | ||
sort.Strings(expected) | ||
assert.True(t, reflect.DeepEqual(expected, actual), | ||
"Expected: %s Actual: %s", expected, actual) | ||
|
||
p.Timeout = 12.0 | ||
actual = p.args("www.google.com") | ||
expected = []string{"-c", "2", "-I", "eth0", "-t", "12.0", "www.google.com"} | ||
sort.Strings(actual) | ||
sort.Strings(expected) | ||
assert.True(t, reflect.DeepEqual(expected, actual), | ||
"Expected: %s Actual: %s", expected, actual) | ||
|
||
p.PingInterval = 1.2 | ||
actual = p.args("www.google.com") | ||
expected = []string{"-c", "2", "-I", "eth0", "-t", "12.0", "-i", "1.2", | ||
"www.google.com"} | ||
sort.Strings(actual) | ||
sort.Strings(expected) | ||
assert.True(t, reflect.DeepEqual(expected, actual), | ||
"Expected: %s Actual: %s", expected, actual) | ||
} |