Skip to content

Commit 68e41f1

Browse files
committed
Ping plugin
Closes #167
1 parent 65b33a8 commit 68e41f1

File tree

7 files changed

+416
-7
lines changed

7 files changed

+416
-7
lines changed

CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
## v0.1.8 [unreleased]
22

33
### Release Notes
4-
Telegraf will now write data in UTC at second precision by default
4+
- Telegraf will now write data in UTC at second precision by default
5+
- Now using Go 1.5 to build telegraf
56

67
### Features
78
- [#150](https://github.com/influxdb/telegraf/pull/150): Add Host Uptime metric to system plugin
@@ -10,6 +11,7 @@ Telegraf will now write data in UTC at second precision by default
1011
- [#165](https://github.com/influxdb/telegraf/pull/165): Add additional metrics to mysql plugin. Thanks @nickscript0
1112
- [#162](https://github.com/influxdb/telegraf/pull/162): Write UTC by default, provide option
1213
- [#166](https://github.com/influxdb/telegraf/pull/166): Upload binaries to S3
14+
- [#169](https://github.com/influxdb/telegraf/pull/169): Ping plugin
1315

1416
### Bugfixes
1517

config.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -439,8 +439,8 @@ func PrintSampleConfig() {
439439
func PrintPluginConfig(name string) error {
440440
if creator, ok := plugins.Plugins[name]; ok {
441441
plugin := creator()
442-
fmt.Printf("# %s\n[%s]\n", plugin.Description(), name)
443-
fmt.Printf(strings.TrimSpace(plugin.SampleConfig()))
442+
fmt.Printf("# %s\n[%s]", plugin.Description(), name)
443+
fmt.Printf(plugin.SampleConfig())
444444
} else {
445445
return errors.New(fmt.Sprintf("Plugin %s not found", name))
446446
}

outputs/kafka/kafka.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ type Kafka struct {
1919
}
2020

2121
var sampleConfig = `
22-
# URLs of kafka brokers
23-
brokers = ["localhost:9092"]
24-
# Kafka topic for producer messages
25-
topic = "telegraf"
22+
# URLs of kafka brokers
23+
brokers = ["localhost:9092"]
24+
# Kafka topic for producer messages
25+
topic = "telegraf"
2626
`
2727

2828
func (k *Kafka) Connect() error {

plugins/all/all.go

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
_ "github.com/influxdb/telegraf/plugins/mongodb"
1515
_ "github.com/influxdb/telegraf/plugins/mysql"
1616
_ "github.com/influxdb/telegraf/plugins/nginx"
17+
_ "github.com/influxdb/telegraf/plugins/ping"
1718
_ "github.com/influxdb/telegraf/plugins/postgresql"
1819
_ "github.com/influxdb/telegraf/plugins/prometheus"
1920
_ "github.com/influxdb/telegraf/plugins/rabbitmq"

plugins/ping/ping.go

+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package ping
2+
3+
import (
4+
"errors"
5+
"os/exec"
6+
"strconv"
7+
"strings"
8+
"sync"
9+
10+
"github.com/influxdb/telegraf/plugins"
11+
)
12+
13+
// HostPinger is a function that runs the "ping" function using a list of
14+
// passed arguments. This can be easily switched with a mocked ping function
15+
// for unit test purposes (see ping_test.go)
16+
type HostPinger func(args ...string) (string, error)
17+
18+
type Ping struct {
19+
// Interval at which to ping (ping -i <INTERVAL>)
20+
PingInterval float64 `toml:"ping_interval"`
21+
22+
// Number of pings to send (ping -c <COUNT>)
23+
Count int
24+
25+
// Ping timeout, in seconds. 0 means no timeout (ping -t <TIMEOUT>)
26+
Timeout float64
27+
28+
// Interface to send ping from (ping -I <INTERFACE>)
29+
Interface string
30+
31+
// URLs to ping
32+
Urls []string
33+
34+
// host ping function
35+
pingHost HostPinger
36+
}
37+
38+
func (_ *Ping) Description() string {
39+
return "Ping given url(s) and return statistics"
40+
}
41+
42+
var sampleConfig = `
43+
# urls to ping
44+
urls = ["www.google.com"] # required
45+
# number of pings to send (ping -c <COUNT>)
46+
count = 1 # required
47+
# interval, in s, at which to ping. 0 == default (ping -i <PING_INTERVAL>)
48+
ping_interval = 0.0
49+
# ping timeout, in s. 0 == no timeout (ping -t <TIMEOUT>)
50+
timeout = 0.0
51+
# interface to send ping from (ping -I <INTERFACE>)
52+
interface = ""
53+
`
54+
55+
func (_ *Ping) SampleConfig() string {
56+
return sampleConfig
57+
}
58+
59+
func (p *Ping) Gather(acc plugins.Accumulator) error {
60+
61+
var wg sync.WaitGroup
62+
errorChannel := make(chan error, len(p.Urls)*2)
63+
64+
// Spin off a go routine for each url to ping
65+
for _, url := range p.Urls {
66+
wg.Add(1)
67+
go func(url string, acc plugins.Accumulator) {
68+
defer wg.Done()
69+
args := p.args(url)
70+
out, err := p.pingHost(args...)
71+
if err != nil {
72+
// Combine go err + stderr output
73+
errorChannel <- errors.New(
74+
strings.TrimSpace(out) + ", " + err.Error())
75+
}
76+
tags := map[string]string{"url": url}
77+
trans, rec, avg, err := processPingOutput(out)
78+
if err != nil {
79+
// fatal error
80+
errorChannel <- err
81+
return
82+
}
83+
// Calculate packet loss percentage
84+
loss := float64(trans-rec) / float64(trans) * 100.0
85+
acc.Add("packets_transmitted", trans, tags)
86+
acc.Add("packets_received", rec, tags)
87+
acc.Add("percent_packet_loss", loss, tags)
88+
acc.Add("average_response_ms", avg, tags)
89+
}(url, acc)
90+
}
91+
92+
wg.Wait()
93+
close(errorChannel)
94+
95+
// Get all errors and return them as one giant error
96+
errorStrings := []string{}
97+
for err := range errorChannel {
98+
errorStrings = append(errorStrings, err.Error())
99+
}
100+
101+
if len(errorStrings) == 0 {
102+
return nil
103+
}
104+
return errors.New(strings.Join(errorStrings, "\n"))
105+
}
106+
107+
func hostPinger(args ...string) (string, error) {
108+
c := exec.Command("ping", args...)
109+
out, err := c.CombinedOutput()
110+
return string(out), err
111+
}
112+
113+
// args returns the arguments for the 'ping' executable
114+
func (p *Ping) args(url string) []string {
115+
// Build the ping command args based on toml config
116+
args := []string{"-c", strconv.Itoa(p.Count)}
117+
if p.PingInterval > 0 {
118+
args = append(args, "-i", strconv.FormatFloat(p.PingInterval, 'f', 1, 64))
119+
}
120+
if p.Timeout > 0 {
121+
args = append(args, "-t", strconv.FormatFloat(p.Timeout, 'f', 1, 64))
122+
}
123+
if p.Interface != "" {
124+
args = append(args, "-I", p.Interface)
125+
}
126+
args = append(args, url)
127+
return args
128+
}
129+
130+
// processPingOutput takes in a string output from the ping command, like:
131+
//
132+
// PING www.google.com (173.194.115.84): 56 data bytes
133+
// 64 bytes from 173.194.115.84: icmp_seq=0 ttl=54 time=52.172 ms
134+
// 64 bytes from 173.194.115.84: icmp_seq=1 ttl=54 time=34.843 ms
135+
//
136+
// --- www.google.com ping statistics ---
137+
// 2 packets transmitted, 2 packets received, 0.0% packet loss
138+
// round-trip min/avg/max/stddev = 34.843/43.508/52.172/8.664 ms
139+
//
140+
// It returns (<transmitted packets>, <received packets>, <average response>)
141+
func processPingOutput(out string) (int, int, float64, error) {
142+
var trans, recv int
143+
var avg float64
144+
// Set this error to nil if we find a 'transmitted' line
145+
err := errors.New("Fatal error processing ping output")
146+
lines := strings.Split(out, "\n")
147+
for _, line := range lines {
148+
if strings.Contains(line, "transmitted") &&
149+
strings.Contains(line, "received") {
150+
err = nil
151+
stats := strings.Split(line, ", ")
152+
// Transmitted packets
153+
trans, err = strconv.Atoi(strings.Split(stats[0], " ")[0])
154+
if err != nil {
155+
return trans, recv, avg, err
156+
}
157+
// Received packets
158+
recv, err = strconv.Atoi(strings.Split(stats[1], " ")[0])
159+
if err != nil {
160+
return trans, recv, avg, err
161+
}
162+
} else if strings.Contains(line, "min/avg/max") {
163+
stats := strings.Split(line, " = ")[1]
164+
avg, err = strconv.ParseFloat(strings.Split(stats, "/")[1], 64)
165+
if err != nil {
166+
return trans, recv, avg, err
167+
}
168+
}
169+
}
170+
return trans, recv, avg, err
171+
}
172+
173+
func init() {
174+
plugins.Add("ping", func() plugins.Plugin {
175+
return &Ping{pingHost: hostPinger}
176+
})
177+
}

0 commit comments

Comments
 (0)