Skip to content

Commit

Permalink
Input plugin for running ntp queries
Browse files Browse the repository at this point in the history
see #235
  • Loading branch information
sparrc committed Mar 14, 2016
1 parent 2fbcb5c commit 4927e8e
Show file tree
Hide file tree
Showing 6 changed files with 512 additions and 2 deletions.
2 changes: 0 additions & 2 deletions plugins/inputs/EXAMPLE_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ The example plugin gathers metrics about example things

### Example Output:

Give an example `-test` output here

```
$ ./telegraf -config telegraf.conf -input-filter example -test
measurement1,tag1=foo,tag2=bar field1=1i,field2=2.1 1453831884664956455
Expand Down
1 change: 1 addition & 0 deletions plugins/inputs/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/net_response"
_ "github.com/influxdata/telegraf/plugins/inputs/nginx"
_ "github.com/influxdata/telegraf/plugins/inputs/nsq"
_ "github.com/influxdata/telegraf/plugins/inputs/ntpq"
_ "github.com/influxdata/telegraf/plugins/inputs/passenger"
_ "github.com/influxdata/telegraf/plugins/inputs/phpfpm"
_ "github.com/influxdata/telegraf/plugins/inputs/ping"
Expand Down
60 changes: 60 additions & 0 deletions plugins/inputs/ntpq/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# ntpq Input Plugin

Get standard NTP query metrics, requires ntpq executable.

Below is the documentation of the various headers returned from the NTP query
command when running `ntpq -p`.

- remote – The remote peer or server being synced to. “LOCAL” is this local host
(included in case there are no remote peers or servers available);
- refid – Where or what the remote peer or server is itself synchronised to;
- st (stratum) – The remote peer or server Stratum
- t (type) – Type (u: unicast or manycast client, b: broadcast or multicast client,
l: local reference clock, s: symmetric peer, A: manycast server,
B: broadcast server, M: multicast server, see “Automatic Server Discovery“);
- when – When last polled (seconds ago, “h” hours ago, or “d” days ago);
- poll – Polling frequency: rfc5905 suggests this ranges in NTPv4 from 4 (16s)
to 17 (36h) (log2 seconds), however observation suggests the actual displayed
value is seconds for a much smaller range of 64 (26) to 1024 (210) seconds;
- reach – An 8-bit left-shift shift register value recording polls (bit set =
successful, bit reset = fail) displayed in octal;
- delay – Round trip communication delay to the remote peer or server (milliseconds);
- offset – Mean offset (phase) in the times reported between this local host and
the remote peer or server (RMS, milliseconds);
- jitter – Mean deviation (jitter) in the time reported for that remote peer or
server (RMS of difference of multiple time samples, milliseconds);

### Configuration:

```toml
# Get standard NTP query metrics, requires ntpq executable
[[inputs.ntpq]]
## If false, set the -n ntpq flag. Can reduce metric gather times.
dns_lookup = true
```

### Measurements & Fields:

- ntpq
- delay (float, milliseconds)
- jitter (float, milliseconds)
- offset (float, milliseconds)
- poll (int, seconds)
- reach (int)
- when (int, seconds)

### Tags:

- All measurements have the following tags:
- refid
- remote
- type
- stratum

### Example Output:

```
$ telegraf -config ~/ws/telegraf.conf -input-filter ntpq -test
* Plugin: ntpq, Collection 1
> ntpq,refid=.GPSs.,remote=*time.apple.com,stratum=1,type=u delay=91.797,jitter=3.735,offset=12.841,poll=64i,reach=377i,when=35i 1457960478909556134
```
192 changes: 192 additions & 0 deletions plugins/inputs/ntpq/ntpq.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// +build !windows

package ntpq

import (
"bufio"
"bytes"
"log"
"os/exec"
"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 times.
dns_lookup = true
`
}

func (n *NTPQ) Gather(acc telegraf.Accumulator) error {
out, err := n.runQ()
if err != nil {
return err
}

lineCounter := 0
scanner := bufio.NewScanner(bytes.NewReader(out))
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) < 2 {
continue
}

// If lineCounter == 0, then this is the header line
if lineCounter == 0 {
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 {
tags := make(map[string]string)
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 {
if index == -1 {
continue
}

if key == "when" {
when := fields[index]
switch {
case strings.HasSuffix(when, "h"):
m, err := strconv.Atoi(strings.TrimSuffix(fields[index], "h"))
if err != nil {
log.Printf("ERROR ntpq: parsing int: %s", fields[index])
continue
}
// seconds in an hour
mFields[key] = int64(m) * 360
continue
case strings.HasSuffix(when, "d"):
m, err := strconv.Atoi(strings.TrimSuffix(fields[index], "d"))
if err != nil {
log.Printf("ERROR ntpq: parsing int: %s", fields[index])
continue
}
// seconds in a day
mFields[key] = int64(m) * 86400
continue
}
}

m, err := strconv.Atoi(fields[index])
if err != nil {
log.Printf("ERROR ntpq: parsing int: %s", fields[index])
continue
}
mFields[key] = int64(m)
}

// get float metrics from output
for key, index := range floatI {
if index == -1 {
continue
}

m, err := strconv.ParseFloat(fields[index], 64)
if err != nil {
log.Printf("ERROR ntpq: parsing float: %s", fields[index])
}
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
})
}
Loading

0 comments on commit 4927e8e

Please sign in to comment.