From f89c774226aad29b4e3255a061fa8d63c0ea70f5 Mon Sep 17 00:00:00 2001 From: aromeyer Date: Mon, 20 Nov 2017 23:32:06 +0100 Subject: [PATCH] Add unbound input plugin (#3434) --- README.md | 1 + etc/telegraf.conf | 13 ++ plugins/inputs/all/all.go | 1 + plugins/inputs/unbound/README.md | 135 ++++++++++++++++ plugins/inputs/unbound/unbound.go | 137 ++++++++++++++++ plugins/inputs/unbound/unbound_test.go | 206 +++++++++++++++++++++++++ 6 files changed, 493 insertions(+) create mode 100644 plugins/inputs/unbound/README.md create mode 100644 plugins/inputs/unbound/unbound.go create mode 100644 plugins/inputs/unbound/unbound_test.go diff --git a/README.md b/README.md index f527e6258..ad02091da 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/etc/telegraf.conf b/etc/telegraf.conf index 3ab0afd51..e805bb467 100644 --- a/etc/telegraf.conf +++ b/etc/telegraf.conf @@ -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]] diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index 421fd113c..0adbb8abb 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -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" diff --git a/plugins/inputs/unbound/README.md b/plugins/inputs/unbound/README.md new file mode 100644 index 000000000..b3aff8078 --- /dev/null +++ b/plugins/inputs/unbound/README.md @@ -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 + +``` diff --git a/plugins/inputs/unbound/unbound.go b/plugins/inputs/unbound/unbound.go new file mode 100644 index 000000000..c3e7a4fef --- /dev/null +++ b/plugins/inputs/unbound/unbound.go @@ -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, + } + }) +} diff --git a/plugins/inputs/unbound/unbound_test.go b/plugins/inputs/unbound/unbound_test.go new file mode 100644 index 000000000..b8e821089 --- /dev/null +++ b/plugins/inputs/unbound/unbound_test.go @@ -0,0 +1,206 @@ +package unbound + +import ( + "bytes" + "testing" + "time" + + "github.com/influxdata/telegraf/internal" + "github.com/influxdata/telegraf/testutil" + "github.com/stretchr/testify/assert" +) + +var TestTimeout = internal.Duration{Duration: time.Second} + +func UnboundControl(output string, Timeout internal.Duration, useSudo bool) func(string, internal.Duration, bool) (*bytes.Buffer, error) { + return func(string, internal.Duration, bool) (*bytes.Buffer, error) { + return bytes.NewBuffer([]byte(output)), nil + } +} + +func TestParseFullOutput(t *testing.T) { + acc := &testutil.Accumulator{} + v := &Unbound{ + run: UnboundControl(fullOutput, TestTimeout, true), + } + err := v.Gather(acc) + + assert.NoError(t, err) + + assert.True(t, acc.HasMeasurement("unbound")) + + assert.Len(t, acc.Metrics, 1) + assert.Equal(t, acc.NFields(), 63) + + acc.AssertContainsFields(t, "unbound", parsedFullOutput) +} + +var parsedFullOutput = map[string]interface{}{ + "thread0_num_queries": float64(11907596), + "thread0_num_cachehits": float64(11489288), + "thread0_num_cachemiss": float64(418308), + "thread0_num_prefetch": float64(0), + "thread0_num_recursivereplies": float64(418308), + "thread0_requestlist_avg": float64(0.400229), + "thread0_requestlist_max": float64(11), + "thread0_requestlist_overwritten": float64(0), + "thread0_requestlist_exceeded": float64(0), + "thread0_requestlist_current_all": float64(0), + "thread0_requestlist_current_user": float64(0), + "thread0_recursion_time_avg": float64(0.015020), + "thread0_recursion_time_median": float64(0.00292343), + "total_num_queries": float64(11907596), + "total_num_cachehits": float64(11489288), + "total_num_cachemiss": float64(418308), + "total_num_prefetch": float64(0), + "total_num_recursivereplies": float64(418308), + "total_requestlist_avg": float64(0.400229), + "total_requestlist_max": float64(11), + "total_requestlist_overwritten": float64(0), + "total_requestlist_exceeded": float64(0), + "total_requestlist_current_all": float64(0), + "total_requestlist_current_user": float64(0), + "total_recursion_time_avg": float64(0.015020), + "total_recursion_time_median": float64(0.00292343), + "time_now": float64(1509968734.735180), + "time_up": float64(1472897.672099), + "time_elapsed": float64(1472897.672099), + "mem_total_sbrk": float64(7462912), + "mem_cache_rrset": float64(285056), + "mem_cache_message": float64(320000), + "mem_mod_iterator": float64(16532), + "mem_mod_validator": float64(112097), + "num_query_type_A": float64(7062688), + "num_query_type_PTR": float64(43097), + "num_query_type_TXT": float64(2998), + "num_query_type_AAAA": float64(4499711), + "num_query_type_SRV": float64(5691), + "num_query_type_ANY": float64(293411), + "num_query_class_IN": float64(11907596), + "num_query_opcode_QUERY": float64(11907596), + "num_query_tcp": float64(293411), + "num_query_ipv6": float64(0), + "num_query_flags_QR": float64(0), + "num_query_flags_AA": float64(0), + "num_query_flags_TC": float64(0), + "num_query_flags_RD": float64(11907596), + "num_query_flags_RA": float64(0), + "num_query_flags_Z": float64(0), + "num_query_flags_AD": float64(1), + "num_query_flags_CD": float64(0), + "num_query_edns_present": float64(6202), + "num_query_edns_DO": float64(6201), + "num_answer_rcode_NOERROR": float64(11857463), + "num_answer_rcode_SERVFAIL": float64(17), + "num_answer_rcode_NXDOMAIN": float64(50116), + "num_answer_rcode_nodata": float64(3914360), + "num_answer_secure": float64(44289), + "num_answer_bogus": float64(1), + "num_rrset_bogus": float64(0), + "unwanted_queries": float64(0), + "unwanted_replies": float64(0), +} + +var fullOutput = `thread0.num.queries=11907596 +thread0.num.cachehits=11489288 +thread0.num.cachemiss=418308 +thread0.num.prefetch=0 +thread0.num.recursivereplies=418308 +thread0.requestlist.avg=0.400229 +thread0.requestlist.max=11 +thread0.requestlist.overwritten=0 +thread0.requestlist.exceeded=0 +thread0.requestlist.current.all=0 +thread0.requestlist.current.user=0 +thread0.recursion.time.avg=0.015020 +thread0.recursion.time.median=0.00292343 +total.num.queries=11907596 +total.num.cachehits=11489288 +total.num.cachemiss=418308 +total.num.prefetch=0 +total.num.recursivereplies=418308 +total.requestlist.avg=0.400229 +total.requestlist.max=11 +total.requestlist.overwritten=0 +total.requestlist.exceeded=0 +total.requestlist.current.all=0 +total.requestlist.current.user=0 +total.recursion.time.avg=0.015020 +total.recursion.time.median=0.00292343 +time.now=1509968734.735180 +time.up=1472897.672099 +time.elapsed=1472897.672099 +mem.total.sbrk=7462912 +mem.cache.rrset=285056 +mem.cache.message=320000 +mem.mod.iterator=16532 +mem.mod.validator=112097 +histogram.000000.000000.to.000000.000001=20 +histogram.000000.000001.to.000000.000002=5 +histogram.000000.000002.to.000000.000004=13 +histogram.000000.000004.to.000000.000008=18 +histogram.000000.000008.to.000000.000016=67 +histogram.000000.000016.to.000000.000032=94 +histogram.000000.000032.to.000000.000064=113 +histogram.000000.000064.to.000000.000128=190 +histogram.000000.000128.to.000000.000256=369 +histogram.000000.000256.to.000000.000512=1034 +histogram.000000.000512.to.000000.001024=5503 +histogram.000000.001024.to.000000.002048=155724 +histogram.000000.002048.to.000000.004096=107623 +histogram.000000.004096.to.000000.008192=17739 +histogram.000000.008192.to.000000.016384=4177 +histogram.000000.016384.to.000000.032768=82021 +histogram.000000.032768.to.000000.065536=33772 +histogram.000000.065536.to.000000.131072=7159 +histogram.000000.131072.to.000000.262144=1109 +histogram.000000.262144.to.000000.524288=295 +histogram.000000.524288.to.000001.000000=890 +histogram.000001.000000.to.000002.000000=136 +histogram.000002.000000.to.000004.000000=233 +histogram.000004.000000.to.000008.000000=2 +histogram.000008.000000.to.000016.000000=0 +histogram.000016.000000.to.000032.000000=2 +histogram.000032.000000.to.000064.000000=0 +histogram.000064.000000.to.000128.000000=0 +histogram.000128.000000.to.000256.000000=0 +histogram.000256.000000.to.000512.000000=0 +histogram.000512.000000.to.001024.000000=0 +histogram.001024.000000.to.002048.000000=0 +histogram.002048.000000.to.004096.000000=0 +histogram.004096.000000.to.008192.000000=0 +histogram.008192.000000.to.016384.000000=0 +histogram.016384.000000.to.032768.000000=0 +histogram.032768.000000.to.065536.000000=0 +histogram.065536.000000.to.131072.000000=0 +histogram.131072.000000.to.262144.000000=0 +histogram.262144.000000.to.524288.000000=0 +num.query.type.A=7062688 +num.query.type.PTR=43097 +num.query.type.TXT=2998 +num.query.type.AAAA=4499711 +num.query.type.SRV=5691 +num.query.type.ANY=293411 +num.query.class.IN=11907596 +num.query.opcode.QUERY=11907596 +num.query.tcp=293411 +num.query.ipv6=0 +num.query.flags.QR=0 +num.query.flags.AA=0 +num.query.flags.TC=0 +num.query.flags.RD=11907596 +num.query.flags.RA=0 +num.query.flags.Z=0 +num.query.flags.AD=1 +num.query.flags.CD=0 +num.query.edns.present=6202 +num.query.edns.DO=6201 +num.answer.rcode.NOERROR=11857463 +num.answer.rcode.SERVFAIL=17 +num.answer.rcode.NXDOMAIN=50116 +num.answer.rcode.nodata=3914360 +num.answer.secure=44289 +num.answer.bogus=1 +num.rrset.bogus=0 +unwanted.queries=0 +unwanted.replies=0`