Add support for IPv6 in the ping plugin (#4703)
This commit is contained in:
		
							parent
							
								
									2bb7ddd0b6
								
							
						
					
					
						commit
						86b2145272
					
				|  | @ -22,9 +22,11 @@ import ( | ||||||
| // HostPinger is a function that runs the "ping" function using a list of
 | // HostPinger is a function that runs the "ping" function using a list of
 | ||||||
| // passed arguments. This can be easily switched with a mocked ping function
 | // passed arguments. This can be easily switched with a mocked ping function
 | ||||||
| // for unit test purposes (see ping_test.go)
 | // for unit test purposes (see ping_test.go)
 | ||||||
| type HostPinger func(timeout float64, args ...string) (string, error) | type HostPinger func(binary string, timeout float64, args ...string) (string, error) | ||||||
| 
 | 
 | ||||||
| type Ping struct { | type Ping struct { | ||||||
|  | 	wg sync.WaitGroup | ||||||
|  | 
 | ||||||
| 	// Interval at which to ping (ping -i <INTERVAL>)
 | 	// Interval at which to ping (ping -i <INTERVAL>)
 | ||||||
| 	PingInterval float64 `toml:"ping_interval"` | 	PingInterval float64 `toml:"ping_interval"` | ||||||
| 
 | 
 | ||||||
|  | @ -43,6 +45,13 @@ type Ping struct { | ||||||
| 	// URLs to ping
 | 	// URLs to ping
 | ||||||
| 	Urls []string | 	Urls []string | ||||||
| 
 | 
 | ||||||
|  | 	// Ping executable binary
 | ||||||
|  | 	Binary string | ||||||
|  | 
 | ||||||
|  | 	// Arguments for ping command.
 | ||||||
|  | 	// when `Arguments` is not empty, other options (ping_interval, timeout, etc) will be ignored
 | ||||||
|  | 	Arguments []string | ||||||
|  | 
 | ||||||
| 	// host ping function
 | 	// host ping function
 | ||||||
| 	pingHost HostPinger | 	pingHost HostPinger | ||||||
| } | } | ||||||
|  | @ -71,6 +80,13 @@ const sampleConfig = ` | ||||||
|   ## Interface or source address to send ping from (ping -I <INTERFACE/SRC_ADDR>) |   ## Interface or source address to send ping from (ping -I <INTERFACE/SRC_ADDR>) | ||||||
|   ## on Darwin and Freebsd only source address possible: (ping -S <SRC_ADDR>) |   ## on Darwin and Freebsd only source address possible: (ping -S <SRC_ADDR>) | ||||||
|   # interface = "" |   # interface = "" | ||||||
|  | 
 | ||||||
|  |   ## Specify the ping executable binary, default is "ping" | ||||||
|  |   # binary = "ping" | ||||||
|  | 
 | ||||||
|  |   ## Arguments for ping command | ||||||
|  |   ## when arguments is not empty, other options (ping_interval, timeout, etc) will be ignored | ||||||
|  |   # arguments = ["-c", "3"] | ||||||
| ` | ` | ||||||
| 
 | 
 | ||||||
| func (_ *Ping) SampleConfig() string { | func (_ *Ping) SampleConfig() string { | ||||||
|  | @ -78,14 +94,19 @@ func (_ *Ping) SampleConfig() string { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *Ping) Gather(acc telegraf.Accumulator) error { | func (p *Ping) Gather(acc telegraf.Accumulator) error { | ||||||
| 
 |  | ||||||
| 	var wg sync.WaitGroup |  | ||||||
| 
 |  | ||||||
| 	// Spin off a go routine for each url to ping
 | 	// Spin off a go routine for each url to ping
 | ||||||
| 	for _, url := range p.Urls { | 	for _, url := range p.Urls { | ||||||
| 		wg.Add(1) | 		p.wg.Add(1) | ||||||
| 		go func(u string) { | 		go p.pingToURL(url, acc) | ||||||
| 			defer wg.Done() | 	} | ||||||
|  | 
 | ||||||
|  | 	p.wg.Wait() | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *Ping) pingToURL(u string, acc telegraf.Accumulator) { | ||||||
|  | 	defer p.wg.Done() | ||||||
| 	tags := map[string]string{"url": u} | 	tags := map[string]string{"url": u} | ||||||
| 	fields := map[string]interface{}{"result_code": 0} | 	fields := map[string]interface{}{"result_code": 0} | ||||||
| 
 | 
 | ||||||
|  | @ -98,9 +119,12 @@ func (p *Ping) Gather(acc telegraf.Accumulator) error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	args := p.args(u, runtime.GOOS) | 	args := p.args(u, runtime.GOOS) | ||||||
| 			totalTimeout := float64(p.Count)*p.Timeout + float64(p.Count-1)*p.PingInterval | 	totalTimeout := 60.0 | ||||||
|  | 	if len(p.Arguments) == 0 { | ||||||
|  | 		totalTimeout = float64(p.Count)*p.Timeout + float64(p.Count-1)*p.PingInterval | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 			out, err := p.pingHost(totalTimeout, args...) | 	out, err := p.pingHost(p.Binary, totalTimeout, args...) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		// Some implementations of ping return a 1 exit code on
 | 		// Some implementations of ping return a 1 exit code on
 | ||||||
| 		// timeout, if this occurs we will not exit and try to parse
 | 		// timeout, if this occurs we will not exit and try to parse
 | ||||||
|  | @ -152,16 +176,10 @@ func (p *Ping) Gather(acc telegraf.Accumulator) error { | ||||||
| 		fields["standard_deviation_ms"] = stddev | 		fields["standard_deviation_ms"] = stddev | ||||||
| 	} | 	} | ||||||
| 	acc.AddFields("ping", fields, tags) | 	acc.AddFields("ping", fields, tags) | ||||||
| 		}(url) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	wg.Wait() |  | ||||||
| 
 |  | ||||||
| 	return nil |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func hostPinger(timeout float64, args ...string) (string, error) { | func hostPinger(binary string, timeout float64, args ...string) (string, error) { | ||||||
| 	bin, err := exec.LookPath("ping") | 	bin, err := exec.LookPath(binary) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
|  | @ -173,15 +191,21 @@ func hostPinger(timeout float64, args ...string) (string, error) { | ||||||
| 
 | 
 | ||||||
| // args returns the arguments for the 'ping' executable
 | // args returns the arguments for the 'ping' executable
 | ||||||
| func (p *Ping) args(url string, system string) []string { | func (p *Ping) args(url string, system string) []string { | ||||||
| 	// Build the ping command args based on toml config
 | 	if len(p.Arguments) > 0 { | ||||||
|  | 		return p.Arguments | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// build the ping command args based on toml config
 | ||||||
| 	args := []string{"-c", strconv.Itoa(p.Count), "-n", "-s", "16"} | 	args := []string{"-c", strconv.Itoa(p.Count), "-n", "-s", "16"} | ||||||
| 	if p.PingInterval > 0 { | 	if p.PingInterval > 0 { | ||||||
| 		args = append(args, "-i", strconv.FormatFloat(p.PingInterval, 'f', -1, 64)) | 		args = append(args, "-i", strconv.FormatFloat(p.PingInterval, 'f', -1, 64)) | ||||||
| 	} | 	} | ||||||
| 	if p.Timeout > 0 { | 	if p.Timeout > 0 { | ||||||
| 		switch system { | 		switch system { | ||||||
| 		case "darwin", "freebsd", "netbsd", "openbsd": | 		case "darwin": | ||||||
| 			args = append(args, "-W", strconv.FormatFloat(p.Timeout*1000, 'f', -1, 64)) | 			args = append(args, "-W", strconv.FormatFloat(p.Timeout*1000, 'f', -1, 64)) | ||||||
|  | 		case "freebsd", "netbsd", "openbsd": | ||||||
|  | 			args = append(args, "-w", strconv.FormatFloat(p.Timeout*1000, 'f', -1, 64)) | ||||||
| 		case "linux": | 		case "linux": | ||||||
| 			args = append(args, "-W", strconv.FormatFloat(p.Timeout, 'f', -1, 64)) | 			args = append(args, "-W", strconv.FormatFloat(p.Timeout, 'f', -1, 64)) | ||||||
| 		default: | 		default: | ||||||
|  | @ -196,19 +220,21 @@ func (p *Ping) args(url string, system string) []string { | ||||||
| 		case "linux": | 		case "linux": | ||||||
| 			args = append(args, "-w", strconv.Itoa(p.Deadline)) | 			args = append(args, "-w", strconv.Itoa(p.Deadline)) | ||||||
| 		default: | 		default: | ||||||
| 			// Not sure the best option here, just assume GNU ping?
 | 			// not sure the best option here, just assume gnu ping?
 | ||||||
| 			args = append(args, "-w", strconv.Itoa(p.Deadline)) | 			args = append(args, "-w", strconv.Itoa(p.Deadline)) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if p.Interface != "" { | 	if p.Interface != "" { | ||||||
| 		switch system { | 		switch system { | ||||||
| 		case "darwin", "freebsd", "netbsd", "openbsd": | 		case "darwin": | ||||||
| 			args = append(args, "-S", p.Interface) | 			args = append(args, "-I", p.Interface) | ||||||
|  | 		case "freebsd", "netbsd", "openbsd": | ||||||
|  | 			args = append(args, "-s", p.Interface) | ||||||
| 		case "linux": | 		case "linux": | ||||||
| 			args = append(args, "-I", p.Interface) | 			args = append(args, "-I", p.Interface) | ||||||
| 		default: | 		default: | ||||||
| 			// Not sure the best option here, just assume GNU ping?
 | 			// not sure the best option here, just assume gnu ping?
 | ||||||
| 			args = append(args, "-I", p.Interface) | 			args = append(args, "-i", p.Interface) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	args = append(args, url) | 	args = append(args, url) | ||||||
|  | @ -217,7 +243,7 @@ func (p *Ping) args(url string, system string) []string { | ||||||
| 
 | 
 | ||||||
| // processPingOutput takes in a string output from the ping command, like:
 | // processPingOutput takes in a string output from the ping command, like:
 | ||||||
| //
 | //
 | ||||||
| //     PING www.google.com (173.194.115.84): 56 data bytes
 | //     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=0 ttl=54 time=52.172 ms
 | ||||||
| //     64 bytes from 173.194.115.84: icmp_seq=1 ttl=54 time=34.843 ms
 | //     64 bytes from 173.194.115.84: icmp_seq=1 ttl=54 time=34.843 ms
 | ||||||
| //
 | //
 | ||||||
|  | @ -280,6 +306,8 @@ func init() { | ||||||
| 			Count:        1, | 			Count:        1, | ||||||
| 			Timeout:      1.0, | 			Timeout:      1.0, | ||||||
| 			Deadline:     10, | 			Deadline:     10, | ||||||
|  | 			Binary:       "ping", | ||||||
|  | 			Arguments:    []string{}, | ||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -110,9 +110,9 @@ func TestArgs(t *testing.T) { | ||||||
| 		system string | 		system string | ||||||
| 		output []string | 		output []string | ||||||
| 	}{ | 	}{ | ||||||
| 		{"darwin", []string{"-c", "2", "-n", "-s", "16", "-i", "1.2", "-W", "12000", "-t", "24", "-S", "eth0", "www.google.com"}}, | 		{"darwin", []string{"-c", "2", "-n", "-s", "16", "-i", "1.2", "-W", "12000", "-t", "24", "-I", "eth0", "www.google.com"}}, | ||||||
| 		{"linux", []string{"-c", "2", "-n", "-s", "16", "-i", "1.2", "-W", "12", "-w", "24", "-I", "eth0", "www.google.com"}}, | 		{"linux", []string{"-c", "2", "-n", "-s", "16", "-i", "1.2", "-W", "12", "-w", "24", "-I", "eth0", "www.google.com"}}, | ||||||
| 		{"anything else", []string{"-c", "2", "-n", "-s", "16", "-i", "1.2", "-W", "12", "-w", "24", "-I", "eth0", "www.google.com"}}, | 		{"anything else", []string{"-c", "2", "-n", "-s", "16", "-i", "1.2", "-W", "12", "-w", "24", "-i", "eth0", "www.google.com"}}, | ||||||
| 	} | 	} | ||||||
| 	for i := range systemCases { | 	for i := range systemCases { | ||||||
| 		actual := p.args("www.google.com", systemCases[i].system) | 		actual := p.args("www.google.com", systemCases[i].system) | ||||||
|  | @ -124,7 +124,24 @@ func TestArgs(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func mockHostPinger(timeout float64, args ...string) (string, error) { | func TestArguments(t *testing.T) { | ||||||
|  | 	arguments := []string{"-c", "3"} | ||||||
|  | 	p := Ping{ | ||||||
|  | 		Count:        2, | ||||||
|  | 		Interface:    "eth0", | ||||||
|  | 		Timeout:      12.0, | ||||||
|  | 		Deadline:     24, | ||||||
|  | 		PingInterval: 1.2, | ||||||
|  | 		Arguments:    arguments, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, system := range []string{"darwin", "linux", "anything else"} { | ||||||
|  | 		actual := p.args("www.google.com", system) | ||||||
|  | 		require.True(t, reflect.DeepEqual(actual, arguments), "Expected: %s Actual: %s", arguments, actual) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func mockHostPinger(binary string, timeout float64, args ...string) (string, error) { | ||||||
| 	return linuxPingOutput, nil | 	return linuxPingOutput, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -165,7 +182,7 @@ PING www.google.com (216.58.218.164) 56(84) bytes of data. | ||||||
| rtt min/avg/max/mdev = 35.225/44.033/51.806/5.325 ms | rtt min/avg/max/mdev = 35.225/44.033/51.806/5.325 ms | ||||||
| ` | ` | ||||||
| 
 | 
 | ||||||
| func mockLossyHostPinger(timeout float64, args ...string) (string, error) { | func mockLossyHostPinger(binary string, timeout float64, args ...string) (string, error) { | ||||||
| 	return lossyPingOutput, nil | 	return lossyPingOutput, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -200,7 +217,7 @@ Request timeout for icmp_seq 0 | ||||||
| 2 packets transmitted, 0 packets received, 100.0% packet loss | 2 packets transmitted, 0 packets received, 100.0% packet loss | ||||||
| ` | ` | ||||||
| 
 | 
 | ||||||
| func mockErrorHostPinger(timeout float64, args ...string) (string, error) { | func mockErrorHostPinger(binary string, timeout float64, args ...string) (string, error) { | ||||||
| 	// This error will not trigger correct error paths
 | 	// This error will not trigger correct error paths
 | ||||||
| 	return errorPingOutput, nil | 	return errorPingOutput, nil | ||||||
| } | } | ||||||
|  | @ -225,7 +242,7 @@ func TestBadPingGather(t *testing.T) { | ||||||
| 	acc.AssertContainsTaggedFields(t, "ping", fields, tags) | 	acc.AssertContainsTaggedFields(t, "ping", fields, tags) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func mockFatalHostPinger(timeout float64, args ...string) (string, error) { | func mockFatalHostPinger(binary string, timeout float64, args ...string) (string, error) { | ||||||
| 	return fatalPingOutput, errors.New("So very bad") | 	return fatalPingOutput, errors.New("So very bad") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -265,7 +282,7 @@ func TestErrorWithHostNamePingGather(t *testing.T) { | ||||||
| 		var acc testutil.Accumulator | 		var acc testutil.Accumulator | ||||||
| 		p := Ping{ | 		p := Ping{ | ||||||
| 			Urls: []string{"www.amazon.com"}, | 			Urls: []string{"www.amazon.com"}, | ||||||
| 			pingHost: func(timeout float64, args ...string) (string, error) { | 			pingHost: func(binary string, timeout float64, args ...string) (string, error) { | ||||||
| 				return param.out, errors.New("So very bad") | 				return param.out, errors.New("So very bad") | ||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
|  | @ -274,3 +291,16 @@ func TestErrorWithHostNamePingGather(t *testing.T) { | ||||||
| 		assert.Contains(t, acc.Errors, param.error) | 		assert.Contains(t, acc.Errors, param.error) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestPingBinary(t *testing.T) { | ||||||
|  | 	var acc testutil.Accumulator | ||||||
|  | 	p := Ping{ | ||||||
|  | 		Urls:   []string{"www.google.com"}, | ||||||
|  | 		Binary: "ping6", | ||||||
|  | 		pingHost: func(binary string, timeout float64, args ...string) (string, error) { | ||||||
|  | 			assert.True(t, binary == "ping6") | ||||||
|  | 			return "", nil | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	acc.GatherError(p.Gather) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ package ping | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"fmt" | ||||||
| 	"net" | 	"net" | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"regexp" | 	"regexp" | ||||||
|  | @ -20,9 +21,11 @@ import ( | ||||||
| // HostPinger is a function that runs the "ping" function using a list of
 | // HostPinger is a function that runs the "ping" function using a list of
 | ||||||
| // passed arguments. This can be easily switched with a mocked ping function
 | // passed arguments. This can be easily switched with a mocked ping function
 | ||||||
| // for unit test purposes (see ping_test.go)
 | // for unit test purposes (see ping_test.go)
 | ||||||
| type HostPinger func(timeout float64, args ...string) (string, error) | type HostPinger func(binary string, timeout float64, args ...string) (string, error) | ||||||
| 
 | 
 | ||||||
| type Ping struct { | type Ping struct { | ||||||
|  | 	wg sync.WaitGroup | ||||||
|  | 
 | ||||||
| 	// Number of pings to send (ping -c <COUNT>)
 | 	// Number of pings to send (ping -c <COUNT>)
 | ||||||
| 	Count int | 	Count int | ||||||
| 
 | 
 | ||||||
|  | @ -32,6 +35,13 @@ type Ping struct { | ||||||
| 	// URLs to ping
 | 	// URLs to ping
 | ||||||
| 	Urls []string | 	Urls []string | ||||||
| 
 | 
 | ||||||
|  | 	// Ping executable binary
 | ||||||
|  | 	Binary string | ||||||
|  | 
 | ||||||
|  | 	// Arguments for ping command.
 | ||||||
|  | 	// when `Arguments` is not empty, other options (ping_interval, timeout, etc) will be ignored
 | ||||||
|  | 	Arguments []string | ||||||
|  | 
 | ||||||
| 	// host ping function
 | 	// host ping function
 | ||||||
| 	pingHost HostPinger | 	pingHost HostPinger | ||||||
| } | } | ||||||
|  | @ -49,14 +59,100 @@ const sampleConfig = ` | ||||||
| 
 | 
 | ||||||
| 	## Ping timeout, in seconds. 0.0 means default timeout (ping -w <TIMEOUT>) | 	## Ping timeout, in seconds. 0.0 means default timeout (ping -w <TIMEOUT>) | ||||||
| 	# timeout = 0.0 | 	# timeout = 0.0 | ||||||
|  | 
 | ||||||
|  | 	## Specify the ping executable binary, default is "ping" | ||||||
|  | 	# binary = "ping" | ||||||
|  | 
 | ||||||
|  | 	## Arguments for ping command | ||||||
|  | 	## when arguments is not empty, other options (ping_interval, timeout, etc) will be ignored | ||||||
|  | 	# arguments = ["-c", "3"] | ||||||
| ` | ` | ||||||
| 
 | 
 | ||||||
| func (s *Ping) SampleConfig() string { | func (s *Ping) SampleConfig() string { | ||||||
| 	return sampleConfig | 	return sampleConfig | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func hostPinger(timeout float64, args ...string) (string, error) { | func (p *Ping) Gather(acc telegraf.Accumulator) error { | ||||||
| 	bin, err := exec.LookPath("ping") | 	if p.Count < 1 { | ||||||
|  | 		p.Count = 1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Spin off a go routine for each url to ping
 | ||||||
|  | 	for _, url := range p.Urls { | ||||||
|  | 		p.wg.Add(1) | ||||||
|  | 		go p.pingToURL(url, acc) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	p.wg.Wait() | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (p *Ping) pingToURL(u string, acc telegraf.Accumulator) { | ||||||
|  | 	defer p.wg.Done() | ||||||
|  | 
 | ||||||
|  | 	tags := map[string]string{"url": u} | ||||||
|  | 	fields := map[string]interface{}{"result_code": 0} | ||||||
|  | 
 | ||||||
|  | 	_, err := net.LookupHost(u) | ||||||
|  | 	if err != nil { | ||||||
|  | 		acc.AddError(err) | ||||||
|  | 		fields["result_code"] = 1 | ||||||
|  | 		acc.AddFields("ping", fields, tags) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	args := p.args(u) | ||||||
|  | 	totalTimeout := 60.0 | ||||||
|  | 	if len(p.Arguments) == 0 { | ||||||
|  | 		totalTimeout = p.timeout() * float64(p.Count) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	out, err := p.pingHost(p.Binary, totalTimeout, args...) | ||||||
|  | 	// ping host return exitcode != 0 also when there was no response from host
 | ||||||
|  | 	// but command was execute successfully
 | ||||||
|  | 	var pendingError error | ||||||
|  | 	if err != nil { | ||||||
|  | 		// Combine go err + stderr output
 | ||||||
|  | 		pendingError = errors.New(strings.TrimSpace(out) + ", " + err.Error()) | ||||||
|  | 	} | ||||||
|  | 	trans, recReply, receivePacket, avg, min, max, err := processPingOutput(out) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// fatal error
 | ||||||
|  | 		if pendingError != nil { | ||||||
|  | 			acc.AddError(fmt.Errorf("%s: %s", pendingError, u)) | ||||||
|  | 		} else { | ||||||
|  | 			acc.AddError(fmt.Errorf("%s: %s", err, u)) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		fields["result_code"] = 2 | ||||||
|  | 		fields["errors"] = 100.0 | ||||||
|  | 		acc.AddFields("ping", fields, tags) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	// Calculate packet loss percentage
 | ||||||
|  | 	lossReply := float64(trans-recReply) / float64(trans) * 100.0 | ||||||
|  | 	lossPackets := float64(trans-receivePacket) / float64(trans) * 100.0 | ||||||
|  | 
 | ||||||
|  | 	fields["packets_transmitted"] = trans | ||||||
|  | 	fields["reply_received"] = recReply | ||||||
|  | 	fields["packets_received"] = receivePacket | ||||||
|  | 	fields["percent_packet_loss"] = lossPackets | ||||||
|  | 	fields["percent_reply_loss"] = lossReply | ||||||
|  | 	if avg >= 0 { | ||||||
|  | 		fields["average_response_ms"] = float64(avg) | ||||||
|  | 	} | ||||||
|  | 	if min >= 0 { | ||||||
|  | 		fields["minimum_response_ms"] = float64(min) | ||||||
|  | 	} | ||||||
|  | 	if max >= 0 { | ||||||
|  | 		fields["maximum_response_ms"] = float64(max) | ||||||
|  | 	} | ||||||
|  | 	acc.AddFields("ping", fields, tags) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func hostPinger(binary string, timeout float64, args ...string) (string, error) { | ||||||
|  | 	bin, err := exec.LookPath(binary) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
|  | @ -66,6 +162,23 @@ func hostPinger(timeout float64, args ...string) (string, error) { | ||||||
| 	return string(out), err | 	return string(out), err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // args returns the arguments for the 'ping' executable
 | ||||||
|  | func (p *Ping) args(url string) []string { | ||||||
|  | 	if len(p.Arguments) > 0 { | ||||||
|  | 		return p.Arguments | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	args := []string{"-n", strconv.Itoa(p.Count)} | ||||||
|  | 
 | ||||||
|  | 	if p.Timeout > 0 { | ||||||
|  | 		args = append(args, "-w", strconv.FormatFloat(p.Timeout*1000, 'f', 0, 64)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	args = append(args, url) | ||||||
|  | 
 | ||||||
|  | 	return args | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // 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 )
 | // based on linux implementation but using regex ( multilanguage support )
 | ||||||
| // It returns (<transmitted packets>, <received reply>, <received packet>, <average response>, <min response>, <max response>)
 | // It returns (<transmitted packets>, <received reply>, <received packet>, <average response>, <min response>, <max response>)
 | ||||||
|  | @ -134,106 +247,13 @@ func (p *Ping) timeout() float64 { | ||||||
| 	return 4 + 1 | 	return 4 + 1 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // args returns the arguments for the 'ping' executable
 |  | ||||||
| func (p *Ping) args(url string) []string { |  | ||||||
| 	args := []string{"-n", strconv.Itoa(p.Count)} |  | ||||||
| 
 |  | ||||||
| 	if p.Timeout > 0 { |  | ||||||
| 		args = append(args, "-w", strconv.FormatFloat(p.Timeout*1000, 'f', 0, 64)) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	args = append(args, url) |  | ||||||
| 
 |  | ||||||
| 	return args |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (p *Ping) Gather(acc telegraf.Accumulator) error { |  | ||||||
| 	if p.Count < 1 { |  | ||||||
| 		p.Count = 1 |  | ||||||
| 	} |  | ||||||
| 	var wg sync.WaitGroup |  | ||||||
| 	errorChannel := make(chan error, len(p.Urls)*2) |  | ||||||
| 	var pendingError error = nil |  | ||||||
| 	// Spin off a go routine for each url to ping
 |  | ||||||
| 	for _, url := range p.Urls { |  | ||||||
| 		wg.Add(1) |  | ||||||
| 		go func(u string) { |  | ||||||
| 			defer wg.Done() |  | ||||||
| 
 |  | ||||||
| 			tags := map[string]string{"url": u} |  | ||||||
| 			fields := map[string]interface{}{"result_code": 0} |  | ||||||
| 
 |  | ||||||
| 			_, err := net.LookupHost(u) |  | ||||||
| 			if err != nil { |  | ||||||
| 				errorChannel <- err |  | ||||||
| 				fields["result_code"] = 1 |  | ||||||
| 				acc.AddFields("ping", fields, tags) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			args := p.args(u) |  | ||||||
| 			totalTimeout := p.timeout() * float64(p.Count) |  | ||||||
| 			out, err := p.pingHost(totalTimeout, args...) |  | ||||||
| 			// ping host return exitcode != 0 also when there was no response from host
 |  | ||||||
| 			// but command was execute successfully
 |  | ||||||
| 			if err != nil { |  | ||||||
| 				// Combine go err + stderr output
 |  | ||||||
| 				pendingError = errors.New(strings.TrimSpace(out) + ", " + err.Error()) |  | ||||||
| 			} |  | ||||||
| 			trans, recReply, receivePacket, avg, min, max, err := processPingOutput(out) |  | ||||||
| 			if err != nil { |  | ||||||
| 				// fatal error
 |  | ||||||
| 				if pendingError != nil { |  | ||||||
| 					errorChannel <- pendingError |  | ||||||
| 				} |  | ||||||
| 				errorChannel <- err |  | ||||||
| 
 |  | ||||||
| 				fields["errors"] = 100.0 |  | ||||||
| 				acc.AddFields("ping", fields, tags) |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			// Calculate packet loss percentage
 |  | ||||||
| 			lossReply := float64(trans-recReply) / float64(trans) * 100.0 |  | ||||||
| 			lossPackets := float64(trans-receivePacket) / float64(trans) * 100.0 |  | ||||||
| 
 |  | ||||||
| 			fields["packets_transmitted"] = trans |  | ||||||
| 			fields["reply_received"] = recReply |  | ||||||
| 			fields["packets_received"] = receivePacket |  | ||||||
| 			fields["percent_packet_loss"] = lossPackets |  | ||||||
| 			fields["percent_reply_loss"] = lossReply |  | ||||||
| 			if avg >= 0 { |  | ||||||
| 				fields["average_response_ms"] = float64(avg) |  | ||||||
| 			} |  | ||||||
| 			if min >= 0 { |  | ||||||
| 				fields["minimum_response_ms"] = float64(min) |  | ||||||
| 			} |  | ||||||
| 			if max >= 0 { |  | ||||||
| 				fields["maximum_response_ms"] = float64(max) |  | ||||||
| 			} |  | ||||||
| 			acc.AddFields("ping", fields, tags) |  | ||||||
| 		}(url) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	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")) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func init() { | func init() { | ||||||
| 	inputs.Add("ping", func() telegraf.Input { | 	inputs.Add("ping", func() telegraf.Input { | ||||||
| 		return &Ping{ | 		return &Ping{ | ||||||
| 			pingHost:  hostPinger, | 			pingHost:  hostPinger, | ||||||
| 			Count:     1, | 			Count:     1, | ||||||
|  | 			Binary:    "ping", | ||||||
|  | 			Arguments: []string{}, | ||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -4,10 +4,12 @@ package ping | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"reflect" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"github.com/influxdata/telegraf/testutil" | 	"github.com/influxdata/telegraf/testutil" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"github.com/stretchr/testify/require" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Windows ping format ( should support multilanguage ?)
 | // Windows ping format ( should support multilanguage ?)
 | ||||||
|  | @ -59,7 +61,7 @@ func TestHost(t *testing.T) { | ||||||
| 	assert.Equal(t, 52, max, "Max 52") | 	assert.Equal(t, 52, max, "Max 52") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func mockHostPinger(timeout float64, args ...string) (string, error) { | func mockHostPinger(binary string, timeout float64, args ...string) (string, error) { | ||||||
| 	return winENPingOutput, nil | 	return winENPingOutput, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -102,7 +104,7 @@ Statystyka badania ping dla 195.187.242.157: | ||||||
|              (100% straty), |              (100% straty), | ||||||
| ` | ` | ||||||
| 
 | 
 | ||||||
| func mockErrorHostPinger(timeout float64, args ...string) (string, error) { | func mockErrorHostPinger(binary string, timeout float64, args ...string) (string, error) { | ||||||
| 	return errorPingOutput, errors.New("No packets received") | 	return errorPingOutput, errors.New("No packets received") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -128,6 +130,18 @@ func TestBadPingGather(t *testing.T) { | ||||||
| 	acc.AssertContainsTaggedFields(t, "ping", fields, tags) | 	acc.AssertContainsTaggedFields(t, "ping", fields, tags) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func TestArguments(t *testing.T) { | ||||||
|  | 	arguments := []string{"-c", "3"} | ||||||
|  | 	p := Ping{ | ||||||
|  | 		Count:     2, | ||||||
|  | 		Timeout:   12.0, | ||||||
|  | 		Arguments: arguments, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	actual := p.args("www.google.com") | ||||||
|  | 	require.True(t, reflect.DeepEqual(actual, arguments), "Expected : %s Actual: %s", arguments, actual) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| var lossyPingOutput = ` | var lossyPingOutput = ` | ||||||
| Badanie thecodinglove.com [66.6.44.4] z 9800 bajtami danych: | Badanie thecodinglove.com [66.6.44.4] z 9800 bajtami danych: | ||||||
| Upłynął limit czasu żądania. | Upłynął limit czasu żądania. | ||||||
|  | @ -147,7 +161,7 @@ Szacunkowy czas błądzenia pakietów w millisekundach: | ||||||
|     Minimum = 114 ms, Maksimum = 119 ms, Czas średni = 115 ms |     Minimum = 114 ms, Maksimum = 119 ms, Czas średni = 115 ms | ||||||
| ` | ` | ||||||
| 
 | 
 | ||||||
| func mockLossyHostPinger(timeout float64, args ...string) (string, error) { | func mockLossyHostPinger(binary string, timeout float64, args ...string) (string, error) { | ||||||
| 	return lossyPingOutput, nil | 	return lossyPingOutput, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -207,7 +221,7 @@ Options: | ||||||
| 
 | 
 | ||||||
| ` | ` | ||||||
| 
 | 
 | ||||||
| func mockFatalHostPinger(timeout float64, args ...string) (string, error) { | func mockFatalHostPinger(binary string, timeout float64, args ...string) (string, error) { | ||||||
| 	return fatalPingOutput, errors.New("So very bad") | 	return fatalPingOutput, errors.New("So very bad") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -249,7 +263,7 @@ Ping statistics for 8.8.8.8: | ||||||
|     Packets: Sent = 4, Received = 1, Lost = 3 (75% loss), |     Packets: Sent = 4, Received = 1, Lost = 3 (75% loss), | ||||||
| ` | ` | ||||||
| 
 | 
 | ||||||
| func mockUnreachableHostPinger(timeout float64, args ...string) (string, error) { | func mockUnreachableHostPinger(binary string, timeout float64, args ...string) (string, error) { | ||||||
| 	return UnreachablePingOutput, errors.New("So very bad") | 	return UnreachablePingOutput, errors.New("So very bad") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -298,7 +312,7 @@ Ping statistics for 8.8.8.8: | ||||||
|     Packets: Sent = 4, Received = 1, Lost = 3 (75% loss), |     Packets: Sent = 4, Received = 1, Lost = 3 (75% loss), | ||||||
| ` | ` | ||||||
| 
 | 
 | ||||||
| func mockTTLExpiredPinger(timeout float64, args ...string) (string, error) { | func mockTTLExpiredPinger(binary string, timeout float64, args ...string) (string, error) { | ||||||
| 	return TTLExpiredPingOutput, errors.New("So very bad") | 	return TTLExpiredPingOutput, errors.New("So very bad") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -333,3 +347,16 @@ func TestTTLExpiredPingGather(t *testing.T) { | ||||||
| 	assert.False(t, acc.HasInt64Field("ping", "minimum_response_ms"), | 	assert.False(t, acc.HasInt64Field("ping", "minimum_response_ms"), | ||||||
| 		"Fatal ping should not have packet measurements") | 		"Fatal ping should not have packet measurements") | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestPingBinary(t *testing.T) { | ||||||
|  | 	var acc testutil.Accumulator | ||||||
|  | 	p := Ping{ | ||||||
|  | 		Urls:   []string{"www.google.com"}, | ||||||
|  | 		Binary: "ping6", | ||||||
|  | 		pingHost: func(binary string, timeout float64, args ...string) (string, error) { | ||||||
|  | 			assert.True(t, binary == "ping6") | ||||||
|  | 			return "", nil | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	acc.GatherError(p.Gather) | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue