Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unbound input plugin #3434

Merged
merged 10 commits into from
Nov 20, 2017
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions etc/telegraf.conf
Original file line number Diff line number Diff line change
Expand Up @@ -2865,6 +2865,23 @@
# # 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"
#
# ## By default, telegraf gather stats for 4 metric points.
# ## Setting stats will override the defaults shown below.
# ## Glob matching can be used, ie, stats = ["total.*"]
# ## stats may also be set to ["*"], which will collect all stats
# ## except histogram.* statistics that will never be collected.
# stats = ["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 @@ -91,6 +91,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
139 changes: 139 additions & 0 deletions plugins/inputs/unbound/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# 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"

## By default, telegraf gather stats for 4 metric points.
## Setting stats will override the defaults shown below.
## Glob matching can be used, ie, stats = ["total.*"]
## stats may also be set to ["*"], which will collect all stats
## except histogram.* statistics that will never be collected.
stats = ["total.*", "num.*","time.up", "mem.*"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should remove this filter, users can simply use the builtin fielddrop/fieldpass filters to get the same behavior.

```

### 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

```
161 changes: 161 additions & 0 deletions plugins/inputs/unbound/unbound.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// +build !windows
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove this build flag, and also in the test file.


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 {
Stats []string
Binary string
Timeout internal.Duration
UseSudo bool

filter filter.Filter
run runner
}

var defaultStats = []string{"total.*", "num.*", "time.up", "mem.*"}
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"

## By default, telegraf gather stats for 4 metric points.
## Setting stats will override the defaults shown below.
## Glob matching can be used, ie, stats = ["total.*"]
## stats may also be set to ["*"], which will collect all stats
## except histogram.* statistics that will never be collected.
stats = ["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 the configured 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 {
if s.filter == nil {
var err error
if len(s.Stats) == 0 {
s.filter, err = filter.Compile(defaultStats)
} else {
// change "all" -> "*":
if s.Stats[0] == "all" {
s.Stats[0] = "*"
}
s.filter, err = filter.Compile(s.Stats)
}
if err != nil {
return err
}
}
// 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]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could panic if somehow there are not two fields, make sure to guard against this.


// Filter value
if s.filter != nil && (!s.filter.Match(stat) || 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,
Stats: defaultStats,
Binary: defaultBinary,
Timeout: defaultTimeout,
UseSudo: false,
}
})
}
Loading