2016-03-14 12:56:33 +00:00
|
|
|
package ntpq
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
2017-04-24 18:13:26 +00:00
|
|
|
"fmt"
|
2016-03-14 12:56:33 +00:00
|
|
|
"os/exec"
|
2017-07-18 18:01:08 +00:00
|
|
|
"regexp"
|
2016-03-14 12:56:33 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/influxdata/telegraf"
|
|
|
|
"github.com/influxdata/telegraf/plugins/inputs"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Mapping of ntpq header names to tag keys
|
|
|
|
var tagHeaders map[string]string = map[string]string{
|
|
|
|
"remote": "remote",
|
|
|
|
"refid": "refid",
|
|
|
|
"st": "stratum",
|
|
|
|
"t": "type",
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mapping of the ntpq tag key to the index in the command output
|
|
|
|
var tagI map[string]int = map[string]int{
|
|
|
|
"remote": -1,
|
|
|
|
"refid": -1,
|
|
|
|
"stratum": -1,
|
|
|
|
"type": -1,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mapping of float metrics to their index in the command output
|
|
|
|
var floatI map[string]int = map[string]int{
|
|
|
|
"delay": -1,
|
|
|
|
"offset": -1,
|
|
|
|
"jitter": -1,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mapping of int metrics to their index in the command output
|
|
|
|
var intI map[string]int = map[string]int{
|
|
|
|
"when": -1,
|
|
|
|
"poll": -1,
|
|
|
|
"reach": -1,
|
|
|
|
}
|
|
|
|
|
|
|
|
type NTPQ struct {
|
|
|
|
runQ func() ([]byte, error)
|
|
|
|
|
|
|
|
DNSLookup bool `toml:"dns_lookup"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *NTPQ) Description() string {
|
|
|
|
return "Get standard NTP query metrics, requires ntpq executable."
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *NTPQ) SampleConfig() string {
|
|
|
|
return `
|
|
|
|
## If false, set the -n ntpq flag. Can reduce metric gather time.
|
|
|
|
dns_lookup = true
|
|
|
|
`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *NTPQ) Gather(acc telegraf.Accumulator) error {
|
|
|
|
out, err := n.runQ()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-07-18 18:01:08 +00:00
|
|
|
// Due to problems with a parsing, we have to use regexp expression in order
|
|
|
|
// to remove string that starts from '(' and ends with space
|
|
|
|
// see: https://github.com/influxdata/telegraf/issues/2386
|
2017-08-25 18:54:06 +00:00
|
|
|
reg, err := regexp.Compile("\\s+\\([\\S]*")
|
2017-07-18 18:01:08 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-03-14 12:56:33 +00:00
|
|
|
lineCounter := 0
|
2019-05-07 21:54:43 +00:00
|
|
|
numColumns := 0
|
2016-03-14 12:56:33 +00:00
|
|
|
scanner := bufio.NewScanner(bytes.NewReader(out))
|
|
|
|
for scanner.Scan() {
|
2016-05-18 12:37:09 +00:00
|
|
|
line := scanner.Text()
|
|
|
|
|
|
|
|
tags := make(map[string]string)
|
|
|
|
// if there is an ntpq state prefix, remove it and make it it's own tag
|
|
|
|
// see https://github.com/influxdata/telegraf/issues/1161
|
|
|
|
if strings.ContainsAny(string(line[0]), "*#o+x.-") {
|
|
|
|
tags["state_prefix"] = string(line[0])
|
|
|
|
line = strings.TrimLeft(line, "*#o+x.-")
|
|
|
|
}
|
|
|
|
|
2017-07-18 18:01:08 +00:00
|
|
|
line = reg.ReplaceAllString(line, "")
|
|
|
|
|
2016-05-18 12:37:09 +00:00
|
|
|
fields := strings.Fields(line)
|
2016-03-14 12:56:33 +00:00
|
|
|
if len(fields) < 2 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// If lineCounter == 0, then this is the header line
|
|
|
|
if lineCounter == 0 {
|
2019-05-07 21:54:43 +00:00
|
|
|
numColumns = len(fields)
|
2016-03-14 12:56:33 +00:00
|
|
|
for i, field := range fields {
|
|
|
|
// Check if field is a tag:
|
|
|
|
if tagKey, ok := tagHeaders[field]; ok {
|
|
|
|
tagI[tagKey] = i
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if field is a float metric:
|
|
|
|
if _, ok := floatI[field]; ok {
|
|
|
|
floatI[field] = i
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if field is an int metric:
|
|
|
|
if _, ok := intI[field]; ok {
|
|
|
|
intI[field] = i
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2019-05-07 21:54:43 +00:00
|
|
|
if len(fields) != numColumns {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2016-03-14 12:56:33 +00:00
|
|
|
mFields := make(map[string]interface{})
|
|
|
|
|
|
|
|
// Get tags from output
|
|
|
|
for key, index := range tagI {
|
|
|
|
if index == -1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
tags[key] = fields[index]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get integer metrics from output
|
|
|
|
for key, index := range intI {
|
2016-08-16 08:06:19 +00:00
|
|
|
if index == -1 || index >= len(fields) {
|
2016-03-14 12:56:33 +00:00
|
|
|
continue
|
|
|
|
}
|
2016-05-18 20:22:10 +00:00
|
|
|
if fields[index] == "-" {
|
|
|
|
continue
|
|
|
|
}
|
2016-03-14 12:56:33 +00:00
|
|
|
|
|
|
|
if key == "when" {
|
|
|
|
when := fields[index]
|
|
|
|
switch {
|
|
|
|
case strings.HasSuffix(when, "h"):
|
|
|
|
m, err := strconv.Atoi(strings.TrimSuffix(fields[index], "h"))
|
|
|
|
if err != nil {
|
2017-04-24 18:13:26 +00:00
|
|
|
acc.AddError(fmt.Errorf("E! Error ntpq: parsing int: %s", fields[index]))
|
2016-03-14 12:56:33 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
// seconds in an hour
|
2017-01-24 23:27:44 +00:00
|
|
|
mFields[key] = int64(m) * 3600
|
2016-03-14 12:56:33 +00:00
|
|
|
continue
|
|
|
|
case strings.HasSuffix(when, "d"):
|
|
|
|
m, err := strconv.Atoi(strings.TrimSuffix(fields[index], "d"))
|
|
|
|
if err != nil {
|
2017-04-24 18:13:26 +00:00
|
|
|
acc.AddError(fmt.Errorf("E! Error ntpq: parsing int: %s", fields[index]))
|
2016-03-14 12:56:33 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
// seconds in a day
|
|
|
|
mFields[key] = int64(m) * 86400
|
|
|
|
continue
|
|
|
|
case strings.HasSuffix(when, "m"):
|
|
|
|
m, err := strconv.Atoi(strings.TrimSuffix(fields[index], "m"))
|
|
|
|
if err != nil {
|
2017-04-24 18:13:26 +00:00
|
|
|
acc.AddError(fmt.Errorf("E! Error ntpq: parsing int: %s", fields[index]))
|
2016-03-14 12:56:33 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
// seconds in a day
|
|
|
|
mFields[key] = int64(m) * 60
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m, err := strconv.Atoi(fields[index])
|
|
|
|
if err != nil {
|
2017-04-24 18:13:26 +00:00
|
|
|
acc.AddError(fmt.Errorf("E! Error ntpq: parsing int: %s", fields[index]))
|
2016-03-14 12:56:33 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
mFields[key] = int64(m)
|
|
|
|
}
|
|
|
|
|
|
|
|
// get float metrics from output
|
|
|
|
for key, index := range floatI {
|
2016-08-16 08:06:19 +00:00
|
|
|
if index == -1 || index >= len(fields) {
|
2016-03-14 12:56:33 +00:00
|
|
|
continue
|
|
|
|
}
|
2016-05-18 20:22:10 +00:00
|
|
|
if fields[index] == "-" {
|
|
|
|
continue
|
|
|
|
}
|
2016-03-14 12:56:33 +00:00
|
|
|
|
|
|
|
m, err := strconv.ParseFloat(fields[index], 64)
|
|
|
|
if err != nil {
|
2017-04-24 18:13:26 +00:00
|
|
|
acc.AddError(fmt.Errorf("E! Error ntpq: parsing float: %s", fields[index]))
|
2016-03-14 12:56:33 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
mFields[key] = m
|
|
|
|
}
|
|
|
|
|
|
|
|
acc.AddFields("ntpq", mFields, tags)
|
|
|
|
}
|
|
|
|
|
|
|
|
lineCounter++
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *NTPQ) runq() ([]byte, error) {
|
|
|
|
bin, err := exec.LookPath("ntpq")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var cmd *exec.Cmd
|
|
|
|
if n.DNSLookup {
|
|
|
|
cmd = exec.Command(bin, "-p")
|
|
|
|
} else {
|
|
|
|
cmd = exec.Command(bin, "-p", "-n")
|
|
|
|
}
|
|
|
|
|
|
|
|
return cmd.Output()
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
inputs.Add("ntpq", func() telegraf.Input {
|
|
|
|
n := &NTPQ{}
|
|
|
|
n.runQ = n.runq
|
|
|
|
return n
|
|
|
|
})
|
|
|
|
}
|