Skip to content

Commit

Permalink
Add unbound input plugin (#3434)
Browse files Browse the repository at this point in the history
  • Loading branch information
aromeyer authored and danielnelson committed Nov 20, 2017
1 parent 51ec55f commit f89c774
Show file tree
Hide file tree
Showing 6 changed files with 493 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ configuration options.
* [teamspeak](./plugins/inputs/teamspeak)
* [tomcat](./plugins/inputs/tomcat)
* [twemproxy](./plugins/inputs/twemproxy)
* [unbound](./plugins/input/unbound)
* [varnish](./plugins/inputs/varnish)
* [zfs](./plugins/inputs/zfs)
* [zookeeper](./plugins/inputs/zookeeper)
Expand Down
13 changes: 13 additions & 0 deletions etc/telegraf.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2887,6 +2887,19 @@
# # socket_listener plugin
# # see https://github.com/influxdata/telegraf/tree/master/plugins/inputs/socket_listener

# # A plugin to collect stats from Unbound - a validating, recursive, and caching DNS resolver
# [[inputs.unbound]]
# ## If running as a restricted user you can prepend sudo for additional access:
# #use_sudo = false
#
# ## The default location of the unbound-control binary can be overridden with:
# binary = "/usr/sbin/unbound-control"
#
# # The default timeout of 1s can be overriden with:
# #timeout = "1s"
#
# # Use the builtin fielddrop/fieldpass telegraf filters in order to keep/remove specific fields
# fieldpass = ["total_*", "num_*","time_up", "mem_*"]

# # A Webhooks Event collector
# [[inputs.webhooks]]
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 @@ -92,6 +92,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/trig"
_ "github.com/influxdata/telegraf/plugins/inputs/twemproxy"
_ "github.com/influxdata/telegraf/plugins/inputs/udp_listener"
_ "github.com/influxdata/telegraf/plugins/inputs/unbound"
_ "github.com/influxdata/telegraf/plugins/inputs/varnish"
_ "github.com/influxdata/telegraf/plugins/inputs/webhooks"
_ "github.com/influxdata/telegraf/plugins/inputs/win_perf_counters"
Expand Down
135 changes: 135 additions & 0 deletions plugins/inputs/unbound/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Unbound Input Plugin

This plugin gathers stats from [Unbound - a validating, recursive, and caching DNS resolver](https://www.unbound.net/)

### Configuration:

```toml
# A plugin to collect stats from Unbound - a validating, recursive, and caching DNS resolver
[[inputs.unbound]]
## If running as a restricted user you can prepend sudo for additional access:
#use_sudo = false

## The default location of the unbound-control binary can be overridden with:
binary = "/usr/sbin/unbound-control"

## The default timeout of 1s can be overriden with:
#timeout = "1s"

## Use the builtin fielddrop/fieldpass telegraf filters in order to keep only specific fields
fieldpass = ["total_*", "num_*","time_up", "mem_*"]
```

### Measurements & Fields:

This is the full list of stats provided by unbound-control and potentially collected by telegram
depending of your unbound configuration. Histogram related statistics will never be collected,
extended statistics can also be imported ("extended-statistics: yes" in unbound configuration).
In the output, the dots in the unbound-control stat name are replaced by underscores(see
https://www.unbound.net/documentation/unbound-control.html for details).

- unbound
thread0_num_queries
thread0_num_cachehits
thread0_num_cachemiss
thread0_num_prefetch
thread0_num_recursivereplies
thread0_requestlist_avg
thread0_requestlist_max
thread0_requestlist_overwritten
thread0_requestlist_exceeded
thread0_requestlist_current_all
thread0_requestlist_current_user
thread0_recursion_time_avg
thread0_recursion_time_median
total_num_queries
total_num_cachehits
total_num_cachemiss
total_num_prefetch
total_num_recursivereplies
total_requestlist_avg
total_requestlist_max
total_requestlist_overwritten
total_requestlist_exceeded
total_requestlist_current_all
total_requestlist_current_user
total_recursion_time_avg
total_recursion_time_median
time_now
time_up
time_elapsed
mem_total_sbrk
mem_cache_rrset
mem_cache_message
mem_mod_iterator
mem_mod_validator
num_query_type_A
num_query_type_PTR
num_query_type_TXT
num_query_type_AAAA
num_query_type_SRV
num_query_type_ANY
num_query_class_IN
num_query_opcode_QUERY
num_query_tcp
num_query_ipv6
num_query_flags_QR
num_query_flags_AA
num_query_flags_TC
num_query_flags_RD
num_query_flags_RA
num_query_flags_Z
num_query_flags_AD
num_query_flags_CD
num_query_edns_present
num_query_edns_DO
num_answer_rcode_NOERROR
num_answer_rcode_SERVFAIL
num_answer_rcode_NXDOMAIN
num_answer_rcode_nodata
num_answer_secure
num_answer_bogus
num_rrset_bogus
unwanted_queries
unwanted_replies

### Permissions:

It's important to note that this plugin references unbound-control, which may require additional permissions to execute successfully.
Depending on the user/group permissions of the telegraf user executing this plugin, you may need to alter the group membership, set facls, or use sudo.

**Group membership (Recommended)**:
```bash
$ groups telegraf
telegraf : telegraf

$ usermod -a -G unbound telegraf

$ groups telegraf
telegraf : telegraf unbound
```

**Sudo privileges**:
If you use this method, you will need the following in your telegraf config:
```toml
[[inputs.unbound]]
use_sudo = true
```

You will also need to update your sudoers file:
```bash
$ visudo
# Add the following line:
telegraf ALL=(ALL) NOPASSWD: /usr/sbin/unbound-control
```

Please use the solution you see as most appropriate.

### Example Output:

```
telegraf --config etc/telegraf.conf --input-filter unbound --test
* Plugin: inputs.unbound, Collection 1
> unbound,host=localhost total_num_cachehits=0,total_num_prefetch=0,total_requestlist_avg=0,total_requestlist_max=0,total_recursion_time_median=0,total_num_queries=0,total_requestlist_overwritten=0,total_requestlist_current_all=0,time_up=159185.583967,total_num_recursivereplies=0,total_requestlist_exceeded=0,total_requestlist_current_user=0,total_recursion_time_avg=0,total_tcpusage=0,total_num_cachemiss=0 1510130793000000000
```
137 changes: 137 additions & 0 deletions plugins/inputs/unbound/unbound.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package unbound

import (
"bufio"
"bytes"
"fmt"
"os/exec"
"strconv"
"strings"
"time"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/filter"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs"
)

type runner func(cmdName string, Timeout internal.Duration, UseSudo bool) (*bytes.Buffer, error)

// Unbound is used to store configuration values
type Unbound struct {
Binary string
Timeout internal.Duration
UseSudo bool

filter filter.Filter
run runner
}

var defaultBinary = "/usr/sbin/unbound-control"
var defaultTimeout = internal.Duration{Duration: time.Second}

var sampleConfig = `
## If running as a restricted user you can prepend sudo for additional access:
#use_sudo = false
## The default location of the unbound-control binary can be overridden with:
binary = "/usr/sbin/unbound-control"
## The default timeout of 1s can be overriden with:
timeout = "1s"
## Use the builtin fielddrop/fieldpass telegraf filters in order to keep/remove specific fields
fieldpass = ["total_*", "num_*","time_up", "mem_*"]
`

func (s *Unbound) Description() string {
return "A plugin to collect stats from Unbound - a validating, recursive, and caching DNS resolver "
}

// SampleConfig displays configuration instructions
func (s *Unbound) SampleConfig() string {
return sampleConfig
}

// Shell out to unbound_stat and return the output
func unboundRunner(cmdName string, Timeout internal.Duration, UseSudo bool) (*bytes.Buffer, error) {
cmdArgs := []string{"stats_noreset"}

cmd := exec.Command(cmdName, cmdArgs...)

if UseSudo {
cmdArgs = append([]string{cmdName}, cmdArgs...)
cmd = exec.Command("sudo", cmdArgs...)
}

var out bytes.Buffer
cmd.Stdout = &out
err := internal.RunTimeout(cmd, Timeout.Duration)
if err != nil {
return &out, fmt.Errorf("error running unbound-control: %s", err)
}

return &out, nil
}

// Gather collects stats from unbound-control and adds them to the Accumulator
//
// All the dots in stat name will replaced by underscores. Histogram statistics will not be collected.
func (s *Unbound) Gather(acc telegraf.Accumulator) error {

// Always exclude histrogram statistics
stat_excluded := []string{"histogram.*"}
filter_excluded, err := filter.Compile(stat_excluded)
if err != nil {
return err
}

out, err := s.run(s.Binary, s.Timeout, s.UseSudo)
if err != nil {
return fmt.Errorf("error gathering metrics: %s", err)
}

// Process values
fields := make(map[string]interface{})
scanner := bufio.NewScanner(out)
for scanner.Scan() {

cols := strings.Split(scanner.Text(), "=")

// Check split correctness
if len(cols) != 2 {
continue
}

stat := cols[0]
value := cols[1]

// Filter value
if filter_excluded.Match(stat) {
continue
}

field := strings.Replace(stat, ".", "_", -1)

fields[field], err = strconv.ParseFloat(value, 64)
if err != nil {
acc.AddError(fmt.Errorf("Expected a numerical value for %s = %v\n",
stat, value))
}
}

acc.AddFields("unbound", fields, nil)

return nil
}

func init() {
inputs.Add("unbound", func() telegraf.Input {
return &Unbound{
run: unboundRunner,
Binary: defaultBinary,
Timeout: defaultTimeout,
UseSudo: false,
}
})
}
Loading

0 comments on commit f89c774

Please sign in to comment.