summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/modules/unbound
diff options
context:
space:
mode:
Diffstat (limited to '')
l---------src/go/collectors/go.d.plugin/modules/unbound/README.md1
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/charts.go527
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/collect.go209
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config/config.go78
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config/config_test.go172
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config/parse.go165
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config/parse_test.go93
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config/testdata/infinite_rec.conf85
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config/testdata/non_existent_glob_include.conf85
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config/testdata/non_existent_include.conf85
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_glob.conf82
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_glob2.conf80
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_glob3.conf81
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include.conf82
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include2.conf81
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include3.conf81
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include_toplevel.conf82
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include_toplevel2.conf81
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include_toplevel3.conf81
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/config_schema.json113
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/init.go106
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/integrations/unbound.md270
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/metadata.yaml431
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/testdata/config.json12
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/testdata/config.yaml10
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/common.txt66
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/extended.txt162
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/cumulative/extended1.txt162
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/cumulative/extended2.txt162
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/cumulative/extended3.txt163
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/reset/extended1.txt163
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/reset/extended2.txt156
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/reset/extended3.txt163
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/testdata/unbound.conf85
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/testdata/unbound_disabled.conf85
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/testdata/unbound_empty.conf85
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/unbound.go126
-rw-r--r--src/go/collectors/go.d.plugin/modules/unbound/unbound_test.go1288
38 files changed, 6039 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/README.md b/src/go/collectors/go.d.plugin/modules/unbound/README.md
new file mode 120000
index 000000000..5b0f42b04
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/README.md
@@ -0,0 +1 @@
+integrations/unbound.md \ No newline at end of file
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/charts.go b/src/go/collectors/go.d.plugin/modules/unbound/charts.go
new file mode 100644
index 000000000..0f0607664
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/charts.go
@@ -0,0 +1,527 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package unbound
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
+)
+
+type (
+ // Charts is an alias for module.Charts
+ Charts = module.Charts
+ // Chart is an alias for module.Charts
+ Chart = module.Chart
+ // Dims is an alias for module.Dims
+ Dims = module.Dims
+ // Dim is an alias for module.Dim
+ Dim = module.Dim
+)
+
+const (
+ prioQueries = module.Priority + iota
+ prioIPRateLimitedQueries
+ prioQueryType
+ prioQueryClass
+ prioQueryOpCode
+ prioQueryFlag
+ prioDNSCryptQueries
+
+ prioRecurReplies
+ prioReplyRCode
+
+ prioRecurTime
+
+ prioCache
+ prioCachePercentage
+ prioCachePrefetch
+ prioCacheExpired
+ prioZeroTTL
+ prioCacheCount
+
+ prioReqListUsage
+ prioReqListCurUsage
+ prioReqListJostle
+
+ prioTCPUsage
+
+ prioMemCache
+ prioMemMod
+ prioMemStreamWait
+ prioUptime
+
+ prioThread
+)
+
+func charts(cumulative bool) *Charts {
+ return &Charts{
+ makeIncrIf(queriesChart.Copy(), cumulative),
+ makeIncrIf(ipRateLimitedQueriesChart.Copy(), cumulative),
+ makeIncrIf(cacheChart.Copy(), cumulative),
+ makePercOfIncrIf(cachePercentageChart.Copy(), cumulative),
+ makeIncrIf(prefetchChart.Copy(), cumulative),
+ makeIncrIf(expiredChart.Copy(), cumulative),
+ makeIncrIf(zeroTTLChart.Copy(), cumulative),
+ makeIncrIf(dnsCryptChart.Copy(), cumulative),
+ makeIncrIf(recurRepliesChart.Copy(), cumulative),
+ recurTimeChart.Copy(),
+ reqListUsageChart.Copy(),
+ reqListCurUsageChart.Copy(),
+ makeIncrIf(reqListJostleChart.Copy(), cumulative),
+ tcpUsageChart.Copy(),
+ uptimeChart.Copy(),
+ }
+}
+
+func extendedCharts(cumulative bool) *Charts {
+ return &Charts{
+ memCacheChart.Copy(),
+ memModChart.Copy(),
+ memStreamWaitChart.Copy(),
+ cacheCountChart.Copy(),
+ makeIncrIf(queryTypeChart.Copy(), cumulative),
+ makeIncrIf(queryClassChart.Copy(), cumulative),
+ makeIncrIf(queryOpCodeChart.Copy(), cumulative),
+ makeIncrIf(queryFlagChart.Copy(), cumulative),
+ makeIncrIf(answerRCodeChart.Copy(), cumulative),
+ }
+}
+
+func threadCharts(thread string, cumulative bool) *Charts {
+ charts := charts(cumulative)
+ _ = charts.Remove(uptimeChart.ID)
+
+ for i, chart := range *charts {
+ convertTotalChartToThread(chart, thread, prioThread+i)
+ }
+ return charts
+}
+
+func convertTotalChartToThread(chart *Chart, thread string, priority int) {
+ chart.ID = fmt.Sprintf("%s_%s", thread, chart.ID)
+ chart.Title = fmt.Sprintf("%s %s",
+ cases.Title(language.English, cases.Compact).String(thread),
+ chart.Title,
+ )
+ chart.Fam = thread + "_stats"
+ chart.Ctx = "thread_" + chart.Ctx
+ chart.Priority = priority
+ for _, dim := range chart.Dims {
+ dim.ID = strings.Replace(dim.ID, "total", thread, 1)
+ }
+}
+
+// Common stats charts
+// see https://nlnetlabs.nl/documentation/unbound/unbound-control for the stats provided by unbound-control
+var (
+ queriesChart = Chart{
+ ID: "queries",
+ Title: "Received Queries",
+ Units: "queries",
+ Fam: "queries",
+ Ctx: "unbound.queries",
+ Priority: prioQueries,
+ Dims: Dims{
+ {ID: "total.num.queries", Name: "queries"},
+ },
+ }
+ ipRateLimitedQueriesChart = Chart{
+ ID: "queries_ip_ratelimited",
+ Title: "Rate Limited Queries",
+ Units: "queries",
+ Fam: "queries",
+ Ctx: "unbound.queries_ip_ratelimited",
+ Priority: prioIPRateLimitedQueries,
+ Dims: Dims{
+ {ID: "total.num.queries_ip_ratelimited", Name: "ratelimited"},
+ },
+ }
+ // ifdef USE_DNSCRYPT
+ dnsCryptChart = Chart{
+ ID: "dnscrypt_queries",
+ Title: "DNSCrypt Queries",
+ Units: "queries",
+ Fam: "queries",
+ Ctx: "unbound.dnscrypt_queries",
+ Priority: prioDNSCryptQueries,
+ Dims: Dims{
+ {ID: "total.num.dnscrypt.crypted", Name: "crypted"},
+ {ID: "total.num.dnscrypt.cert", Name: "cert"},
+ {ID: "total.num.dnscrypt.cleartext", Name: "cleartext"},
+ {ID: "total.num.dnscrypt.malformed", Name: "malformed"},
+ },
+ }
+ cacheChart = Chart{
+ ID: "cache",
+ Title: "Cache Statistics",
+ Units: "events",
+ Fam: "cache",
+ Ctx: "unbound.cache",
+ Type: module.Stacked,
+ Priority: prioCache,
+ Dims: Dims{
+ {ID: "total.num.cachehits", Name: "hits"},
+ {ID: "total.num.cachemiss", Name: "miss"},
+ },
+ }
+ cachePercentageChart = Chart{
+ ID: "cache_percentage",
+ Title: "Cache Statistics Percentage",
+ Units: "percentage",
+ Fam: "cache",
+ Ctx: "unbound.cache_percentage",
+ Type: module.Stacked,
+ Priority: prioCachePercentage,
+ Dims: Dims{
+ {ID: "total.num.cachehits", Name: "hits", Algo: module.PercentOfAbsolute},
+ {ID: "total.num.cachemiss", Name: "miss", Algo: module.PercentOfAbsolute},
+ },
+ }
+ prefetchChart = Chart{
+ ID: "cache_prefetch",
+ Title: "Cache Prefetches",
+ Units: "prefetches",
+ Fam: "cache",
+ Ctx: "unbound.prefetch",
+ Priority: prioCachePrefetch,
+ Dims: Dims{
+ {ID: "total.num.prefetch", Name: "prefetches"},
+ },
+ }
+ expiredChart = Chart{
+ ID: "cache_expired",
+ Title: "Replies Served From Expired Cache",
+ Units: "replies",
+ Fam: "cache",
+ Ctx: "unbound.expired",
+ Priority: prioCacheExpired,
+ Dims: Dims{
+ {ID: "total.num.expired", Name: "expired"},
+ },
+ }
+ zeroTTLChart = Chart{
+ ID: "zero_ttl_replies",
+ Title: "Replies Served From Expired Cache",
+ Units: "replies",
+ Fam: "cache",
+ Ctx: "unbound.zero_ttl_replies",
+ Priority: prioZeroTTL,
+ Dims: Dims{
+ {ID: "total.num.zero_ttl", Name: "zero_ttl"},
+ },
+ }
+ recurRepliesChart = Chart{
+ ID: "recursive_replies",
+ Title: "Replies That Needed Recursive Processing",
+ Units: "replies",
+ Fam: "replies",
+ Ctx: "unbound.recursive_replies",
+ Priority: prioRecurReplies,
+ Dims: Dims{
+ {ID: "total.num.recursivereplies", Name: "recursive"},
+ },
+ }
+ recurTimeChart = Chart{
+ ID: "recursion_time",
+ Title: "Time Spent On Recursive Processing",
+ Units: "milliseconds",
+ Fam: "recursion timings",
+ Ctx: "unbound.recursion_time",
+ Priority: prioRecurTime,
+ Dims: Dims{
+ {ID: "total.recursion.time.avg", Name: "avg"},
+ {ID: "total.recursion.time.median", Name: "median"},
+ },
+ }
+ reqListUsageChart = Chart{
+ ID: "request_list_usage",
+ Title: "Request List Usage",
+ Units: "queries",
+ Fam: "request list",
+ Ctx: "unbound.request_list_usage",
+ Priority: prioReqListUsage,
+ Dims: Dims{
+ {ID: "total.requestlist.avg", Name: "avg", Div: 1000},
+ {ID: "total.requestlist.max", Name: "max"}, // all time max in cumulative mode, never resets
+ },
+ }
+ reqListCurUsageChart = Chart{
+ ID: "current_request_list_usage",
+ Title: "Current Request List Usage",
+ Units: "queries",
+ Fam: "request list",
+ Ctx: "unbound.current_request_list_usage",
+ Type: module.Area,
+ Priority: prioReqListCurUsage,
+ Dims: Dims{
+ {ID: "total.requestlist.current.all", Name: "all"},
+ {ID: "total.requestlist.current.user", Name: "user"},
+ },
+ }
+ reqListJostleChart = Chart{
+ ID: "request_list_jostle_list",
+ Title: "Request List Jostle List Events",
+ Units: "queries",
+ Fam: "request list",
+ Ctx: "unbound.request_list_jostle_list",
+ Priority: prioReqListJostle,
+ Dims: Dims{
+ {ID: "total.requestlist.overwritten", Name: "overwritten"},
+ {ID: "total.requestlist.exceeded", Name: "dropped"},
+ },
+ }
+ tcpUsageChart = Chart{
+ ID: "tcpusage",
+ Title: "TCP Handler Buffers",
+ Units: "buffers",
+ Fam: "tcp buffers",
+ Ctx: "unbound.tcpusage",
+ Priority: prioTCPUsage,
+ Dims: Dims{
+ {ID: "total.tcpusage", Name: "usage"},
+ },
+ }
+ uptimeChart = Chart{
+ ID: "uptime",
+ Title: "Uptime",
+ Units: "seconds",
+ Fam: "uptime",
+ Ctx: "unbound.uptime",
+ Priority: prioUptime,
+ Dims: Dims{
+ {ID: "time.up", Name: "time"},
+ },
+ }
+)
+
+// Extended stats charts
+var (
+ // TODO: do not add dnscrypt stuff by default?
+ memCacheChart = Chart{
+ ID: "cache_memory",
+ Title: "Cache Memory",
+ Units: "KB",
+ Fam: "mem",
+ Ctx: "unbound.cache_memory",
+ Type: module.Stacked,
+ Priority: prioMemCache,
+ Dims: Dims{
+ {ID: "mem.cache.message", Name: "message", Div: 1024},
+ {ID: "mem.cache.rrset", Name: "rrset", Div: 1024},
+ {ID: "mem.cache.dnscrypt_nonce", Name: "dnscrypt_nonce", Div: 1024}, // ifdef USE_DNSCRYPT
+ {ID: "mem.cache.dnscrypt_shared_secret", Name: "dnscrypt_shared_secret", Div: 1024}, // ifdef USE_DNSCRYPT
+ },
+ }
+ // TODO: do not add subnet and ipsecmod stuff by default?
+ memModChart = Chart{
+ ID: "mod_memory",
+ Title: "Module Memory",
+ Units: "KB",
+ Fam: "mem",
+ Ctx: "unbound.mod_memory",
+ Type: module.Stacked,
+ Priority: prioMemMod,
+ Dims: Dims{
+ {ID: "mem.mod.iterator", Name: "iterator", Div: 1024},
+ {ID: "mem.mod.respip", Name: "respip", Div: 1024},
+ {ID: "mem.mod.validator", Name: "validator", Div: 1024},
+ {ID: "mem.mod.subnet", Name: "subnet", Div: 1024}, // ifdef CLIENT_SUBNET
+ {ID: "mem.mod.ipsecmod", Name: "ipsec", Div: 1024}, // ifdef USE_IPSECMOD
+ },
+ }
+ memStreamWaitChart = Chart{
+ ID: "mem_stream_wait",
+ Title: "TCP and TLS Stream Waif Buffer Memory",
+ Units: "KB",
+ Fam: "mem",
+ Ctx: "unbound.mem_streamwait",
+ Priority: prioMemStreamWait,
+ Dims: Dims{
+ {ID: "mem.streamwait", Name: "streamwait", Div: 1024},
+ },
+ }
+ // NOTE: same family as for cacheChart
+ cacheCountChart = Chart{
+ ID: "cache_count",
+ Title: "Cache Items Count",
+ Units: "items",
+ Fam: "cache",
+ Ctx: "unbound.cache_count",
+ Type: module.Stacked,
+ Priority: prioCacheCount,
+ Dims: Dims{
+ {ID: "infra.cache.count", Name: "infra"},
+ {ID: "key.cache.count", Name: "key"},
+ {ID: "msg.cache.count", Name: "msg"},
+ {ID: "rrset.cache.count", Name: "rrset"},
+ {ID: "dnscrypt_nonce.cache.count", Name: "dnscrypt_nonce"},
+ {ID: "dnscrypt_shared_secret.cache.count", Name: "shared_secret"},
+ },
+ }
+ queryTypeChart = Chart{
+ ID: "queries_by_type",
+ Title: "Queries By Type",
+ Units: "queries",
+ Fam: "queries",
+ Ctx: "unbound.type_queries",
+ Type: module.Stacked,
+ Priority: prioQueryType,
+ }
+ queryClassChart = Chart{
+ ID: "queries_by_class",
+ Title: "Queries By Class",
+ Units: "queries",
+ Fam: "queries",
+ Ctx: "unbound.class_queries",
+ Type: module.Stacked,
+ Priority: prioQueryClass,
+ }
+ queryOpCodeChart = Chart{
+ ID: "queries_by_opcode",
+ Title: "Queries By OpCode",
+ Units: "queries",
+ Fam: "queries",
+ Ctx: "unbound.opcode_queries",
+ Type: module.Stacked,
+ Priority: prioQueryOpCode,
+ }
+ queryFlagChart = Chart{
+ ID: "queries_by_flag",
+ Title: "Queries By Flag",
+ Units: "queries",
+ Fam: "queries",
+ Ctx: "unbound.flag_queries",
+ Type: module.Stacked,
+ Priority: prioQueryFlag,
+ Dims: Dims{
+ {ID: "num.query.flags.QR", Name: "QR"},
+ {ID: "num.query.flags.AA", Name: "AA"},
+ {ID: "num.query.flags.TC", Name: "TC"},
+ {ID: "num.query.flags.RD", Name: "RD"},
+ {ID: "num.query.flags.RA", Name: "RA"},
+ {ID: "num.query.flags.Z", Name: "Z"},
+ {ID: "num.query.flags.AD", Name: "AD"},
+ {ID: "num.query.flags.CD", Name: "CD"},
+ },
+ }
+ answerRCodeChart = Chart{
+ ID: "replies_by_rcode",
+ Title: "Replies By RCode",
+ Units: "replies",
+ Fam: "replies",
+ Ctx: "unbound.rcode_answers",
+ Type: module.Stacked,
+ Priority: prioReplyRCode,
+ }
+)
+
+func (u *Unbound) updateCharts() {
+ if len(u.curCache.threads) > 1 {
+ for v := range u.curCache.threads {
+ if !u.cache.threads[v] {
+ u.cache.threads[v] = true
+ u.addThreadCharts(v)
+ }
+ }
+ }
+ // 0-6 rcodes always included
+ if hasExtendedData := len(u.curCache.answerRCode) > 0; !hasExtendedData {
+ return
+ }
+
+ if !u.extChartsCreated {
+ charts := extendedCharts(u.Cumulative)
+ if err := u.Charts().Add(*charts...); err != nil {
+ u.Warningf("add extended charts: %v", err)
+ }
+ u.extChartsCreated = true
+ }
+
+ for v := range u.curCache.queryType {
+ if !u.cache.queryType[v] {
+ u.cache.queryType[v] = true
+ u.addDimToQueryTypeChart(v)
+ }
+ }
+ for v := range u.curCache.queryClass {
+ if !u.cache.queryClass[v] {
+ u.cache.queryClass[v] = true
+ u.addDimToQueryClassChart(v)
+ }
+ }
+ for v := range u.curCache.queryOpCode {
+ if !u.cache.queryOpCode[v] {
+ u.cache.queryOpCode[v] = true
+ u.addDimToQueryOpCodeChart(v)
+ }
+ }
+ for v := range u.curCache.answerRCode {
+ if !u.cache.answerRCode[v] {
+ u.cache.answerRCode[v] = true
+ u.addDimToAnswerRcodeChart(v)
+ }
+ }
+}
+
+func (u *Unbound) addThreadCharts(thread string) {
+ charts := threadCharts(thread, u.Cumulative)
+ if err := u.Charts().Add(*charts...); err != nil {
+ u.Warningf("add '%s' charts: %v", thread, err)
+ }
+}
+
+func (u *Unbound) addDimToQueryTypeChart(typ string) {
+ u.addDimToChart(queryTypeChart.ID, "num.query.type."+typ, typ)
+}
+func (u *Unbound) addDimToQueryClassChart(class string) {
+ u.addDimToChart(queryClassChart.ID, "num.query.class."+class, class)
+}
+func (u *Unbound) addDimToQueryOpCodeChart(opcode string) {
+ u.addDimToChart(queryOpCodeChart.ID, "num.query.opcode."+opcode, opcode)
+}
+func (u *Unbound) addDimToAnswerRcodeChart(rcode string) {
+ u.addDimToChart(answerRCodeChart.ID, "num.answer.rcode."+rcode, rcode)
+}
+
+func (u *Unbound) addDimToChart(chartID, dimID, dimName string) {
+ chart := u.Charts().Get(chartID)
+ if chart == nil {
+ u.Warningf("add '%s' dim: couldn't find '%s' chart", dimID, chartID)
+ return
+ }
+ dim := &Dim{ID: dimID, Name: dimName}
+ if u.Cumulative {
+ dim.Algo = module.Incremental
+ }
+ if err := chart.AddDim(dim); err != nil {
+ u.Warningf("add '%s' dim: %v", dimID, err)
+ return
+ }
+ chart.MarkNotCreated()
+}
+
+func makeIncrIf(chart *Chart, do bool) *Chart {
+ if !do {
+ return chart
+ }
+ chart.Units += "/s"
+ for _, d := range chart.Dims {
+ d.Algo = module.Incremental
+ }
+ return chart
+}
+
+func makePercOfIncrIf(chart *Chart, do bool) *Chart {
+ if !do {
+ return chart
+ }
+ for _, d := range chart.Dims {
+ d.Algo = module.PercentOfIncremental
+ }
+ return chart
+}
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/collect.go b/src/go/collectors/go.d.plugin/modules/unbound/collect.go
new file mode 100644
index 000000000..125f206ae
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/collect.go
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package unbound
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+)
+
+// https://github.com/NLnetLabs/unbound/blob/master/daemon/remote.c (do_stats: print_stats, print_thread_stats, print_mem, print_uptime, print_ext)
+// https://github.com/NLnetLabs/unbound/blob/master/libunbound/unbound.h (structs: ub_server_stats, ub_shm_stat_info)
+// https://docs.datadoghq.com/integrations/unbound/#metrics (stats description)
+// https://docs.menandmice.com/display/MM/Unbound+request-list+demystified (request lists explanation)
+
+func (u *Unbound) collect() (map[string]int64, error) {
+ stats, err := u.scrapeUnboundStats()
+ if err != nil {
+ return nil, err
+ }
+
+ mx := u.collectStats(stats)
+ u.updateCharts()
+ return mx, nil
+}
+
+func (u *Unbound) scrapeUnboundStats() ([]entry, error) {
+ var output []string
+ var command = "UBCT1 stats"
+ if u.Cumulative {
+ command = "UBCT1 stats_noreset"
+ }
+
+ if err := u.client.Connect(); err != nil {
+ return nil, fmt.Errorf("failed to connect: %v", err)
+ }
+ defer func() { _ = u.client.Disconnect() }()
+
+ err := u.client.Command(command+"\n", func(bytes []byte) bool {
+ output = append(output, string(bytes))
+ return true
+ })
+ if err != nil {
+ return nil, fmt.Errorf("send command '%s': %w", command, err)
+ }
+
+ switch len(output) {
+ case 0:
+ return nil, fmt.Errorf("command '%s': empty resopnse", command)
+ case 1:
+ // in case of error the first line of the response is: error <descriptive text possible> \n
+ return nil, fmt.Errorf("command '%s': '%s'", command, output[0])
+ }
+ return parseStatsOutput(output)
+}
+
+func (u *Unbound) collectStats(stats []entry) map[string]int64 {
+ if u.Cumulative {
+ return u.collectCumulativeStats(stats)
+ }
+ return u.collectNonCumulativeStats(stats)
+}
+
+func (u *Unbound) collectCumulativeStats(stats []entry) map[string]int64 {
+ mul := float64(1000)
+ // following stats change only on cachemiss event in cumulative mode
+ // - *.requestlist.avg,
+ // - *.recursion.time.avg
+ // - *.recursion.time.median
+ v := findEntry("total.num.cachemiss", stats)
+ if v == u.prevCacheMiss {
+ // so we need to reset them if there is no such event
+ mul = 0
+ }
+ u.prevCacheMiss = v
+ return u.processStats(stats, mul)
+}
+
+func (u *Unbound) collectNonCumulativeStats(stats []entry) map[string]int64 {
+ mul := float64(1000)
+ mx := u.processStats(stats, mul)
+
+ // see 'static int print_ext(RES* ssl, struct ub_stats_info* s)' in
+ // https://github.com/NLnetLabs/unbound/blob/master/daemon/remote.c
+ // - zero value queries type not included
+ // - zero value queries class not included
+ // - zero value queries opcode not included
+ // - only 0-6 rcodes answers always included, other zero value rcodes not included
+ for k := range u.cache.queryType {
+ if _, ok := u.curCache.queryType[k]; !ok {
+ mx["num.query.type."+k] = 0
+ }
+ }
+ for k := range u.cache.queryClass {
+ if _, ok := u.curCache.queryClass[k]; !ok {
+ mx["num.query.class."+k] = 0
+ }
+ }
+ for k := range u.cache.queryOpCode {
+ if _, ok := u.curCache.queryOpCode[k]; !ok {
+ mx["num.query.opcode."+k] = 0
+ }
+ }
+ for k := range u.cache.answerRCode {
+ if _, ok := u.curCache.answerRCode[k]; !ok {
+ mx["num.answer.rcode."+k] = 0
+ }
+ }
+ return mx
+}
+
+func (u *Unbound) processStats(stats []entry, mul float64) map[string]int64 {
+ u.curCache.clear()
+ mx := make(map[string]int64, len(stats))
+ for _, e := range stats {
+ switch {
+ // *.requestlist.avg, *.recursion.time.avg, *.recursion.time.median
+ case e.hasSuffix(".avg"), e.hasSuffix(".median"):
+ e.value *= mul
+ case e.hasPrefix("thread") && e.hasSuffix("num.queries"):
+ v := extractThread(e.key)
+ u.curCache.threads[v] = true
+ case e.hasPrefix("num.query.type"):
+ v := extractQueryType(e.key)
+ u.curCache.queryType[v] = true
+ case e.hasPrefix("num.query.class"):
+ v := extractQueryClass(e.key)
+ u.curCache.queryClass[v] = true
+ case e.hasPrefix("num.query.opcode"):
+ v := extractQueryOpCode(e.key)
+ u.curCache.queryOpCode[v] = true
+ case e.hasPrefix("num.answer.rcode"):
+ v := extractAnswerRCode(e.key)
+ u.curCache.answerRCode[v] = true
+ }
+ mx[e.key] = int64(e.value)
+ }
+ return mx
+}
+
+func extractThread(key string) string { idx := strings.IndexByte(key, '.'); return key[:idx] }
+func extractQueryType(key string) string { i := len("num.query.type."); return key[i:] }
+func extractQueryClass(key string) string { i := len("num.query.class."); return key[i:] }
+func extractQueryOpCode(key string) string { i := len("num.query.opcode."); return key[i:] }
+func extractAnswerRCode(key string) string { i := len("num.answer.rcode."); return key[i:] }
+
+type entry struct {
+ key string
+ value float64
+}
+
+func (e entry) hasPrefix(prefix string) bool { return strings.HasPrefix(e.key, prefix) }
+func (e entry) hasSuffix(suffix string) bool { return strings.HasSuffix(e.key, suffix) }
+
+func findEntry(key string, entries []entry) float64 {
+ for _, e := range entries {
+ if e.key == key {
+ return e.value
+ }
+ }
+ return -1
+}
+
+func parseStatsOutput(output []string) ([]entry, error) {
+ var es []entry
+ for _, v := range output {
+ e, err := parseStatsLine(v)
+ if err != nil {
+ return nil, err
+ }
+ if e.hasPrefix("histogram") {
+ continue
+ }
+ es = append(es, e)
+ }
+ return es, nil
+}
+
+func parseStatsLine(line string) (entry, error) {
+ // 'stats' output is a list of [key]=[value] lines.
+ parts := strings.Split(line, "=")
+ if len(parts) != 2 {
+ return entry{}, fmt.Errorf("bad line syntax: %s", line)
+ }
+ f, err := strconv.ParseFloat(parts[1], 64)
+ return entry{key: parts[0], value: f}, err
+}
+
+func newCollectCache() collectCache {
+ return collectCache{
+ threads: make(map[string]bool),
+ queryType: make(map[string]bool),
+ queryClass: make(map[string]bool),
+ queryOpCode: make(map[string]bool),
+ answerRCode: make(map[string]bool),
+ }
+}
+
+type collectCache struct {
+ threads map[string]bool
+ queryType map[string]bool
+ queryClass map[string]bool
+ queryOpCode map[string]bool
+ answerRCode map[string]bool
+}
+
+func (c *collectCache) clear() {
+ *c = newCollectCache()
+}
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config/config.go b/src/go/collectors/go.d.plugin/modules/unbound/config/config.go
new file mode 100644
index 000000000..69dc5c219
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config/config.go
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package config
+
+import (
+ "fmt"
+ "strings"
+)
+
+// UnboundConfig represents Unbound configuration file.
+type UnboundConfig struct {
+ cumulative string // statistics-cumulative
+ enable string // control-enable
+ iface string // control-interface
+ port string // control-port
+ useCert string // control-use-cert
+ keyFile string // control-key-file
+ certFile string // control-cert-file
+}
+
+func (c UnboundConfig) String() string {
+ format := strings.Join([]string{
+ "[",
+ `"statistics-cumulative": '%s', `,
+ `"control-enable": '%s', `,
+ `"control-interface": '%s', `,
+ `"control-port": '%s', `,
+ `"control-user-cert": '%s', `,
+ `"control-key-file": '%s', `,
+ `"control-cert-file": '%s'`,
+ "]",
+ }, "")
+ return fmt.Sprintf(format, c.cumulative, c.enable, c.iface, c.port, c.useCert, c.keyFile, c.certFile)
+}
+
+func (c UnboundConfig) Empty() bool { return c == UnboundConfig{} }
+func (c UnboundConfig) Cumulative() (bool, bool) { return c.cumulative == "yes", c.cumulative != "" }
+func (c UnboundConfig) ControlEnabled() (bool, bool) { return c.enable == "yes", c.enable != "" }
+func (c UnboundConfig) ControlInterface() (string, bool) { return c.iface, c.iface != "" }
+func (c UnboundConfig) ControlPort() (string, bool) { return c.port, c.port != "" }
+func (c UnboundConfig) ControlUseCert() (bool, bool) { return c.useCert == "yes", c.useCert != "" }
+func (c UnboundConfig) ControlKeyFile() (string, bool) { return c.keyFile, c.keyFile != "" }
+func (c UnboundConfig) ControlCertFile() (string, bool) { return c.certFile, c.certFile != "" }
+
+func fromOptions(options []option) *UnboundConfig {
+ cfg := &UnboundConfig{}
+ for _, opt := range options {
+ switch opt.name {
+ default:
+ case optInterface:
+ applyControlInterface(cfg, opt.value)
+ case optCumulative:
+ cfg.cumulative = opt.value
+ case optEnable:
+ cfg.enable = opt.value
+ case optPort:
+ cfg.port = opt.value
+ case optUseCert:
+ cfg.useCert = opt.value
+ case optKeyFile:
+ cfg.keyFile = opt.value
+ case optCertFile:
+ cfg.certFile = opt.value
+ }
+ }
+ return cfg
+}
+
+// Unbound doesn't allow to query stats from unix socket when control-interface is enabled on ip interface.
+func applyControlInterface(cfg *UnboundConfig, value string) {
+ if cfg.iface == "" || !isUnixSocket(value) || isUnixSocket(cfg.iface) {
+ cfg.iface = value
+ }
+}
+
+func isUnixSocket(address string) bool {
+ return strings.HasPrefix(address, "/")
+}
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config/config_test.go b/src/go/collectors/go.d.plugin/modules/unbound/config/config_test.go
new file mode 100644
index 000000000..0375c1368
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config/config_test.go
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package config
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestUnboundConfig_Empty(t *testing.T) {
+ assert.True(t, UnboundConfig{}.Empty())
+ assert.False(t, UnboundConfig{enable: "yes"}.Empty())
+}
+
+func TestUnboundConfig_Cumulative(t *testing.T) {
+ tests := []struct {
+ input string
+ wantValue bool
+ wantOK bool
+ }{
+ {input: "yes", wantValue: true, wantOK: true},
+ {input: "no", wantValue: false, wantOK: true},
+ {input: "", wantValue: false, wantOK: false},
+ {input: "some value", wantValue: false, wantOK: true},
+ }
+
+ for _, test := range tests {
+ t.Run(test.input, func(t *testing.T) {
+ cfg := UnboundConfig{cumulative: test.input}
+
+ v, ok := cfg.Cumulative()
+ assert.Equal(t, test.wantValue, v)
+ assert.Equal(t, test.wantOK, ok)
+ })
+ }
+}
+
+func TestUnboundConfig_ControlEnabled(t *testing.T) {
+ tests := []struct {
+ input string
+ wantValue bool
+ wantOK bool
+ }{
+ {input: "yes", wantValue: true, wantOK: true},
+ {input: "no", wantValue: false, wantOK: true},
+ {input: "", wantValue: false, wantOK: false},
+ {input: "some value", wantValue: false, wantOK: true},
+ }
+
+ for _, test := range tests {
+ t.Run(test.input, func(t *testing.T) {
+ cfg := UnboundConfig{enable: test.input}
+
+ v, ok := cfg.ControlEnabled()
+ assert.Equal(t, test.wantValue, v)
+ assert.Equal(t, test.wantOK, ok)
+ })
+ }
+}
+
+func TestUnboundConfig_ControlInterface(t *testing.T) {
+ tests := []struct {
+ input string
+ wantValue string
+ wantOK bool
+ }{
+ {input: "127.0.0.1", wantValue: "127.0.0.1", wantOK: true},
+ {input: "/var/run/unbound.sock", wantValue: "/var/run/unbound.sock", wantOK: true},
+ {input: "", wantValue: "", wantOK: false},
+ {input: "some value", wantValue: "some value", wantOK: true},
+ }
+
+ for _, test := range tests {
+ t.Run(test.input, func(t *testing.T) {
+ cfg := UnboundConfig{iface: test.input}
+
+ v, ok := cfg.ControlInterface()
+ assert.Equal(t, test.wantValue, v)
+ assert.Equal(t, test.wantOK, ok)
+ })
+ }
+}
+
+func TestUnboundConfig_ControlPort(t *testing.T) {
+ tests := []struct {
+ input string
+ wantValue string
+ wantOK bool
+ }{
+ {input: "8953", wantValue: "8953", wantOK: true},
+ {input: "", wantValue: "", wantOK: false},
+ {input: "some value", wantValue: "some value", wantOK: true},
+ }
+
+ for _, test := range tests {
+ t.Run(test.input, func(t *testing.T) {
+ cfg := UnboundConfig{port: test.input}
+
+ v, ok := cfg.ControlPort()
+ assert.Equal(t, test.wantValue, v)
+ assert.Equal(t, test.wantOK, ok)
+ })
+ }
+}
+
+func TestUnboundConfig_ControlUseCert(t *testing.T) {
+ tests := []struct {
+ input string
+ wantValue bool
+ wantOK bool
+ }{
+ {input: "yes", wantValue: true, wantOK: true},
+ {input: "no", wantValue: false, wantOK: true},
+ {input: "", wantValue: false, wantOK: false},
+ {input: "some value", wantValue: false, wantOK: true},
+ }
+
+ for _, test := range tests {
+ t.Run(test.input, func(t *testing.T) {
+ cfg := UnboundConfig{useCert: test.input}
+
+ v, ok := cfg.ControlUseCert()
+ assert.Equal(t, test.wantValue, v)
+ assert.Equal(t, test.wantOK, ok)
+ })
+ }
+}
+
+func TestUnboundConfig_ControlKeyFile(t *testing.T) {
+ tests := []struct {
+ input string
+ wantValue string
+ wantOK bool
+ }{
+ {input: "/etc/unbound/unbound_control.key", wantValue: "/etc/unbound/unbound_control.key", wantOK: true},
+ {input: "", wantValue: "", wantOK: false},
+ {input: "some value", wantValue: "some value", wantOK: true},
+ }
+
+ for _, test := range tests {
+ t.Run(test.input, func(t *testing.T) {
+ cfg := UnboundConfig{keyFile: test.input}
+
+ v, ok := cfg.ControlKeyFile()
+ assert.Equal(t, test.wantValue, v)
+ assert.Equal(t, test.wantOK, ok)
+ })
+ }
+}
+
+func TestUnboundConfig_ControlCertFile(t *testing.T) {
+ tests := []struct {
+ input string
+ wantValue string
+ wantOK bool
+ }{
+ {input: "/etc/unbound/unbound_control.pem", wantValue: "/etc/unbound/unbound_control.pem", wantOK: true},
+ {input: "", wantValue: "", wantOK: false},
+ {input: "some value", wantValue: "some value", wantOK: true},
+ }
+
+ for _, test := range tests {
+ t.Run(test.input, func(t *testing.T) {
+ cfg := UnboundConfig{certFile: test.input}
+
+ v, ok := cfg.ControlCertFile()
+ assert.Equal(t, test.wantValue, v)
+ assert.Equal(t, test.wantOK, ok)
+ })
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config/parse.go b/src/go/collectors/go.d.plugin/modules/unbound/config/parse.go
new file mode 100644
index 000000000..99a632d50
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config/parse.go
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package config
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+)
+
+type option struct{ name, value string }
+
+const (
+ optInclude = "include"
+ optIncludeToplevel = "include-toplevel"
+ optCumulative = "statistics-cumulative"
+ optEnable = "control-enable"
+ optInterface = "control-interface"
+ optPort = "control-port"
+ optUseCert = "control-use-cert"
+ optKeyFile = "control-key-file"
+ optCertFile = "control-cert-file"
+)
+
+func isOptionUsed(opt option) bool {
+ switch opt.name {
+ case optInclude,
+ optIncludeToplevel,
+ optCumulative,
+ optEnable,
+ optInterface,
+ optPort,
+ optUseCert,
+ optKeyFile,
+ optCertFile:
+ return true
+ }
+ return false
+}
+
+// TODO:
+// If also using chroot, using full path names for the included files works, relative pathnames for the included names
+// work if the directory where the daemon is started equals its chroot/working directory or is specified before
+// the include statement with directory: dir.
+
+// Parse parses Unbound configuration files into UnboundConfig.
+// It follows logic described in the 'man unbound.conf':
+// - Files can be included using the 'include:' directive. It can appear anywhere, it accepts a single file name as argument.
+// - Processing continues as if the text from the included file was copied into the config file at that point.
+// - Wildcards can be used to include multiple files.
+//
+// It stops processing on any error: syntax error, recursive include, glob matches directory etc.
+func Parse(entryPath string) (*UnboundConfig, error) {
+ options, err := parse(entryPath, nil)
+ if err != nil {
+ return nil, err
+ }
+ return fromOptions(options), nil
+}
+
+func parse(filename string, visited map[string]bool) ([]option, error) {
+ if visited == nil {
+ visited = make(map[string]bool)
+ }
+ if visited[filename] {
+ return nil, fmt.Errorf("'%s' already visited", filename)
+ }
+ visited[filename] = true
+
+ f, err := open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer func() { _ = f.Close() }()
+
+ var options []option
+ sc := bufio.NewScanner(f)
+
+ for sc.Scan() {
+ line := strings.TrimSpace(sc.Text())
+ if line == "" || strings.HasPrefix(line, "#") {
+ continue
+ }
+
+ opt, err := parseLine(line)
+ if err != nil {
+ return nil, fmt.Errorf("file '%s', error on parsing line '%s': %v", filename, line, err)
+ }
+
+ if !isOptionUsed(opt) {
+ continue
+ }
+
+ if opt.name != optInclude && opt.name != optIncludeToplevel {
+ options = append(options, opt)
+ continue
+ }
+
+ filenames, err := globInclude(opt.value)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, name := range filenames {
+ opts, err := parse(name, visited)
+ if err != nil {
+ return nil, err
+ }
+ options = append(options, opts...)
+ }
+ }
+ return options, nil
+}
+
+func globInclude(include string) ([]string, error) {
+ if isGlobPattern(include) {
+ return filepath.Glob(include)
+ }
+ return []string{include}, nil
+}
+
+func parseLine(line string) (option, error) {
+ parts := strings.Split(line, ":")
+ if len(parts) < 2 {
+ return option{}, errors.New("bad syntax")
+ }
+ key, value := cleanKeyValue(parts[0], parts[1])
+ return option{name: key, value: value}, nil
+}
+
+func cleanKeyValue(key, value string) (string, string) {
+ if i := strings.IndexByte(value, '#'); i > 0 {
+ value = value[:i-1]
+ }
+ key = strings.TrimSpace(key)
+ value = strings.Trim(strings.TrimSpace(value), "\"'")
+ return key, value
+}
+
+func isGlobPattern(value string) bool {
+ magicChars := `*?[`
+ if runtime.GOOS != "windows" {
+ magicChars = `*?[\`
+ }
+ return strings.ContainsAny(value, magicChars)
+}
+
+func open(filename string) (*os.File, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ fi, err := f.Stat()
+ if err != nil {
+ return nil, err
+ }
+ if !fi.Mode().IsRegular() {
+ return nil, fmt.Errorf("'%s' is not a regular file", filename)
+ }
+ return f, nil
+}
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config/parse_test.go b/src/go/collectors/go.d.plugin/modules/unbound/config/parse_test.go
new file mode 100644
index 000000000..72542a861
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config/parse_test.go
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package config
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestParse(t *testing.T) {
+ tests := map[string]struct {
+ path string
+ wantCfg UnboundConfig
+ wantErr bool
+ }{
+ "valid include": {
+ path: "testdata/valid_include.conf",
+ wantCfg: UnboundConfig{
+ cumulative: "yes",
+ enable: "yes",
+ iface: "10.0.0.1",
+ port: "8955",
+ useCert: "yes",
+ keyFile: "/etc/unbound/unbound_control_2.key",
+ certFile: "/etc/unbound/unbound_control_2.pem",
+ },
+ },
+ "valid include-toplevel": {
+ path: "testdata/valid_include_toplevel.conf",
+ wantCfg: UnboundConfig{
+ cumulative: "yes",
+ enable: "yes",
+ iface: "10.0.0.1",
+ port: "8955",
+ useCert: "yes",
+ keyFile: "/etc/unbound/unbound_control_2.key",
+ certFile: "/etc/unbound/unbound_control_2.pem",
+ },
+ },
+ "valid glob include": {
+ path: "testdata/valid_glob.conf",
+ wantCfg: UnboundConfig{
+ cumulative: "yes",
+ enable: "yes",
+ iface: "10.0.0.1",
+ port: "8955",
+ useCert: "yes",
+ keyFile: "/etc/unbound/unbound_control_2.key",
+ certFile: "/etc/unbound/unbound_control_2.pem",
+ },
+ },
+ "non existent glob include": {
+ path: "testdata/non_existent_glob_include.conf",
+ wantCfg: UnboundConfig{
+ cumulative: "yes",
+ enable: "yes",
+ iface: "10.0.0.1",
+ port: "8953",
+ useCert: "yes",
+ keyFile: "/etc/unbound/unbound_control.key",
+ certFile: "/etc/unbound/unbound_control.pem",
+ },
+ },
+ "infinite recursion include": {
+ path: "testdata/infinite_rec.conf",
+ wantErr: true,
+ },
+ "non existent include": {
+ path: "testdata/non_existent_include.conf",
+ wantErr: true,
+ },
+ "non existent path": {
+ path: "testdata/non_existent_path.conf",
+ wantErr: true,
+ },
+ }
+
+ for name, test := range tests {
+ name = fmt.Sprintf("%s (%s)", name, test.path)
+ t.Run(name, func(t *testing.T) {
+ cfg, err := Parse(test.path)
+
+ if test.wantErr {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ assert.Equal(t, test.wantCfg, *cfg)
+ }
+ })
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/infinite_rec.conf b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/infinite_rec.conf
new file mode 100644
index 000000000..904f75b30
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/infinite_rec.conf
@@ -0,0 +1,85 @@
+#
+# Example configuration file.
+#
+# See unbound.conf(5) man page, version 1.9.4.
+#
+# this is a comment.
+
+#Use this to include other text into the file.
+include: "testdata/infinite_rec.conf"
+
+# The server clause sets the main parameters.
+server:
+ # whitespace is not necessary, but looks cleaner.
+
+ # verbosity number, 0 is least verbose. 1 is default.
+ # verbosity: 1
+
+ # print statistics to the log (for every thread) every N seconds.
+ # Set to "" or 0 to disable. Default is disabled.
+ # statistics-interval: 0
+
+ # enable shm for stats, default no. if you enable also enable
+ # statistics-interval, every time it also writes stats to the
+ # shared memory segment keyed with shm-key.
+ # shm-enable: no
+
+ # shm for stats uses this key, and key+1 for the shared mem segment.
+ # shm-key: 11777
+
+ # enable cumulative statistics, without clearing them after printing.
+ # statistics-cumulative: no
+ statistics-cumulative: yes
+
+ # enable extended statistics (query types, answer codes, status)
+ # printed from unbound-control. default off, because of speed.
+ # extended-statistics: no
+ # extended-statistics: yes
+
+ # number of threads to create. 1 disables threading.
+ # num-threads: 2
+
+# Python config section. To enable:
+# o use --with-pythonmodule to configure before compiling.
+# o list python in the module-config string (above) to enable.
+# It can be at the start, it gets validated results, or just before
+# the iterator and process before DNSSEC validation.
+# o and give a python-script to run.
+python:
+ # Script file to load
+ # python-script: "/etc/unbound/ubmodule-tst.py"
+
+# Remote control config section.
+remote-control:
+ # Enable remote control with unbound-control(8) here.
+ # set up the keys and certificates with unbound-control-setup.
+ control-enable: yes
+
+ # what interfaces are listened to for remote control.
+ # give 0.0.0.0 and ::0 to listen to all interfaces.
+ # set to an absolute path to use a unix local name pipe, certificates
+ # are not used for that, so key and cert files need not be present.
+ # control-interface: 127.0.0.1
+ control-interface: 10.0.0.1
+ # control-interface: ::1
+ # control-interface: /var/run/test.sock
+
+ # port number for remote control operations.
+ control-port: 8953
+
+ # for localhost, you can disable use of TLS by setting this to "no"
+ # For local sockets this option is ignored, and TLS is not used.
+ # control-use-cert: "yes"
+ control-use-cert: "yes"
+
+ # unbound server key file.
+ # server-key-file: "/etc/unbound/unbound_server.key"
+
+ # unbound server certificate file.
+ # server-cert-file: "/etc/unbound/unbound_server.pem"
+
+ # unbound-control key file.
+ control-key-file: "/etc/unbound/unbound_control.key"
+
+ # unbound-control certificate file.
+ control-cert-file: "/etc/unbound/unbound_control.pem"
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/non_existent_glob_include.conf b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/non_existent_glob_include.conf
new file mode 100644
index 000000000..21620f7d5
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/non_existent_glob_include.conf
@@ -0,0 +1,85 @@
+#
+# Example configuration file.
+#
+# See unbound.conf(5) man page, version 1.9.4.
+#
+# this is a comment.
+
+#Use this to include other text into the file.
+include: "testdata/__non_existent_glob__*.conf"
+
+# The server clause sets the main parameters.
+server:
+ # whitespace is not necessary, but looks cleaner.
+
+ # verbosity number, 0 is least verbose. 1 is default.
+ # verbosity: 1
+
+ # print statistics to the log (for every thread) every N seconds.
+ # Set to "" or 0 to disable. Default is disabled.
+ # statistics-interval: 0
+
+ # enable shm for stats, default no. if you enable also enable
+ # statistics-interval, every time it also writes stats to the
+ # shared memory segment keyed with shm-key.
+ # shm-enable: no
+
+ # shm for stats uses this key, and key+1 for the shared mem segment.
+ # shm-key: 11777
+
+ # enable cumulative statistics, without clearing them after printing.
+ # statistics-cumulative: no
+ statistics-cumulative: yes
+
+ # enable extended statistics (query types, answer codes, status)
+ # printed from unbound-control. default off, because of speed.
+ # extended-statistics: no
+ # extended-statistics: yes
+
+ # number of threads to create. 1 disables threading.
+ # num-threads: 2
+
+# Python config section. To enable:
+# o use --with-pythonmodule to configure before compiling.
+# o list python in the module-config string (above) to enable.
+# It can be at the start, it gets validated results, or just before
+# the iterator and process before DNSSEC validation.
+# o and give a python-script to run.
+python:
+ # Script file to load
+ # python-script: "/etc/unbound/ubmodule-tst.py"
+
+# Remote control config section.
+remote-control:
+ # Enable remote control with unbound-control(8) here.
+ # set up the keys and certificates with unbound-control-setup.
+ control-enable: yes
+
+ # what interfaces are listened to for remote control.
+ # give 0.0.0.0 and ::0 to listen to all interfaces.
+ # set to an absolute path to use a unix local name pipe, certificates
+ # are not used for that, so key and cert files need not be present.
+ # control-interface: 127.0.0.1
+ control-interface: 10.0.0.1
+ # control-interface: ::1
+ # control-interface: /var/run/test.sock
+
+ # port number for remote control operations.
+ control-port: 8953
+
+ # for localhost, you can disable use of TLS by setting this to "no"
+ # For local sockets this option is ignored, and TLS is not used.
+ # control-use-cert: "yes"
+ control-use-cert: "yes"
+
+ # unbound server key file.
+ # server-key-file: "/etc/unbound/unbound_server.key"
+
+ # unbound server certificate file.
+ # server-cert-file: "/etc/unbound/unbound_server.pem"
+
+ # unbound-control key file.
+ control-key-file: "/etc/unbound/unbound_control.key"
+
+ # unbound-control certificate file.
+ control-cert-file: "/etc/unbound/unbound_control.pem"
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/non_existent_include.conf b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/non_existent_include.conf
new file mode 100644
index 000000000..e493e35bb
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/non_existent_include.conf
@@ -0,0 +1,85 @@
+#
+# Example configuration file.
+#
+# See unbound.conf(5) man page, version 1.9.4.
+#
+# this is a comment.
+
+#Use this to include other text into the file.
+include: "testdata/__non_existent_include__.conf"
+
+# The server clause sets the main parameters.
+server:
+ # whitespace is not necessary, but looks cleaner.
+
+ # verbosity number, 0 is least verbose. 1 is default.
+ # verbosity: 1
+
+ # print statistics to the log (for every thread) every N seconds.
+ # Set to "" or 0 to disable. Default is disabled.
+ # statistics-interval: 0
+
+ # enable shm for stats, default no. if you enable also enable
+ # statistics-interval, every time it also writes stats to the
+ # shared memory segment keyed with shm-key.
+ # shm-enable: no
+
+ # shm for stats uses this key, and key+1 for the shared mem segment.
+ # shm-key: 11777
+
+ # enable cumulative statistics, without clearing them after printing.
+ # statistics-cumulative: no
+ statistics-cumulative: yes
+
+ # enable extended statistics (query types, answer codes, status)
+ # printed from unbound-control. default off, because of speed.
+ # extended-statistics: no
+ # extended-statistics: yes
+
+ # number of threads to create. 1 disables threading.
+ # num-threads: 2
+
+# Python config section. To enable:
+# o use --with-pythonmodule to configure before compiling.
+# o list python in the module-config string (above) to enable.
+# It can be at the start, it gets validated results, or just before
+# the iterator and process before DNSSEC validation.
+# o and give a python-script to run.
+python:
+ # Script file to load
+ # python-script: "/etc/unbound/ubmodule-tst.py"
+
+# Remote control config section.
+remote-control:
+ # Enable remote control with unbound-control(8) here.
+ # set up the keys and certificates with unbound-control-setup.
+ control-enable: yes
+
+ # what interfaces are listened to for remote control.
+ # give 0.0.0.0 and ::0 to listen to all interfaces.
+ # set to an absolute path to use a unix local name pipe, certificates
+ # are not used for that, so key and cert files need not be present.
+ # control-interface: 127.0.0.1
+ control-interface: 10.0.0.1
+ # control-interface: ::1
+ # control-interface: /var/run/test.sock
+
+ # port number for remote control operations.
+ control-port: 8953
+
+ # for localhost, you can disable use of TLS by setting this to "no"
+ # For local sockets this option is ignored, and TLS is not used.
+ # control-use-cert: "yes"
+ control-use-cert: "yes"
+
+ # unbound server key file.
+ # server-key-file: "/etc/unbound/unbound_server.key"
+
+ # unbound server certificate file.
+ # server-cert-file: "/etc/unbound/unbound_server.pem"
+
+ # unbound-control key file.
+ control-key-file: "/etc/unbound/unbound_control.key"
+
+ # unbound-control certificate file.
+ control-cert-file: "/etc/unbound/unbound_control.pem"
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_glob.conf b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_glob.conf
new file mode 100644
index 000000000..f020c580a
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_glob.conf
@@ -0,0 +1,82 @@
+#
+# Example configuration file.
+#
+# See unbound.conf(5) man page, version 1.9.4.
+#
+# this is a comment.
+
+#Use this to include other text into the file.
+include: "testdata/valid_glob[2-3].conf"
+
+# The server clause sets the main parameters.
+server:
+ # whitespace is not necessary, but looks cleaner.
+
+ # verbosity number, 0 is least verbose. 1 is default.
+ # verbosity: 1
+
+ # print statistics to the log (for every thread) every N seconds.
+ # Set to "" or 0 to disable. Default is disabled.
+ # statistics-interval: 0
+
+ # enable shm for stats, default no. if you enable also enable
+ # statistics-interval, every time it also writes stats to the
+ # shared memory segment keyed with shm-key.
+ # shm-enable: no
+
+ # shm for stats uses this key, and key+1 for the shared mem segment.
+ # shm-key: 11777
+
+ # enable cumulative statistics, without clearing them after printing.
+ statistics-cumulative: yes
+
+ # enable extended statistics (query types, answer codes, status)
+ # printed from unbound-control. default off, because of speed.
+ # extended-statistics: no
+
+ # number of threads to create. 1 disables threading.
+ # num-threads: 2
+
+# Python config section. To enable:
+# o use --with-pythonmodule to configure before compiling.
+# o list python in the module-config string (above) to enable.
+# It can be at the start, it gets validated results, or just before
+# the iterator and process before DNSSEC validation.
+# o and give a python-script to run.
+python:
+ # Script file to load
+ # python-script: "/etc/unbound/ubmodule-tst.py"
+
+# Remote control config section.
+remote-control:
+ # Enable remote control with unbound-control(8) here.
+ # set up the keys and certificates with unbound-control-setup.
+ control-enable: yes
+
+ # what interfaces are listened to for remote control.
+ # give 0.0.0.0 and ::0 to listen to all interfaces.
+ # set to an absolute path to use a unix local name pipe, certificates
+ # are not used for that, so key and cert files need not be present.
+ # control-interface: 127.0.0.1
+ control-interface: 10.0.0.1
+ # control-interface: ::1
+ # control-interface: /var/run/test.sock
+
+ # port number for remote control operations.
+ # control-port: 8955
+
+ # for localhost, you can disable use of TLS by setting this to "no"
+ # For local sockets this option is ignored, and TLS is not used.
+ control-use-cert: "yes"
+
+ # unbound server key file.
+ # server-key-file: "/etc/unbound/unbound_server.key"
+
+ # unbound server certificate file.
+ # server-cert-file: "/etc/unbound/unbound_server.pem"
+
+ # unbound-control key file.
+ # control-key-file: "/etc/unbound/unbound_control_2.key"
+
+ # unbound-control certificate file.
+ # control-cert-file: "/etc/unbound/unbound_control_2.pem"
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_glob2.conf b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_glob2.conf
new file mode 100644
index 000000000..85bd80e0d
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_glob2.conf
@@ -0,0 +1,80 @@
+#
+# Example configuration file.
+#
+# See unbound.conf(5) man page, version 1.9.4.
+#
+# this is a comment.
+
+#Use this to include other text into the file.
+
+# The server clause sets the main parameters.
+server:
+ # whitespace is not necessary, but looks cleaner.
+
+ # verbosity number, 0 is least verbose. 1 is default.
+ # verbosity: 1
+
+ # print statistics to the log (for every thread) every N seconds.
+ # Set to "" or 0 to disable. Default is disabled.
+ # statistics-interval: 0
+
+ # enable shm for stats, default no. if you enable also enable
+ # statistics-interval, every time it also writes stats to the
+ # shared memory segment keyed with shm-key.
+ # shm-enable: no
+
+ # shm for stats uses this key, and key+1 for the shared mem segment.
+ # shm-key: 11777
+
+ # enable cumulative statistics, without clearing them after printing.
+ # statistics-cumulative: no
+
+ # enable extended statistics (query types, answer codes, status)
+ # printed from unbound-control. default off, because of speed.
+ # extended-statistics: no
+
+ # number of threads to create. 1 disables threading.
+ # num-threads: 2
+
+# Python config section. To enable:
+# o use --with-pythonmodule to configure before compiling.
+# o list python in the module-config string (above) to enable.
+# It can be at the start, it gets validated results, or just before
+# the iterator and process before DNSSEC validation.
+# o and give a python-script to run.
+python:
+ # Script file to load
+ # python-script: "/etc/unbound/ubmodule-tst.py"
+
+# Remote control config section.
+remote-control:
+ # Enable remote control with unbound-control(8) here.
+ # set up the keys and certificates with unbound-control-setup.
+ # control-enable: no
+
+ # what interfaces are listened to for remote control.
+ # give 0.0.0.0 and ::0 to listen to all interfaces.
+ # set to an absolute path to use a unix local name pipe, certificates
+ # are not used for that, so key and cert files need not be present.
+ # control-interface: 127.0.0.1
+ # control-interface: ::1
+ control-interface: /var/run/test.sock
+
+ # port number for remote control operations.
+ # control-port: 8955
+
+ # for localhost, you can disable use of TLS by setting this to "no"
+ # For local sockets this option is ignored, and TLS is not used.
+ # control-use-cert: "yes"
+
+ # unbound server key file.
+ # server-key-file: "/etc/unbound/unbound_server.key"
+
+ # unbound server certificate file.
+ # server-cert-file: "/etc/unbound/unbound_server.pem"
+
+ # unbound-control key file.
+ control-key-file: "/etc/unbound/unbound_control_2.key"
+
+ # unbound-control certificate file.
+ control-cert-file: "/etc/unbound/unbound_control_2.pem"
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_glob3.conf b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_glob3.conf
new file mode 100644
index 000000000..f20eacf1a
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_glob3.conf
@@ -0,0 +1,81 @@
+#
+# Example configuration file.
+#
+# See unbound.conf(5) man page, version 1.9.4.
+#
+# this is a comment.
+
+#Use this to include other text into the file.
+
+# The server clause sets the main parameters.
+server:
+ # whitespace is not necessary, but looks cleaner.
+
+ # verbosity number, 0 is least verbose. 1 is default.
+ # verbosity: 1
+
+ # print statistics to the log (for every thread) every N seconds.
+ # Set to "" or 0 to disable. Default is disabled.
+ # statistics-interval: 0
+
+ # enable shm for stats, default no. if you enable also enable
+ # statistics-interval, every time it also writes stats to the
+ # shared memory segment keyed with shm-key.
+ # shm-enable: no
+
+ # shm for stats uses this key, and key+1 for the shared mem segment.
+ # shm-key: 11777
+
+ # enable cumulative statistics, without clearing them after printing.
+ # statistics-cumulative: no
+
+ # enable extended statistics (query types, answer codes, status)
+ # printed from unbound-control. default off, because of speed.
+ # extended-statistics: no
+
+ # number of threads to create. 1 disables threading.
+ # num-threads: 2
+
+# Python config section. To enable:
+# o use --with-pythonmodule to configure before compiling.
+# o list python in the module-config string (above) to enable.
+# It can be at the start, it gets validated results, or just before
+# the iterator and process before DNSSEC validation.
+# o and give a python-script to run.
+python:
+ # Script file to load
+ # python-script: "/etc/unbound/ubmodule-tst.py"
+
+# Remote control config section.
+remote-control:
+ # Enable remote control with unbound-control(8) here.
+ # set up the keys and certificates with unbound-control-setup.
+ # control-enable: no
+
+ # what interfaces are listened to for remote control.
+ # give 0.0.0.0 and ::0 to listen to all interfaces.
+ # set to an absolute path to use a unix local name pipe, certificates
+ # are not used for that, so key and cert files need not be present.
+ # control-interface: 127.0.0.1
+ control-interface: 10.0.0.3
+ # control-interface: ::1
+ # control-interface: /var/run/test.sock
+
+ # port number for remote control operations.
+ control-port: 8955
+
+ # for localhost, you can disable use of TLS by setting this to "no"
+ # For local sockets this option is ignored, and TLS is not used.
+ # control-use-cert: "yes"
+
+ # unbound server key file.
+ # server-key-file: "/etc/unbound/unbound_server.key"
+
+ # unbound server certificate file.
+ # server-cert-file: "/etc/unbound/unbound_server.pem"
+
+ # unbound-control key file.
+ # control-key-file: "/etc/unbound/unbound_control.key"
+
+ # unbound-control certificate file.
+ # control-cert-file: "/etc/unbound/unbound_control.pem"
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include.conf b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include.conf
new file mode 100644
index 000000000..1974f6178
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include.conf
@@ -0,0 +1,82 @@
+#
+# Example configuration file.
+#
+# See unbound.conf(5) man page, version 1.9.4.
+#
+# this is a comment.
+
+#Use this to include other text into the file.
+include: "testdata/valid_include2.conf"
+
+# The server clause sets the main parameters.
+server:
+ # whitespace is not necessary, but looks cleaner.
+
+ # verbosity number, 0 is least verbose. 1 is default.
+ # verbosity: 1
+
+ # print statistics to the log (for every thread) every N seconds.
+ # Set to "" or 0 to disable. Default is disabled.
+ # statistics-interval: 0
+
+ # enable shm for stats, default no. if you enable also enable
+ # statistics-interval, every time it also writes stats to the
+ # shared memory segment keyed with shm-key.
+ # shm-enable: no
+
+ # shm for stats uses this key, and key+1 for the shared mem segment.
+ # shm-key: 11777
+
+ # enable cumulative statistics, without clearing them after printing.
+ statistics-cumulative: yes
+
+ # enable extended statistics (query types, answer codes, status)
+ # printed from unbound-control. default off, because of speed.
+ # extended-statistics: no
+
+ # number of threads to create. 1 disables threading.
+ # num-threads: 2
+
+# Python config section. To enable:
+# o use --with-pythonmodule to configure before compiling.
+# o list python in the module-config string (above) to enable.
+# It can be at the start, it gets validated results, or just before
+# the iterator and process before DNSSEC validation.
+# o and give a python-script to run.
+python:
+ # Script file to load
+ # python-script: "/etc/unbound/ubmodule-tst.py"
+
+# Remote control config section.
+remote-control:
+ # Enable remote control with unbound-control(8) here.
+ # set up the keys and certificates with unbound-control-setup.
+ control-enable: yes
+
+ # what interfaces are listened to for remote control.
+ # give 0.0.0.0 and ::0 to listen to all interfaces.
+ # set to an absolute path to use a unix local name pipe, certificates
+ # are not used for that, so key and cert files need not be present.
+ # control-interface: 127.0.0.1
+ control-interface: 10.0.0.1
+ # control-interface: ::1
+ # control-interface: /var/run/test.sock
+
+ # port number for remote control operations.
+ # control-port: 8955
+
+ # for localhost, you can disable use of TLS by setting this to "no"
+ # For local sockets this option is ignored, and TLS is not used.
+ control-use-cert: "yes"
+
+ # unbound server key file.
+ # server-key-file: "/etc/unbound/unbound_server.key"
+
+ # unbound server certificate file.
+ # server-cert-file: "/etc/unbound/unbound_server.pem"
+
+ # unbound-control key file.
+ # control-key-file: "/etc/unbound/unbound_control_2.key"
+
+ # unbound-control certificate file.
+ # control-cert-file: "/etc/unbound/unbound_control_2.pem"
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include2.conf b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include2.conf
new file mode 100644
index 000000000..c956d44d5
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include2.conf
@@ -0,0 +1,81 @@
+#
+# Example configuration file.
+#
+# See unbound.conf(5) man page, version 1.9.4.
+#
+# this is a comment.
+
+#Use this to include other text into the file.
+include: "testdata/valid_include3.conf"
+
+# The server clause sets the main parameters.
+server:
+ # whitespace is not necessary, but looks cleaner.
+
+ # verbosity number, 0 is least verbose. 1 is default.
+ # verbosity: 1
+
+ # print statistics to the log (for every thread) every N seconds.
+ # Set to "" or 0 to disable. Default is disabled.
+ # statistics-interval: 0
+
+ # enable shm for stats, default no. if you enable also enable
+ # statistics-interval, every time it also writes stats to the
+ # shared memory segment keyed with shm-key.
+ # shm-enable: no
+
+ # shm for stats uses this key, and key+1 for the shared mem segment.
+ # shm-key: 11777
+
+ # enable cumulative statistics, without clearing them after printing.
+ # statistics-cumulative: no
+
+ # enable extended statistics (query types, answer codes, status)
+ # printed from unbound-control. default off, because of speed.
+ # extended-statistics: no
+
+ # number of threads to create. 1 disables threading.
+ # num-threads: 2
+
+# Python config section. To enable:
+# o use --with-pythonmodule to configure before compiling.
+# o list python in the module-config string (above) to enable.
+# It can be at the start, it gets validated results, or just before
+# the iterator and process before DNSSEC validation.
+# o and give a python-script to run.
+python:
+ # Script file to load
+ # python-script: "/etc/unbound/ubmodule-tst.py"
+
+# Remote control config section.
+remote-control:
+ # Enable remote control with unbound-control(8) here.
+ # set up the keys and certificates with unbound-control-setup.
+ # control-enable: no
+
+ # what interfaces are listened to for remote control.
+ # give 0.0.0.0 and ::0 to listen to all interfaces.
+ # set to an absolute path to use a unix local name pipe, certificates
+ # are not used for that, so key and cert files need not be present.
+ # control-interface: 127.0.0.1
+ # control-interface: ::1
+ control-interface: /var/run/test.sock
+
+ # port number for remote control operations.
+ # control-port: 8955
+
+ # for localhost, you can disable use of TLS by setting this to "no"
+ # For local sockets this option is ignored, and TLS is not used.
+ # control-use-cert: "yes"
+
+ # unbound server key file.
+ # server-key-file: "/etc/unbound/unbound_server.key"
+
+ # unbound server certificate file.
+ # server-cert-file: "/etc/unbound/unbound_server.pem"
+
+ # unbound-control key file.
+ control-key-file: "/etc/unbound/unbound_control_2.key"
+
+ # unbound-control certificate file.
+ control-cert-file: "/etc/unbound/unbound_control_2.pem"
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include3.conf b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include3.conf
new file mode 100644
index 000000000..f20eacf1a
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include3.conf
@@ -0,0 +1,81 @@
+#
+# Example configuration file.
+#
+# See unbound.conf(5) man page, version 1.9.4.
+#
+# this is a comment.
+
+#Use this to include other text into the file.
+
+# The server clause sets the main parameters.
+server:
+ # whitespace is not necessary, but looks cleaner.
+
+ # verbosity number, 0 is least verbose. 1 is default.
+ # verbosity: 1
+
+ # print statistics to the log (for every thread) every N seconds.
+ # Set to "" or 0 to disable. Default is disabled.
+ # statistics-interval: 0
+
+ # enable shm for stats, default no. if you enable also enable
+ # statistics-interval, every time it also writes stats to the
+ # shared memory segment keyed with shm-key.
+ # shm-enable: no
+
+ # shm for stats uses this key, and key+1 for the shared mem segment.
+ # shm-key: 11777
+
+ # enable cumulative statistics, without clearing them after printing.
+ # statistics-cumulative: no
+
+ # enable extended statistics (query types, answer codes, status)
+ # printed from unbound-control. default off, because of speed.
+ # extended-statistics: no
+
+ # number of threads to create. 1 disables threading.
+ # num-threads: 2
+
+# Python config section. To enable:
+# o use --with-pythonmodule to configure before compiling.
+# o list python in the module-config string (above) to enable.
+# It can be at the start, it gets validated results, or just before
+# the iterator and process before DNSSEC validation.
+# o and give a python-script to run.
+python:
+ # Script file to load
+ # python-script: "/etc/unbound/ubmodule-tst.py"
+
+# Remote control config section.
+remote-control:
+ # Enable remote control with unbound-control(8) here.
+ # set up the keys and certificates with unbound-control-setup.
+ # control-enable: no
+
+ # what interfaces are listened to for remote control.
+ # give 0.0.0.0 and ::0 to listen to all interfaces.
+ # set to an absolute path to use a unix local name pipe, certificates
+ # are not used for that, so key and cert files need not be present.
+ # control-interface: 127.0.0.1
+ control-interface: 10.0.0.3
+ # control-interface: ::1
+ # control-interface: /var/run/test.sock
+
+ # port number for remote control operations.
+ control-port: 8955
+
+ # for localhost, you can disable use of TLS by setting this to "no"
+ # For local sockets this option is ignored, and TLS is not used.
+ # control-use-cert: "yes"
+
+ # unbound server key file.
+ # server-key-file: "/etc/unbound/unbound_server.key"
+
+ # unbound server certificate file.
+ # server-cert-file: "/etc/unbound/unbound_server.pem"
+
+ # unbound-control key file.
+ # control-key-file: "/etc/unbound/unbound_control.key"
+
+ # unbound-control certificate file.
+ # control-cert-file: "/etc/unbound/unbound_control.pem"
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include_toplevel.conf b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include_toplevel.conf
new file mode 100644
index 000000000..9e5675e10
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include_toplevel.conf
@@ -0,0 +1,82 @@
+#
+# Example configuration file.
+#
+# See unbound.conf(5) man page, version 1.9.4.
+#
+# this is a comment.
+
+#Use this to include other text into the file.
+include-toplevel: "testdata/valid_include_toplevel2.conf"
+
+# The server clause sets the main parameters.
+server:
+ # whitespace is not necessary, but looks cleaner.
+
+ # verbosity number, 0 is least verbose. 1 is default.
+ # verbosity: 1
+
+ # print statistics to the log (for every thread) every N seconds.
+ # Set to "" or 0 to disable. Default is disabled.
+ # statistics-interval: 0
+
+ # enable shm for stats, default no. if you enable also enable
+ # statistics-interval, every time it also writes stats to the
+ # shared memory segment keyed with shm-key.
+ # shm-enable: no
+
+ # shm for stats uses this key, and key+1 for the shared mem segment.
+ # shm-key: 11777
+
+ # enable cumulative statistics, without clearing them after printing.
+ statistics-cumulative: yes
+
+ # enable extended statistics (query types, answer codes, status)
+ # printed from unbound-control. default off, because of speed.
+ # extended-statistics: no
+
+ # number of threads to create. 1 disables threading.
+ # num-threads: 2
+
+# Python config section. To enable:
+# o use --with-pythonmodule to configure before compiling.
+# o list python in the module-config string (above) to enable.
+# It can be at the start, it gets validated results, or just before
+# the iterator and process before DNSSEC validation.
+# o and give a python-script to run.
+python:
+# Script file to load
+# python-script: "/etc/unbound/ubmodule-tst.py"
+
+# Remote control config section.
+remote-control:
+ # Enable remote control with unbound-control(8) here.
+ # set up the keys and certificates with unbound-control-setup.
+ control-enable: yes
+
+ # what interfaces are listened to for remote control.
+ # give 0.0.0.0 and ::0 to listen to all interfaces.
+ # set to an absolute path to use a unix local name pipe, certificates
+ # are not used for that, so key and cert files need not be present.
+ # control-interface: 127.0.0.1
+ control-interface: 10.0.0.1
+ # control-interface: ::1
+ # control-interface: /var/run/test.sock
+
+ # port number for remote control operations.
+ # control-port: 8955
+
+ # for localhost, you can disable use of TLS by setting this to "no"
+ # For local sockets this option is ignored, and TLS is not used.
+ control-use-cert: "yes"
+
+ # unbound server key file.
+ # server-key-file: "/etc/unbound/unbound_server.key"
+
+ # unbound server certificate file.
+ # server-cert-file: "/etc/unbound/unbound_server.pem"
+
+ # unbound-control key file.
+ # control-key-file: "/etc/unbound/unbound_control_2.key"
+
+ # unbound-control certificate file.
+ # control-cert-file: "/etc/unbound/unbound_control_2.pem"
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include_toplevel2.conf b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include_toplevel2.conf
new file mode 100644
index 000000000..f3f69470d
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include_toplevel2.conf
@@ -0,0 +1,81 @@
+#
+# Example configuration file.
+#
+# See unbound.conf(5) man page, version 1.9.4.
+#
+# this is a comment.
+
+#Use this to include other text into the file.
+include-toplevel: "testdata/valid_include_toplevel3.conf"
+
+# The server clause sets the main parameters.
+server:
+# whitespace is not necessary, but looks cleaner.
+
+# verbosity number, 0 is least verbose. 1 is default.
+# verbosity: 1
+
+# print statistics to the log (for every thread) every N seconds.
+# Set to "" or 0 to disable. Default is disabled.
+# statistics-interval: 0
+
+# enable shm for stats, default no. if you enable also enable
+# statistics-interval, every time it also writes stats to the
+# shared memory segment keyed with shm-key.
+# shm-enable: no
+
+# shm for stats uses this key, and key+1 for the shared mem segment.
+# shm-key: 11777
+
+# enable cumulative statistics, without clearing them after printing.
+# statistics-cumulative: no
+
+# enable extended statistics (query types, answer codes, status)
+# printed from unbound-control. default off, because of speed.
+# extended-statistics: no
+
+# number of threads to create. 1 disables threading.
+# num-threads: 2
+
+# Python config section. To enable:
+# o use --with-pythonmodule to configure before compiling.
+# o list python in the module-config string (above) to enable.
+# It can be at the start, it gets validated results, or just before
+# the iterator and process before DNSSEC validation.
+# o and give a python-script to run.
+python:
+# Script file to load
+# python-script: "/etc/unbound/ubmodule-tst.py"
+
+# Remote control config section.
+remote-control:
+ # Enable remote control with unbound-control(8) here.
+ # set up the keys and certificates with unbound-control-setup.
+ # control-enable: no
+
+ # what interfaces are listened to for remote control.
+ # give 0.0.0.0 and ::0 to listen to all interfaces.
+ # set to an absolute path to use a unix local name pipe, certificates
+ # are not used for that, so key and cert files need not be present.
+ # control-interface: 127.0.0.1
+ # control-interface: ::1
+ control-interface: /var/run/test.sock
+
+ # port number for remote control operations.
+ # control-port: 8955
+
+ # for localhost, you can disable use of TLS by setting this to "no"
+ # For local sockets this option is ignored, and TLS is not used.
+ # control-use-cert: "yes"
+
+ # unbound server key file.
+ # server-key-file: "/etc/unbound/unbound_server.key"
+
+ # unbound server certificate file.
+ # server-cert-file: "/etc/unbound/unbound_server.pem"
+
+ # unbound-control key file.
+ control-key-file: "/etc/unbound/unbound_control_2.key"
+
+ # unbound-control certificate file.
+ control-cert-file: "/etc/unbound/unbound_control_2.pem"
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include_toplevel3.conf b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include_toplevel3.conf
new file mode 100644
index 000000000..d30778c01
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config/testdata/valid_include_toplevel3.conf
@@ -0,0 +1,81 @@
+#
+# Example configuration file.
+#
+# See unbound.conf(5) man page, version 1.9.4.
+#
+# this is a comment.
+
+#Use this to include other text into the file.
+
+# The server clause sets the main parameters.
+server:
+# whitespace is not necessary, but looks cleaner.
+
+# verbosity number, 0 is least verbose. 1 is default.
+# verbosity: 1
+
+# print statistics to the log (for every thread) every N seconds.
+# Set to "" or 0 to disable. Default is disabled.
+# statistics-interval: 0
+
+# enable shm for stats, default no. if you enable also enable
+# statistics-interval, every time it also writes stats to the
+# shared memory segment keyed with shm-key.
+# shm-enable: no
+
+# shm for stats uses this key, and key+1 for the shared mem segment.
+# shm-key: 11777
+
+# enable cumulative statistics, without clearing them after printing.
+# statistics-cumulative: no
+
+# enable extended statistics (query types, answer codes, status)
+# printed from unbound-control. default off, because of speed.
+# extended-statistics: no
+
+# number of threads to create. 1 disables threading.
+# num-threads: 2
+
+# Python config section. To enable:
+# o use --with-pythonmodule to configure before compiling.
+# o list python in the module-config string (above) to enable.
+# It can be at the start, it gets validated results, or just before
+# the iterator and process before DNSSEC validation.
+# o and give a python-script to run.
+python:
+# Script file to load
+# python-script: "/etc/unbound/ubmodule-tst.py"
+
+# Remote control config section.
+remote-control:
+ # Enable remote control with unbound-control(8) here.
+ # set up the keys and certificates with unbound-control-setup.
+ # control-enable: no
+
+ # what interfaces are listened to for remote control.
+ # give 0.0.0.0 and ::0 to listen to all interfaces.
+ # set to an absolute path to use a unix local name pipe, certificates
+ # are not used for that, so key and cert files need not be present.
+ # control-interface: 127.0.0.1
+ control-interface: 10.0.0.3
+ # control-interface: ::1
+ # control-interface: /var/run/test.sock
+
+ # port number for remote control operations.
+ control-port: 8955
+
+ # for localhost, you can disable use of TLS by setting this to "no"
+ # For local sockets this option is ignored, and TLS is not used.
+ # control-use-cert: "yes"
+
+ # unbound server key file.
+ # server-key-file: "/etc/unbound/unbound_server.key"
+
+ # unbound server certificate file.
+ # server-cert-file: "/etc/unbound/unbound_server.pem"
+
+ # unbound-control key file.
+ # control-key-file: "/etc/unbound/unbound_control.key"
+
+ # unbound-control certificate file.
+ # control-cert-file: "/etc/unbound/unbound_control.pem"
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/config_schema.json b/src/go/collectors/go.d.plugin/modules/unbound/config_schema.json
new file mode 100644
index 000000000..500b60169
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/config_schema.json
@@ -0,0 +1,113 @@
+{
+ "jsonSchema": {
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Unbound collector configuration.",
+ "type": "object",
+ "properties": {
+ "update_every": {
+ "title": "Update every",
+ "description": "Data collection interval, measured in seconds.",
+ "type": "integer",
+ "minimum": 1,
+ "default": 1
+ },
+ "address": {
+ "title": "Address",
+ "description": "The IP address and port where the Unbound server listens for connections.",
+ "type": "string",
+ "default": "127.0.0.1:8953"
+ },
+ "timeout": {
+ "title": "Timeout",
+ "description": "The timeout duration, in seconds, for connection, read, write, and SSL handshake operations.",
+ "type": "number",
+ "minimum": 0.5,
+ "default": 1
+ },
+ "conf_path": {
+ "title": "Path to unbound.conf",
+ "description": "The absolute path to the Unbound configuration file. Providing this path enables the tool to make adjustments based on the 'remote-control' section.",
+ "type": "string",
+ "default": "/etc/unbound/unbound.conf"
+ },
+ "cumulative_stats": {
+ "title": "Cumulative stats",
+ "description": "Specifies whether statistics collection mode is enabled. Should match the 'statistics-cumulative' parameter in unbound.conf.",
+ "type": "boolean",
+ "default": false
+ },
+ "use_tls": {
+ "title": "Use TLS",
+ "description": "Indicates whether TLS should be used for secure communication.",
+ "type": "boolean",
+ "default": true
+ },
+ "tls_skip_verify": {
+ "title": "Skip TLS verification",
+ "description": "If set, TLS certificate verification will be skipped.",
+ "type": "boolean",
+ "default": true
+ },
+ "tls_ca": {
+ "title": "TLS CA",
+ "description": "The path to the CA certificate file for TLS verification.",
+ "type": "string",
+ "pattern": "^$|^/"
+ },
+ "tls_cert": {
+ "title": "TLS certificate",
+ "description": "The path to the client certificate file for TLS authentication.",
+ "type": "string",
+ "default": "/etc/unbound/unbound_control.pem",
+ "pattern": "^$|^/"
+ },
+ "tls_key": {
+ "title": "TLS key",
+ "description": "The path to the client key file for TLS authentication.",
+ "type": "string",
+ "default": "/etc/unbound/unbound_control.key",
+ "pattern": "^$|^/"
+ }
+ },
+ "required": [
+ "address"
+ ],
+ "additionalProperties": false,
+ "patternProperties": {
+ "^name$": {}
+ }
+ },
+ "uiSchema": {
+ "uiOptions": {
+ "fullPage": true
+ },
+ "timeout": {
+ "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
+ },
+ "ui:flavour": "tabs",
+ "ui:options": {
+ "tabs": [
+ {
+ "title": "Base",
+ "fields": [
+ "update_every",
+ "address",
+ "timeout",
+ "conf_path",
+ "cumulative_stats"
+ ]
+ },
+ {
+ "title": "TLS",
+ "fields": [
+ "use_tls",
+ "tls_skip_verify",
+ "tls_ca",
+ "tls_cert",
+ "tls_key"
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/init.go b/src/go/collectors/go.d.plugin/modules/unbound/init.go
new file mode 100644
index 000000000..066315400
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/init.go
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package unbound
+
+import (
+ "crypto/tls"
+ "errors"
+ "net"
+
+ "github.com/netdata/netdata/go/go.d.plugin/modules/unbound/config"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/socket"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/tlscfg"
+)
+
+func (u *Unbound) initConfig() (enabled bool) {
+ if u.ConfPath == "" {
+ u.Info("'conf_path' not set, skipping parameters auto detection")
+ return true
+ }
+
+ u.Infof("reading '%s'", u.ConfPath)
+ cfg, err := config.Parse(u.ConfPath)
+ if err != nil {
+ u.Warningf("%v, skipping parameters auto detection", err)
+ return true
+ }
+
+ if cfg.Empty() {
+ u.Debug("empty configuration")
+ return true
+ }
+
+ if enabled, ok := cfg.ControlEnabled(); ok && !enabled {
+ u.Info("remote control is disabled in the configuration file")
+ return false
+ }
+
+ u.applyConfig(cfg)
+ return true
+}
+
+func (u *Unbound) applyConfig(cfg *config.UnboundConfig) {
+ u.Infof("applying configuration: %s", cfg)
+ if cumulative, ok := cfg.Cumulative(); ok && cumulative != u.Cumulative {
+ u.Debugf("changing 'cumulative_stats': %v => %v", u.Cumulative, cumulative)
+ u.Cumulative = cumulative
+ }
+ if useCert, ok := cfg.ControlUseCert(); ok && useCert != u.UseTLS {
+ u.Debugf("changing 'use_tls': %v => %v", u.UseTLS, useCert)
+ u.UseTLS = useCert
+ }
+ if keyFile, ok := cfg.ControlKeyFile(); ok && keyFile != u.TLSKey {
+ u.Debugf("changing 'tls_key': '%s' => '%s'", u.TLSKey, keyFile)
+ u.TLSKey = keyFile
+ }
+ if certFile, ok := cfg.ControlCertFile(); ok && certFile != u.TLSCert {
+ u.Debugf("changing 'tls_cert': '%s' => '%s'", u.TLSCert, certFile)
+ u.TLSCert = certFile
+ }
+ if iface, ok := cfg.ControlInterface(); ok && adjustControlInterface(iface) != u.Address {
+ address := adjustControlInterface(iface)
+ u.Debugf("changing 'address': '%s' => '%s'", u.Address, address)
+ u.Address = address
+ }
+ if port, ok := cfg.ControlPort(); ok && !socket.IsUnixSocket(u.Address) {
+ if host, curPort, err := net.SplitHostPort(u.Address); err == nil && curPort != port {
+ address := net.JoinHostPort(host, port)
+ u.Debugf("changing 'address': '%s' => '%s'", u.Address, address)
+ u.Address = address
+ }
+ }
+}
+
+func (u *Unbound) initClient() (err error) {
+ var tlsCfg *tls.Config
+ useTLS := !socket.IsUnixSocket(u.Address) && u.UseTLS
+
+ if useTLS && (u.TLSConfig.TLSCert == "" || u.TLSConfig.TLSKey == "") {
+ return errors.New("'tls_cert' or 'tls_key' is missing")
+ }
+
+ if useTLS {
+ if tlsCfg, err = tlscfg.NewTLSConfig(u.TLSConfig); err != nil {
+ return err
+ }
+ }
+
+ u.client = socket.New(socket.Config{
+ Address: u.Address,
+ ConnectTimeout: u.Timeout.Duration(),
+ ReadTimeout: u.Timeout.Duration(),
+ WriteTimeout: u.Timeout.Duration(),
+ TLSConf: tlsCfg,
+ })
+ return nil
+}
+
+func adjustControlInterface(value string) string {
+ if socket.IsUnixSocket(value) {
+ return value
+ }
+ if value == "0.0.0.0" {
+ value = "127.0.0.1"
+ }
+ return net.JoinHostPort(value, "8953")
+}
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/integrations/unbound.md b/src/go/collectors/go.d.plugin/modules/unbound/integrations/unbound.md
new file mode 100644
index 000000000..f934e6660
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/integrations/unbound.md
@@ -0,0 +1,270 @@
+<!--startmeta
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/go/collectors/go.d.plugin/modules/unbound/README.md"
+meta_yaml: "https://github.com/netdata/netdata/edit/master/src/go/collectors/go.d.plugin/modules/unbound/metadata.yaml"
+sidebar_label: "Unbound"
+learn_status: "Published"
+learn_rel_path: "Collecting Metrics/DNS and DHCP Servers"
+most_popular: False
+message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE"
+endmeta-->
+
+# Unbound
+
+
+<img src="https://netdata.cloud/img/unbound.png" width="150"/>
+
+
+Plugin: go.d.plugin
+Module: unbound
+
+<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
+
+## Overview
+
+This collector monitors Unbound servers.
+
+
+
+
+This collector is supported on all platforms.
+
+This collector supports collecting metrics from multiple instances of this integration, including remote instances.
+
+
+### Default Behavior
+
+#### Auto-Detection
+
+This integration doesn't support auto-detection.
+
+#### Limits
+
+The default configuration for this integration does not impose any limits on data collection.
+
+#### Performance Impact
+
+The default configuration for this integration is not expected to impose a significant performance impact on the system.
+
+
+## Metrics
+
+Metrics grouped by *scope*.
+
+The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels.
+
+
+
+### Per Unbound instance
+
+These metrics refer to the entire monitored application.
+
+This scope has no labels.
+
+Metrics:
+
+| Metric | Dimensions | Unit |
+|:------|:----------|:----|
+| unbound.queries | queries | queries |
+| unbound.queries_ip_ratelimited | ratelimited | queries |
+| unbound.dnscrypt_queries | crypted, cert, cleartext, malformed | queries |
+| unbound.cache | hits, miss | events |
+| unbound.cache_percentage | hits, miss | percentage |
+| unbound.prefetch | prefetches | prefetches |
+| unbound.expired | expired | replies |
+| unbound.zero_ttl_replies | zero_ttl | replies |
+| unbound.recursive_replies | recursive | replies |
+| unbound.recursion_time | avg, median | milliseconds |
+| unbound.request_list_usage | avg, max | queries |
+| unbound.current_request_list_usage | all, users | queries |
+| unbound.request_list_jostle_list | overwritten, dropped | queries |
+| unbound.tcpusage | usage | buffers |
+| unbound.uptime | time | seconds |
+| unbound.cache_memory | message, rrset, dnscrypt_nonce, dnscrypt_shared_secret | KB |
+| unbound.mod_memory | iterator, respip, validator, subnet, ipsec | KB |
+| unbound.mem_streamwait | streamwait | KB |
+| unbound.cache_count | infra, key, msg, rrset, dnscrypt_nonce, shared_secret | items |
+| unbound.type_queries | a dimension per query type | queries |
+| unbound.class_queries | a dimension per query class | queries |
+| unbound.opcode_queries | a dimension per query opcode | queries |
+| unbound.flag_queries | qr, aa, tc, rd, ra, z, ad, cd | queries |
+| unbound.rcode_answers | a dimension per reply rcode | replies |
+
+### Per thread
+
+These metrics refer to threads.
+
+This scope has no labels.
+
+Metrics:
+
+| Metric | Dimensions | Unit |
+|:------|:----------|:----|
+| unbound.thread_queries | queries | queries |
+| unbound.thread_queries_ip_ratelimited | ratelimited | queries |
+| unbound.thread_dnscrypt_queries | crypted, cert, cleartext, malformed | queries |
+| unbound.thread_cache | hits, miss | events |
+| unbound.thread_cache_percentage | hits, miss | percentage |
+| unbound.thread_prefetch | prefetches | prefetches |
+| unbound.thread_expired | expired | replies |
+| unbound.thread_zero_ttl_replies | zero_ttl | replies |
+| unbound.thread_recursive_replies | recursive | replies |
+| unbound.thread_recursion_time | avg, median | milliseconds |
+| unbound.thread_request_list_usage | avg, max | queries |
+| unbound.thread_current_request_list_usage | all, users | queries |
+| unbound.thread_request_list_jostle_list | overwritten, dropped | queries |
+| unbound.thread_tcpusage | usage | buffers |
+
+
+
+## Alerts
+
+There are no alerts configured by default for this integration.
+
+
+## Setup
+
+### Prerequisites
+
+#### Enable remote control interface
+
+Set `control-enable` to yes in [unbound.conf](https://nlnetlabs.nl/documentation/unbound/unbound.conf).
+
+
+#### Check permissions and adjust if necessary
+
+If using unix socket:
+
+- socket should be readable and writeable by `netdata` user
+
+If using ip socket and TLS is disabled:
+
+- socket should be accessible via network
+
+If TLS is enabled, in addition:
+
+- `control-key-file` should be readable by `netdata` user
+- `control-cert-file` should be readable by `netdata` user
+
+For auto-detection parameters from `unbound.conf`:
+
+- `unbound.conf` should be readable by `netdata` user
+- if you have several configuration files (include feature) all of them should be readable by `netdata` user
+
+
+
+### Configuration
+
+#### File
+
+The configuration file name for this integration is `go.d/unbound.conf`.
+
+
+You can edit the configuration file using the `edit-config` script from the
+Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory).
+
+```bash
+cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
+sudo ./edit-config go.d/unbound.conf
+```
+#### Options
+
+The following options can be defined globally: update_every, autodetection_retry.
+
+
+<details open><summary>Config options</summary>
+
+| Name | Description | Default | Required |
+|:----|:-----------|:-------|:--------:|
+| update_every | Data collection frequency. | 5 | no |
+| autodetection_retry | Recheck interval in seconds. Zero means no recheck will be scheduled. | 0 | no |
+| address | Server address in IP:PORT format. | 127.0.0.1:8953 | yes |
+| timeout | Connection/read/write/ssl handshake timeout. | 1 | no |
+| conf_path | Absolute path to the unbound configuration file. | /etc/unbound/unbound.conf | no |
+| cumulative_stats | Statistics collection mode. Should have the same value as the `statistics-cumulative` parameter in the unbound configuration file. | no | no |
+| use_tls | Whether to use TLS or not. | yes | no |
+| tls_skip_verify | Server certificate chain and hostname validation policy. Controls whether the client performs this check. | yes | no |
+| tls_ca | Certificate authority that client use when verifying server certificates. | | no |
+| tls_cert | Client tls certificate. | /etc/unbound/unbound_control.pem | no |
+| tls_key | Client tls key. | /etc/unbound/unbound_control.key | no |
+
+</details>
+
+#### Examples
+
+##### Basic
+
+An example configuration.
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: local
+ address: 127.0.0.1:8953
+
+```
+</details>
+
+##### Unix socket
+
+Connecting through Unix socket.
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: socket
+ address: /var/run/unbound.sock
+
+```
+</details>
+
+##### Multi-instance
+
+> **Note**: When you define multiple jobs, their names must be unique.
+
+Local and remote instances.
+
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: local
+ address: 127.0.0.1:8953
+
+ - name: remote
+ address: 203.0.113.11:8953
+
+```
+</details>
+
+
+
+## Troubleshooting
+
+### Debug Mode
+
+To troubleshoot issues with the `unbound` collector, run the `go.d.plugin` with the debug option enabled. The output
+should give you clues as to why the collector isn't working.
+
+- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on
+ your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`.
+
+ ```bash
+ cd /usr/libexec/netdata/plugins.d/
+ ```
+
+- Switch to the `netdata` user.
+
+ ```bash
+ sudo -u netdata -s
+ ```
+
+- Run the `go.d.plugin` to debug the collector:
+
+ ```bash
+ ./go.d.plugin -d -m unbound
+ ```
+
+
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/metadata.yaml b/src/go/collectors/go.d.plugin/modules/unbound/metadata.yaml
new file mode 100644
index 000000000..ec6e6538d
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/metadata.yaml
@@ -0,0 +1,431 @@
+plugin_name: go.d.plugin
+modules:
+ - meta:
+ id: collector-go.d.plugin-unbound
+ plugin_name: go.d.plugin
+ module_name: unbound
+ monitored_instance:
+ name: Unbound
+ link: https://nlnetlabs.nl/projects/unbound/about/
+ icon_filename: unbound.png
+ categories:
+ - data-collection.dns-and-dhcp-servers
+ keywords:
+ - unbound
+ - dns
+ related_resources:
+ integrations:
+ list: []
+ info_provided_to_referring_integrations:
+ description: ""
+ most_popular: false
+ overview:
+ data_collection:
+ metrics_description: |
+ This collector monitors Unbound servers.
+ method_description: ""
+ supported_platforms:
+ include: []
+ exclude: []
+ multi_instance: true
+ additional_permissions:
+ description: ""
+ default_behavior:
+ auto_detection:
+ description: ""
+ limits:
+ description: ""
+ performance_impact:
+ description: ""
+ setup:
+ prerequisites:
+ list:
+ - title: Enable remote control interface
+ description: |
+ Set `control-enable` to yes in [unbound.conf](https://nlnetlabs.nl/documentation/unbound/unbound.conf).
+ - title: Check permissions and adjust if necessary
+ description: |
+ If using unix socket:
+
+ - socket should be readable and writeable by `netdata` user
+
+ If using ip socket and TLS is disabled:
+
+ - socket should be accessible via network
+
+ If TLS is enabled, in addition:
+
+ - `control-key-file` should be readable by `netdata` user
+ - `control-cert-file` should be readable by `netdata` user
+
+ For auto-detection parameters from `unbound.conf`:
+
+ - `unbound.conf` should be readable by `netdata` user
+ - if you have several configuration files (include feature) all of them should be readable by `netdata` user
+ configuration:
+ file:
+ name: go.d/unbound.conf
+ options:
+ description: |
+ The following options can be defined globally: update_every, autodetection_retry.
+ folding:
+ title: Config options
+ enabled: true
+ list:
+ - name: update_every
+ description: Data collection frequency.
+ default_value: 5
+ required: false
+ - name: autodetection_retry
+ description: Recheck interval in seconds. Zero means no recheck will be scheduled.
+ default_value: 0
+ required: false
+ - name: address
+ description: Server address in IP:PORT format.
+ default_value: 127.0.0.1:8953
+ required: true
+ - name: timeout
+ description: Connection/read/write/ssl handshake timeout.
+ default_value: 1
+ required: false
+ - name: conf_path
+ description: Absolute path to the unbound configuration file.
+ default_value: /etc/unbound/unbound.conf
+ required: false
+ - name: cumulative_stats
+ description: Statistics collection mode. Should have the same value as the `statistics-cumulative` parameter in the unbound configuration file.
+ default_value: false
+ required: false
+ - name: use_tls
+ description: Whether to use TLS or not.
+ default_value: true
+ required: false
+ - name: tls_skip_verify
+ description: Server certificate chain and hostname validation policy. Controls whether the client performs this check.
+ default_value: true
+ required: false
+ - name: tls_ca
+ description: Certificate authority that client use when verifying server certificates.
+ default_value: ""
+ required: false
+ - name: tls_cert
+ description: Client tls certificate.
+ default_value: /etc/unbound/unbound_control.pem
+ required: false
+ - name: tls_key
+ description: Client tls key.
+ default_value: /etc/unbound/unbound_control.key
+ required: false
+ examples:
+ folding:
+ title: Config
+ enabled: true
+ list:
+ - name: Basic
+ description: An example configuration.
+ config: |
+ jobs:
+ - name: local
+ address: 127.0.0.1:8953
+ - name: Unix socket
+ description: Connecting through Unix socket.
+ config: |
+ jobs:
+ - name: socket
+ address: /var/run/unbound.sock
+ - name: Multi-instance
+ description: |
+ > **Note**: When you define multiple jobs, their names must be unique.
+
+ Local and remote instances.
+ config: |
+ jobs:
+ - name: local
+ address: 127.0.0.1:8953
+
+ - name: remote
+ address: 203.0.113.11:8953
+ troubleshooting:
+ problems:
+ list: []
+ alerts: []
+ metrics:
+ folding:
+ title: Metrics
+ enabled: false
+ description: ""
+ availability: []
+ scopes:
+ - name: global
+ description: These metrics refer to the entire monitored application.
+ labels: []
+ metrics:
+ - name: unbound.queries
+ description: Received Queries
+ unit: queries
+ chart_type: line
+ dimensions:
+ - name: queries
+ - name: unbound.queries_ip_ratelimited
+ description: Rate Limited Queries
+ unit: queries
+ chart_type: line
+ dimensions:
+ - name: ratelimited
+ - name: unbound.dnscrypt_queries
+ description: DNSCrypt Queries
+ unit: queries
+ chart_type: line
+ dimensions:
+ - name: crypted
+ - name: cert
+ - name: cleartext
+ - name: malformed
+ - name: unbound.cache
+ description: Cache Statistics
+ unit: events
+ chart_type: stacked
+ dimensions:
+ - name: hits
+ - name: miss
+ - name: unbound.cache_percentage
+ description: Cache Statistics Percentage
+ unit: percentage
+ chart_type: stacked
+ dimensions:
+ - name: hits
+ - name: miss
+ - name: unbound.prefetch
+ description: Cache Prefetches
+ unit: prefetches
+ chart_type: line
+ dimensions:
+ - name: prefetches
+ - name: unbound.expired
+ description: Replies Served From Expired Cache
+ unit: replies
+ chart_type: line
+ dimensions:
+ - name: expired
+ - name: unbound.zero_ttl_replies
+ description: Replies Served From Expired Cache
+ unit: replies
+ chart_type: line
+ dimensions:
+ - name: zero_ttl
+ - name: unbound.recursive_replies
+ description: Replies That Needed Recursive Processing
+ unit: replies
+ chart_type: line
+ dimensions:
+ - name: recursive
+ - name: unbound.recursion_time
+ description: Time Spent On Recursive Processing
+ unit: milliseconds
+ chart_type: line
+ dimensions:
+ - name: avg
+ - name: median
+ - name: unbound.request_list_usage
+ description: Request List Usage
+ unit: queries
+ chart_type: line
+ dimensions:
+ - name: avg
+ - name: max
+ - name: unbound.current_request_list_usage
+ description: Current Request List Usage
+ unit: queries
+ chart_type: area
+ dimensions:
+ - name: all
+ - name: users
+ - name: unbound.request_list_jostle_list
+ description: Request List Jostle List Events
+ unit: queries
+ chart_type: line
+ dimensions:
+ - name: overwritten
+ - name: dropped
+ - name: unbound.tcpusage
+ description: TCP Handler Buffers
+ unit: buffers
+ chart_type: line
+ dimensions:
+ - name: usage
+ - name: unbound.uptime
+ description: Uptime
+ unit: seconds
+ chart_type: line
+ dimensions:
+ - name: time
+ - name: unbound.cache_memory
+ description: Cache Memory
+ unit: KB
+ chart_type: stacked
+ dimensions:
+ - name: message
+ - name: rrset
+ - name: dnscrypt_nonce
+ - name: dnscrypt_shared_secret
+ - name: unbound.mod_memory
+ description: Module Memory
+ unit: KB
+ chart_type: stacked
+ dimensions:
+ - name: iterator
+ - name: respip
+ - name: validator
+ - name: subnet
+ - name: ipsec
+ - name: unbound.mem_streamwait
+ description: TCP and TLS Stream Waif Buffer Memory
+ unit: KB
+ chart_type: line
+ dimensions:
+ - name: streamwait
+ - name: unbound.cache_count
+ description: Cache Items Count
+ unit: items
+ chart_type: stacked
+ dimensions:
+ - name: infra
+ - name: key
+ - name: msg
+ - name: rrset
+ - name: dnscrypt_nonce
+ - name: shared_secret
+ - name: unbound.type_queries
+ description: Queries By Type
+ unit: queries
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per query type
+ - name: unbound.class_queries
+ description: Queries By Class
+ unit: queries
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per query class
+ - name: unbound.opcode_queries
+ description: Queries By OpCode
+ unit: queries
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per query opcode
+ - name: unbound.flag_queries
+ description: Queries By Flag
+ unit: queries
+ chart_type: stacked
+ dimensions:
+ - name: qr
+ - name: aa
+ - name: tc
+ - name: rd
+ - name: ra
+ - name: z
+ - name: ad
+ - name: cd
+ - name: unbound.rcode_answers
+ description: Replies By RCode
+ unit: replies
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per reply rcode
+ - name: thread
+ description: These metrics refer to threads.
+ labels: []
+ metrics:
+ - name: unbound.thread_queries
+ description: Thread Received Queries
+ unit: queries
+ chart_type: line
+ dimensions:
+ - name: queries
+ - name: unbound.thread_queries_ip_ratelimited
+ description: Thread Rate Limited Queries
+ unit: queries
+ chart_type: line
+ dimensions:
+ - name: ratelimited
+ - name: unbound.thread_dnscrypt_queries
+ description: Thread DNSCrypt Queries
+ unit: queries
+ chart_type: line
+ dimensions:
+ - name: crypted
+ - name: cert
+ - name: cleartext
+ - name: malformed
+ - name: unbound.thread_cache
+ description: Cache Statistics
+ unit: events
+ chart_type: line
+ dimensions:
+ - name: hits
+ - name: miss
+ - name: unbound.thread_cache_percentage
+ description: Cache Statistics Percentage
+ unit: percentage
+ chart_type: line
+ dimensions:
+ - name: hits
+ - name: miss
+ - name: unbound.thread_prefetch
+ description: Cache Prefetches
+ unit: prefetches
+ chart_type: line
+ dimensions:
+ - name: prefetches
+ - name: unbound.thread_expired
+ description: Replies Served From Expired Cache
+ unit: replies
+ chart_type: line
+ dimensions:
+ - name: expired
+ - name: unbound.thread_zero_ttl_replies
+ description: Replies Served From Expired Cache
+ unit: replies
+ chart_type: line
+ dimensions:
+ - name: zero_ttl
+ - name: unbound.thread_recursive_replies
+ description: Replies That Needed Recursive Processing
+ unit: replies
+ chart_type: line
+ dimensions:
+ - name: recursive
+ - name: unbound.thread_recursion_time
+ description: Time Spent On Recursive Processing
+ unit: milliseconds
+ chart_type: line
+ dimensions:
+ - name: avg
+ - name: median
+ - name: unbound.thread_request_list_usage
+ description: Time Spent On Recursive Processing
+ unit: queries
+ chart_type: line
+ dimensions:
+ - name: avg
+ - name: max
+ - name: unbound.thread_current_request_list_usage
+ description: Current Request List Usage
+ unit: queries
+ chart_type: line
+ dimensions:
+ - name: all
+ - name: users
+ - name: unbound.thread_request_list_jostle_list
+ description: Request List Jostle List Events
+ unit: queries
+ chart_type: line
+ dimensions:
+ - name: overwritten
+ - name: dropped
+ - name: unbound.thread_tcpusage
+ description: TCP Handler Buffers
+ unit: buffers
+ chart_type: line
+ dimensions:
+ - name: usage
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/testdata/config.json b/src/go/collectors/go.d.plugin/modules/unbound/testdata/config.json
new file mode 100644
index 000000000..9874de180
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/testdata/config.json
@@ -0,0 +1,12 @@
+{
+ "update_every": 123,
+ "address": "ok",
+ "conf_path": "ok",
+ "timeout": 123.123,
+ "cumulative_stats": true,
+ "use_tls": true,
+ "tls_ca": "ok",
+ "tls_cert": "ok",
+ "tls_key": "ok",
+ "tls_skip_verify": true
+}
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/testdata/config.yaml b/src/go/collectors/go.d.plugin/modules/unbound/testdata/config.yaml
new file mode 100644
index 000000000..68326cabc
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/testdata/config.yaml
@@ -0,0 +1,10 @@
+update_every: 123
+address: "ok"
+conf_path: "ok"
+timeout: 123.123
+cumulative_stats: yes
+use_tls: yes
+tls_ca: "ok"
+tls_cert: "ok"
+tls_key: "ok"
+tls_skip_verify: yes
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/common.txt b/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/common.txt
new file mode 100644
index 000000000..7a1f91a31
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/common.txt
@@ -0,0 +1,66 @@
+thread0.num.queries=28
+thread0.num.queries_ip_ratelimited=0
+thread0.num.cachehits=21
+thread0.num.cachemiss=7
+thread0.num.prefetch=0
+thread0.num.expired=0
+thread0.num.zero_ttl=0
+thread0.num.recursivereplies=7
+thread0.num.dnscrypt.crypted=0
+thread0.num.dnscrypt.cert=0
+thread0.num.dnscrypt.cleartext=0
+thread0.num.dnscrypt.malformed=0
+thread0.requestlist.avg=0.857143
+thread0.requestlist.max=6
+thread0.requestlist.overwritten=0
+thread0.requestlist.exceeded=0
+thread0.requestlist.current.all=0
+thread0.requestlist.current.user=0
+thread0.recursion.time.avg=1.255822
+thread0.recursion.time.median=0.480597
+thread0.tcpusage=0
+thread1.num.queries=16
+thread1.num.queries_ip_ratelimited=0
+thread1.num.cachehits=13
+thread1.num.cachemiss=3
+thread1.num.prefetch=0
+thread1.num.expired=0
+thread1.num.zero_ttl=0
+thread1.num.recursivereplies=3
+thread1.num.dnscrypt.crypted=0
+thread1.num.dnscrypt.cert=0
+thread1.num.dnscrypt.cleartext=0
+thread1.num.dnscrypt.malformed=0
+thread1.requestlist.avg=0
+thread1.requestlist.max=0
+thread1.requestlist.overwritten=0
+thread1.requestlist.exceeded=0
+thread1.requestlist.current.all=0
+thread1.requestlist.current.user=0
+thread1.recursion.time.avg=0.093941
+thread1.recursion.time.median=0
+thread1.tcpusage=0
+total.num.queries=44
+total.num.queries_ip_ratelimited=0
+total.num.cachehits=34
+total.num.cachemiss=10
+total.num.prefetch=0
+total.num.expired=0
+total.num.zero_ttl=0
+total.num.recursivereplies=10
+total.num.dnscrypt.crypted=0
+total.num.dnscrypt.cert=0
+total.num.dnscrypt.cleartext=0
+total.num.dnscrypt.malformed=0
+total.requestlist.avg=0.6
+total.requestlist.max=6
+total.requestlist.overwritten=0
+total.requestlist.exceeded=0
+total.requestlist.current.all=0
+total.requestlist.current.user=0
+total.recursion.time.avg=0.907258
+total.recursion.time.median=0.240299
+total.tcpusage=0
+time.now=1574094836.941149
+time.up=88.434983
+time.elapsed=88.4349831 \ No newline at end of file
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/extended.txt b/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/extended.txt
new file mode 100644
index 000000000..578794fad
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/extended.txt
@@ -0,0 +1,162 @@
+thread0.num.queries=28
+thread0.num.queries_ip_ratelimited=0
+thread0.num.cachehits=21
+thread0.num.cachemiss=7
+thread0.num.prefetch=0
+thread0.num.expired=0
+thread0.num.zero_ttl=0
+thread0.num.recursivereplies=7
+thread0.num.dnscrypt.crypted=0
+thread0.num.dnscrypt.cert=0
+thread0.num.dnscrypt.cleartext=0
+thread0.num.dnscrypt.malformed=0
+thread0.requestlist.avg=0.857143
+thread0.requestlist.max=6
+thread0.requestlist.overwritten=0
+thread0.requestlist.exceeded=0
+thread0.requestlist.current.all=0
+thread0.requestlist.current.user=0
+thread0.recursion.time.avg=1.255822
+thread0.recursion.time.median=0.480597
+thread0.tcpusage=0
+thread1.num.queries=16
+thread1.num.queries_ip_ratelimited=0
+thread1.num.cachehits=13
+thread1.num.cachemiss=3
+thread1.num.prefetch=0
+thread1.num.expired=0
+thread1.num.zero_ttl=0
+thread1.num.recursivereplies=3
+thread1.num.dnscrypt.crypted=0
+thread1.num.dnscrypt.cert=0
+thread1.num.dnscrypt.cleartext=0
+thread1.num.dnscrypt.malformed=0
+thread1.requestlist.avg=0
+thread1.requestlist.max=0
+thread1.requestlist.overwritten=0
+thread1.requestlist.exceeded=0
+thread1.requestlist.current.all=0
+thread1.requestlist.current.user=0
+thread1.recursion.time.avg=0.093941
+thread1.recursion.time.median=0
+thread1.tcpusage=0
+total.num.queries=44
+total.num.queries_ip_ratelimited=0
+total.num.cachehits=34
+total.num.cachemiss=10
+total.num.prefetch=0
+total.num.expired=0
+total.num.zero_ttl=0
+total.num.recursivereplies=10
+total.num.dnscrypt.crypted=0
+total.num.dnscrypt.cert=0
+total.num.dnscrypt.cleartext=0
+total.num.dnscrypt.malformed=0
+total.requestlist.avg=0.6
+total.requestlist.max=6
+total.requestlist.overwritten=0
+total.requestlist.exceeded=0
+total.requestlist.current.all=0
+total.requestlist.current.user=0
+total.recursion.time.avg=0.907258
+total.recursion.time.median=0.240299
+total.tcpusage=0
+time.now=1574094836.941149
+time.up=88.434983
+time.elapsed=88.434983
+mem.cache.rrset=178642
+mem.cache.message=90357
+mem.mod.iterator=16588
+mem.mod.validator=81059
+mem.mod.respip=0
+mem.mod.subnet=74504
+mem.cache.dnscrypt_shared_secret=0
+mem.cache.dnscrypt_nonce=0
+mem.streamwait=0
+histogram.000000.000000.to.000000.000001=0
+histogram.000000.000001.to.000000.000002=0
+histogram.000000.000002.to.000000.000004=0
+histogram.000000.000004.to.000000.000008=0
+histogram.000000.000008.to.000000.000016=0
+histogram.000000.000016.to.000000.000032=0
+histogram.000000.000032.to.000000.000064=0
+histogram.000000.000064.to.000000.000128=0
+histogram.000000.000128.to.000000.000256=0
+histogram.000000.000256.to.000000.000512=0
+histogram.000000.000512.to.000000.001024=0
+histogram.000000.001024.to.000000.002048=0
+histogram.000000.002048.to.000000.004096=0
+histogram.000000.004096.to.000000.008192=0
+histogram.000000.008192.to.000000.016384=0
+histogram.000000.016384.to.000000.032768=0
+histogram.000000.032768.to.000000.065536=2
+histogram.000000.065536.to.000000.131072=0
+histogram.000000.131072.to.000000.262144=2
+histogram.000000.262144.to.000000.524288=3
+histogram.000000.524288.to.000001.000000=2
+histogram.000001.000000.to.000002.000000=0
+histogram.000002.000000.to.000004.000000=0
+histogram.000004.000000.to.000008.000000=1
+histogram.000008.000000.to.000016.000000=0
+histogram.000016.000000.to.000032.000000=0
+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=13
+num.query.type.PTR=5
+num.query.type.MX=13
+num.query.type.AAAA=13
+num.query.class.IN=44
+num.query.opcode.QUERY=44
+num.query.tcp=0
+num.query.tcpout=1
+num.query.tls=0
+num.query.tls.resume=0
+num.query.ipv6=39
+num.query.flags.QR=0
+num.query.flags.AA=0
+num.query.flags.TC=0
+num.query.flags.RD=44
+num.query.flags.RA=0
+num.query.flags.Z=0
+num.query.flags.AD=0
+num.query.flags.CD=0
+num.query.edns.present=0
+num.query.edns.DO=0
+num.answer.rcode.NOERROR=40
+num.answer.rcode.FORMERR=0
+num.answer.rcode.SERVFAIL=0
+num.answer.rcode.NXDOMAIN=4
+num.answer.rcode.NOTIMPL=0
+num.answer.rcode.REFUSED=0
+num.query.ratelimited=0
+num.answer.secure=0
+num.answer.bogus=0
+num.rrset.bogus=0
+num.query.aggressive.NOERROR=2
+num.query.aggressive.NXDOMAIN=0
+unwanted.queries=0
+unwanted.replies=0
+msg.cache.count=81
+rrset.cache.count=314
+infra.cache.count=205
+key.cache.count=9
+dnscrypt_shared_secret.cache.count=0
+dnscrypt_nonce.cache.count=0
+num.query.dnscrypt.shared_secret.cachemiss=0
+num.query.dnscrypt.replay=0
+num.query.authzone.up=0
+num.query.authzone.down=0
+num.query.subnet=0
+num.query.subnet_cache=0 \ No newline at end of file
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/cumulative/extended1.txt b/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/cumulative/extended1.txt
new file mode 100644
index 000000000..53bd7f955
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/cumulative/extended1.txt
@@ -0,0 +1,162 @@
+thread0.num.queries=90
+thread0.num.queries_ip_ratelimited=0
+thread0.num.cachehits=80
+thread0.num.cachemiss=10
+thread0.num.prefetch=0
+thread0.num.expired=0
+thread0.num.zero_ttl=0
+thread0.num.recursivereplies=10
+thread0.num.dnscrypt.crypted=0
+thread0.num.dnscrypt.cert=0
+thread0.num.dnscrypt.cleartext=0
+thread0.num.dnscrypt.malformed=0
+thread0.requestlist.avg=0.1
+thread0.requestlist.max=1
+thread0.requestlist.overwritten=0
+thread0.requestlist.exceeded=0
+thread0.requestlist.current.all=0
+thread0.requestlist.current.user=0
+thread0.recursion.time.avg=0.222018
+thread0.recursion.time.median=0.337042
+thread0.tcpusage=0
+thread1.num.queries=110
+thread1.num.queries_ip_ratelimited=0
+thread1.num.cachehits=101
+thread1.num.cachemiss=9
+thread1.num.prefetch=0
+thread1.num.expired=0
+thread1.num.zero_ttl=0
+thread1.num.recursivereplies=9
+thread1.num.dnscrypt.crypted=0
+thread1.num.dnscrypt.cert=0
+thread1.num.dnscrypt.cleartext=0
+thread1.num.dnscrypt.malformed=0
+thread1.requestlist.avg=0.222222
+thread1.requestlist.max=1
+thread1.requestlist.overwritten=0
+thread1.requestlist.exceeded=0
+thread1.requestlist.current.all=0
+thread1.requestlist.current.user=0
+thread1.recursion.time.avg=0.844506
+thread1.recursion.time.median=0.360448
+thread1.tcpusage=0
+total.num.queries=200
+total.num.queries_ip_ratelimited=0
+total.num.cachehits=181
+total.num.cachemiss=19
+total.num.prefetch=0
+total.num.expired=0
+total.num.zero_ttl=0
+total.num.recursivereplies=19
+total.num.dnscrypt.crypted=0
+total.num.dnscrypt.cert=0
+total.num.dnscrypt.cleartext=0
+total.num.dnscrypt.malformed=0
+total.requestlist.avg=0.157895
+total.requestlist.max=1
+total.requestlist.overwritten=0
+total.requestlist.exceeded=0
+total.requestlist.current.all=0
+total.requestlist.current.user=0
+total.recursion.time.avg=0.516881
+total.recursion.time.median=0.348745
+total.tcpusage=0
+time.now=1574103378.552596
+time.up=122.956436
+time.elapsed=122.956436
+mem.cache.rrset=175745
+mem.cache.message=93392
+mem.mod.iterator=16588
+mem.mod.validator=81479
+mem.mod.respip=0
+mem.mod.subnet=74504
+mem.cache.dnscrypt_shared_secret=0
+mem.cache.dnscrypt_nonce=0
+mem.streamwait=0
+histogram.000000.000000.to.000000.000001=0
+histogram.000000.000001.to.000000.000002=0
+histogram.000000.000002.to.000000.000004=0
+histogram.000000.000004.to.000000.000008=0
+histogram.000000.000008.to.000000.000016=0
+histogram.000000.000016.to.000000.000032=0
+histogram.000000.000032.to.000000.000064=0
+histogram.000000.000064.to.000000.000128=0
+histogram.000000.000128.to.000000.000256=0
+histogram.000000.000256.to.000000.000512=0
+histogram.000000.000512.to.000000.001024=0
+histogram.000000.001024.to.000000.002048=0
+histogram.000000.002048.to.000000.004096=0
+histogram.000000.004096.to.000000.008192=0
+histogram.000000.008192.to.000000.016384=2
+histogram.000000.016384.to.000000.032768=1
+histogram.000000.032768.to.000000.065536=3
+histogram.000000.065536.to.000000.131072=0
+histogram.000000.131072.to.000000.262144=0
+histogram.000000.262144.to.000000.524288=11
+histogram.000000.524288.to.000001.000000=0
+histogram.000001.000000.to.000002.000000=1
+histogram.000002.000000.to.000004.000000=0
+histogram.000004.000000.to.000008.000000=1
+histogram.000008.000000.to.000016.000000=0
+histogram.000016.000000.to.000032.000000=0
+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=60
+num.query.type.PTR=20
+num.query.type.MX=60
+num.query.type.AAAA=60
+num.query.class.IN=200
+num.query.opcode.QUERY=200
+num.query.tcp=0
+num.query.tcpout=0
+num.query.tls=0
+num.query.tls.resume=0
+num.query.ipv6=0
+num.query.flags.QR=0
+num.query.flags.AA=0
+num.query.flags.TC=0
+num.query.flags.RD=200
+num.query.flags.RA=0
+num.query.flags.Z=0
+num.query.flags.AD=0
+num.query.flags.CD=0
+num.query.edns.present=0
+num.query.edns.DO=0
+num.answer.rcode.NOERROR=184
+num.answer.rcode.FORMERR=0
+num.answer.rcode.SERVFAIL=0
+num.answer.rcode.NXDOMAIN=16
+num.answer.rcode.NOTIMPL=0
+num.answer.rcode.REFUSED=0
+num.query.ratelimited=0
+num.answer.secure=0
+num.answer.bogus=0
+num.rrset.bogus=0
+num.query.aggressive.NOERROR=1
+num.query.aggressive.NXDOMAIN=0
+unwanted.queries=0
+unwanted.replies=0
+msg.cache.count=94
+rrset.cache.count=304
+infra.cache.count=192
+key.cache.count=11
+dnscrypt_shared_secret.cache.count=0
+dnscrypt_nonce.cache.count=0
+num.query.dnscrypt.shared_secret.cachemiss=0
+num.query.dnscrypt.replay=0
+num.query.authzone.up=0
+num.query.authzone.down=0
+num.query.subnet=0
+num.query.subnet_cache=0 \ No newline at end of file
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/cumulative/extended2.txt b/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/cumulative/extended2.txt
new file mode 100644
index 000000000..939ba75de
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/cumulative/extended2.txt
@@ -0,0 +1,162 @@
+thread0.num.queries=133
+thread0.num.queries_ip_ratelimited=0
+thread0.num.cachehits=123
+thread0.num.cachemiss=10
+thread0.num.prefetch=0
+thread0.num.expired=0
+thread0.num.zero_ttl=0
+thread0.num.recursivereplies=10
+thread0.num.dnscrypt.crypted=0
+thread0.num.dnscrypt.cert=0
+thread0.num.dnscrypt.cleartext=0
+thread0.num.dnscrypt.malformed=0
+thread0.requestlist.avg=0.1
+thread0.requestlist.max=1
+thread0.requestlist.overwritten=0
+thread0.requestlist.exceeded=0
+thread0.requestlist.current.all=0
+thread0.requestlist.current.user=0
+thread0.recursion.time.avg=0.222018
+thread0.recursion.time.median=0.337042
+thread0.tcpusage=0
+thread1.num.queries=157
+thread1.num.queries_ip_ratelimited=0
+thread1.num.cachehits=148
+thread1.num.cachemiss=9
+thread1.num.prefetch=0
+thread1.num.expired=0
+thread1.num.zero_ttl=0
+thread1.num.recursivereplies=9
+thread1.num.dnscrypt.crypted=0
+thread1.num.dnscrypt.cert=0
+thread1.num.dnscrypt.cleartext=0
+thread1.num.dnscrypt.malformed=0
+thread1.requestlist.avg=0.222222
+thread1.requestlist.max=1
+thread1.requestlist.overwritten=0
+thread1.requestlist.exceeded=0
+thread1.requestlist.current.all=0
+thread1.requestlist.current.user=0
+thread1.recursion.time.avg=0.844506
+thread1.recursion.time.median=0.360448
+thread1.tcpusage=0
+total.num.queries=290
+total.num.queries_ip_ratelimited=0
+total.num.cachehits=271
+total.num.cachemiss=19
+total.num.prefetch=0
+total.num.expired=0
+total.num.zero_ttl=0
+total.num.recursivereplies=19
+total.num.dnscrypt.crypted=0
+total.num.dnscrypt.cert=0
+total.num.dnscrypt.cleartext=0
+total.num.dnscrypt.malformed=0
+total.requestlist.avg=0.157895
+total.requestlist.max=1
+total.requestlist.overwritten=0
+total.requestlist.exceeded=0
+total.requestlist.current.all=0
+total.requestlist.current.user=0
+total.recursion.time.avg=0.516881
+total.recursion.time.median=0.348745
+total.tcpusage=0
+time.now=1574103461.161540
+time.up=205.565380
+time.elapsed=82.608944
+mem.cache.rrset=175745
+mem.cache.message=93392
+mem.mod.iterator=16588
+mem.mod.validator=81479
+mem.mod.respip=0
+mem.mod.subnet=74504
+mem.cache.dnscrypt_shared_secret=0
+mem.cache.dnscrypt_nonce=0
+mem.streamwait=0
+histogram.000000.000000.to.000000.000001=0
+histogram.000000.000001.to.000000.000002=0
+histogram.000000.000002.to.000000.000004=0
+histogram.000000.000004.to.000000.000008=0
+histogram.000000.000008.to.000000.000016=0
+histogram.000000.000016.to.000000.000032=0
+histogram.000000.000032.to.000000.000064=0
+histogram.000000.000064.to.000000.000128=0
+histogram.000000.000128.to.000000.000256=0
+histogram.000000.000256.to.000000.000512=0
+histogram.000000.000512.to.000000.001024=0
+histogram.000000.001024.to.000000.002048=0
+histogram.000000.002048.to.000000.004096=0
+histogram.000000.004096.to.000000.008192=0
+histogram.000000.008192.to.000000.016384=2
+histogram.000000.016384.to.000000.032768=1
+histogram.000000.032768.to.000000.065536=3
+histogram.000000.065536.to.000000.131072=0
+histogram.000000.131072.to.000000.262144=0
+histogram.000000.262144.to.000000.524288=11
+histogram.000000.524288.to.000001.000000=0
+histogram.000001.000000.to.000002.000000=1
+histogram.000002.000000.to.000004.000000=0
+histogram.000004.000000.to.000008.000000=1
+histogram.000008.000000.to.000016.000000=0
+histogram.000016.000000.to.000032.000000=0
+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=90
+num.query.type.PTR=20
+num.query.type.MX=90
+num.query.type.AAAA=90
+num.query.class.IN=290
+num.query.opcode.QUERY=290
+num.query.tcp=0
+num.query.tcpout=0
+num.query.tls=0
+num.query.tls.resume=0
+num.query.ipv6=0
+num.query.flags.QR=0
+num.query.flags.AA=0
+num.query.flags.TC=0
+num.query.flags.RD=290
+num.query.flags.RA=0
+num.query.flags.Z=0
+num.query.flags.AD=0
+num.query.flags.CD=0
+num.query.edns.present=0
+num.query.edns.DO=0
+num.answer.rcode.NOERROR=274
+num.answer.rcode.FORMERR=0
+num.answer.rcode.SERVFAIL=0
+num.answer.rcode.NXDOMAIN=16
+num.answer.rcode.NOTIMPL=0
+num.answer.rcode.REFUSED=0
+num.query.ratelimited=0
+num.answer.secure=0
+num.answer.bogus=0
+num.rrset.bogus=0
+num.query.aggressive.NOERROR=1
+num.query.aggressive.NXDOMAIN=0
+unwanted.queries=0
+unwanted.replies=0
+msg.cache.count=94
+rrset.cache.count=304
+infra.cache.count=192
+key.cache.count=11
+dnscrypt_shared_secret.cache.count=0
+dnscrypt_nonce.cache.count=0
+num.query.dnscrypt.shared_secret.cachemiss=0
+num.query.dnscrypt.replay=0
+num.query.authzone.up=0
+num.query.authzone.down=0
+num.query.subnet=0
+num.query.subnet_cache=0 \ No newline at end of file
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/cumulative/extended3.txt b/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/cumulative/extended3.txt
new file mode 100644
index 000000000..e9448f7d7
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/cumulative/extended3.txt
@@ -0,0 +1,163 @@
+thread0.num.queries=165
+thread0.num.queries_ip_ratelimited=0
+thread0.num.cachehits=150
+thread0.num.cachemiss=15
+thread0.num.prefetch=0
+thread0.num.expired=0
+thread0.num.zero_ttl=0
+thread0.num.recursivereplies=15
+thread0.num.dnscrypt.crypted=0
+thread0.num.dnscrypt.cert=0
+thread0.num.dnscrypt.cleartext=0
+thread0.num.dnscrypt.malformed=0
+thread0.requestlist.avg=0.0666667
+thread0.requestlist.max=1
+thread0.requestlist.overwritten=0
+thread0.requestlist.exceeded=0
+thread0.requestlist.current.all=0
+thread0.requestlist.current.user=0
+thread0.recursion.time.avg=0.261497
+thread0.recursion.time.median=0.318318
+thread0.tcpusage=0
+thread1.num.queries=195
+thread1.num.queries_ip_ratelimited=0
+thread1.num.cachehits=184
+thread1.num.cachemiss=11
+thread1.num.prefetch=0
+thread1.num.expired=0
+thread1.num.zero_ttl=0
+thread1.num.recursivereplies=11
+thread1.num.dnscrypt.crypted=0
+thread1.num.dnscrypt.cert=0
+thread1.num.dnscrypt.cleartext=0
+thread1.num.dnscrypt.malformed=0
+thread1.requestlist.avg=0.363636
+thread1.requestlist.max=2
+thread1.requestlist.overwritten=0
+thread1.requestlist.exceeded=0
+thread1.requestlist.current.all=0
+thread1.requestlist.current.user=0
+thread1.recursion.time.avg=0.709047
+thread1.recursion.time.median=0.294912
+thread1.tcpusage=0
+total.num.queries=360
+total.num.queries_ip_ratelimited=0
+total.num.cachehits=334
+total.num.cachemiss=26
+total.num.prefetch=0
+total.num.expired=0
+total.num.zero_ttl=0
+total.num.recursivereplies=26
+total.num.dnscrypt.crypted=0
+total.num.dnscrypt.cert=0
+total.num.dnscrypt.cleartext=0
+total.num.dnscrypt.malformed=0
+total.requestlist.avg=0.192308
+total.requestlist.max=2
+total.requestlist.overwritten=0
+total.requestlist.exceeded=0
+total.requestlist.current.all=0
+total.requestlist.current.user=0
+total.recursion.time.avg=0.450844
+total.recursion.time.median=0.306615
+total.tcpusage=0
+time.now=1574103543.692653
+time.up=288.096493
+time.elapsed=82.531113
+mem.cache.rrset=208839
+mem.cache.message=101198
+mem.mod.iterator=16588
+mem.mod.validator=85725
+mem.mod.respip=0
+mem.mod.subnet=74504
+mem.cache.dnscrypt_shared_secret=0
+mem.cache.dnscrypt_nonce=0
+mem.streamwait=0
+histogram.000000.000000.to.000000.000001=0
+histogram.000000.000001.to.000000.000002=0
+histogram.000000.000002.to.000000.000004=0
+histogram.000000.000004.to.000000.000008=0
+histogram.000000.000008.to.000000.000016=0
+histogram.000000.000016.to.000000.000032=0
+histogram.000000.000032.to.000000.000064=0
+histogram.000000.000064.to.000000.000128=0
+histogram.000000.000128.to.000000.000256=0
+histogram.000000.000256.to.000000.000512=0
+histogram.000000.000512.to.000000.001024=0
+histogram.000000.001024.to.000000.002048=0
+histogram.000000.002048.to.000000.004096=0
+histogram.000000.004096.to.000000.008192=0
+histogram.000000.008192.to.000000.016384=2
+histogram.000000.016384.to.000000.032768=1
+histogram.000000.032768.to.000000.065536=5
+histogram.000000.065536.to.000000.131072=3
+histogram.000000.131072.to.000000.262144=0
+histogram.000000.262144.to.000000.524288=11
+histogram.000000.524288.to.000001.000000=2
+histogram.000001.000000.to.000002.000000=1
+histogram.000002.000000.to.000004.000000=0
+histogram.000004.000000.to.000008.000000=1
+histogram.000008.000000.to.000016.000000=0
+histogram.000016.000000.to.000032.000000=0
+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=120
+num.query.type.PTR=20
+num.query.type.MX=110
+num.query.type.AAAA=110
+num.query.class.IN=360
+num.query.opcode.QUERY=360
+num.query.tcp=0
+num.query.tcpout=0
+num.query.tls=0
+num.query.tls.resume=0
+num.query.ipv6=0
+num.query.flags.QR=0
+num.query.flags.AA=0
+num.query.flags.TC=0
+num.query.flags.RD=360
+num.query.flags.RA=0
+num.query.flags.Z=0
+num.query.flags.AD=0
+num.query.flags.CD=0
+num.query.edns.present=0
+num.query.edns.DO=0
+num.answer.rcode.NOERROR=334
+num.answer.rcode.FORMERR=0
+num.answer.rcode.SERVFAIL=10
+num.answer.rcode.NXDOMAIN=16
+num.answer.rcode.NOTIMPL=0
+num.answer.rcode.REFUSED=0
+num.answer.rcode.nodata=20
+num.query.ratelimited=0
+num.answer.secure=0
+num.answer.bogus=0
+num.rrset.bogus=0
+num.query.aggressive.NOERROR=1
+num.query.aggressive.NXDOMAIN=0
+unwanted.queries=0
+unwanted.replies=0
+msg.cache.count=119
+rrset.cache.count=401
+infra.cache.count=232
+key.cache.count=14
+dnscrypt_shared_secret.cache.count=0
+dnscrypt_nonce.cache.count=0
+num.query.dnscrypt.shared_secret.cachemiss=0
+num.query.dnscrypt.replay=0
+num.query.authzone.up=0
+num.query.authzone.down=0
+num.query.subnet=0
+num.query.subnet_cache=0 \ No newline at end of file
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/reset/extended1.txt b/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/reset/extended1.txt
new file mode 100644
index 000000000..8be40ecb2
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/reset/extended1.txt
@@ -0,0 +1,163 @@
+thread0.num.queries=51
+thread0.num.queries_ip_ratelimited=0
+thread0.num.cachehits=44
+thread0.num.cachemiss=7
+thread0.num.prefetch=0
+thread0.num.expired=0
+thread0.num.zero_ttl=0
+thread0.num.recursivereplies=7
+thread0.num.dnscrypt.crypted=0
+thread0.num.dnscrypt.cert=0
+thread0.num.dnscrypt.cleartext=0
+thread0.num.dnscrypt.malformed=0
+thread0.requestlist.avg=0
+thread0.requestlist.max=0
+thread0.requestlist.overwritten=0
+thread0.requestlist.exceeded=0
+thread0.requestlist.current.all=0
+thread0.requestlist.current.user=0
+thread0.recursion.time.avg=0.365956
+thread0.recursion.time.median=0.057344
+thread0.tcpusage=0
+thread1.num.queries=49
+thread1.num.queries_ip_ratelimited=0
+thread1.num.cachehits=46
+thread1.num.cachemiss=3
+thread1.num.prefetch=0
+thread1.num.expired=0
+thread1.num.zero_ttl=0
+thread1.num.recursivereplies=3
+thread1.num.dnscrypt.crypted=0
+thread1.num.dnscrypt.cert=0
+thread1.num.dnscrypt.cleartext=0
+thread1.num.dnscrypt.malformed=0
+thread1.requestlist.avg=0
+thread1.requestlist.max=0
+thread1.requestlist.overwritten=0
+thread1.requestlist.exceeded=0
+thread1.requestlist.current.all=0
+thread1.requestlist.current.user=0
+thread1.recursion.time.avg=1.582766
+thread1.recursion.time.median=0
+thread1.tcpusage=0
+total.num.queries=100
+total.num.queries_ip_ratelimited=0
+total.num.cachehits=90
+total.num.cachemiss=10
+total.num.prefetch=0
+total.num.expired=0
+total.num.zero_ttl=0
+total.num.recursivereplies=10
+total.num.dnscrypt.crypted=0
+total.num.dnscrypt.cert=0
+total.num.dnscrypt.cleartext=0
+total.num.dnscrypt.malformed=0
+total.requestlist.avg=0
+total.requestlist.max=0
+total.requestlist.overwritten=0
+total.requestlist.exceeded=0
+total.requestlist.current.all=0
+total.requestlist.current.user=0
+total.recursion.time.avg=0.730999
+total.recursion.time.median=0.028672
+total.tcpusage=0
+time.now=1574103644.993894
+time.up=45.285130
+time.elapsed=45.285130
+mem.cache.rrset=172757
+mem.cache.message=86064
+mem.mod.iterator=16588
+mem.mod.validator=79979
+mem.mod.respip=0
+mem.mod.subnet=74504
+mem.cache.dnscrypt_shared_secret=0
+mem.cache.dnscrypt_nonce=0
+mem.streamwait=0
+histogram.000000.000000.to.000000.000001=0
+histogram.000000.000001.to.000000.000002=0
+histogram.000000.000002.to.000000.000004=0
+histogram.000000.000004.to.000000.000008=0
+histogram.000000.000008.to.000000.000016=0
+histogram.000000.000016.to.000000.000032=0
+histogram.000000.000032.to.000000.000064=0
+histogram.000000.000064.to.000000.000128=0
+histogram.000000.000128.to.000000.000256=0
+histogram.000000.000256.to.000000.000512=0
+histogram.000000.000512.to.000000.001024=0
+histogram.000000.001024.to.000000.002048=0
+histogram.000000.002048.to.000000.004096=0
+histogram.000000.004096.to.000000.008192=0
+histogram.000000.008192.to.000000.016384=0
+histogram.000000.016384.to.000000.032768=2
+histogram.000000.032768.to.000000.065536=3
+histogram.000000.065536.to.000000.131072=1
+histogram.000000.131072.to.000000.262144=1
+histogram.000000.262144.to.000000.524288=1
+histogram.000000.524288.to.000001.000000=0
+histogram.000001.000000.to.000002.000000=1
+histogram.000002.000000.to.000004.000000=0
+histogram.000004.000000.to.000008.000000=1
+histogram.000008.000000.to.000016.000000=0
+histogram.000016.000000.to.000032.000000=0
+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=30
+num.query.type.PTR=10
+num.query.type.MX=30
+num.query.type.AAAA=30
+num.query.class.IN=100
+num.query.opcode.QUERY=100
+num.query.tcp=0
+num.query.tcpout=1
+num.query.tls=0
+num.query.tls.resume=0
+num.query.ipv6=0
+num.query.flags.QR=0
+num.query.flags.AA=0
+num.query.flags.TC=0
+num.query.flags.RD=100
+num.query.flags.RA=0
+num.query.flags.Z=0
+num.query.flags.AD=0
+num.query.flags.CD=0
+num.query.edns.present=0
+num.query.edns.DO=0
+num.answer.rcode.NOERROR=90
+num.answer.rcode.FORMERR=0
+num.answer.rcode.SERVFAIL=0
+num.answer.rcode.NXDOMAIN=10
+num.answer.rcode.NOTIMPL=0
+num.answer.rcode.REFUSED=0
+num.answer.rcode.nodata=10
+num.query.ratelimited=0
+num.answer.secure=0
+num.answer.bogus=0
+num.rrset.bogus=0
+num.query.aggressive.NOERROR=2
+num.query.aggressive.NXDOMAIN=0
+unwanted.queries=0
+unwanted.replies=0
+msg.cache.count=67
+rrset.cache.count=303
+infra.cache.count=181
+key.cache.count=10
+dnscrypt_shared_secret.cache.count=0
+dnscrypt_nonce.cache.count=0
+num.query.dnscrypt.shared_secret.cachemiss=0
+num.query.dnscrypt.replay=0
+num.query.authzone.up=0
+num.query.authzone.down=0
+num.query.subnet=0
+num.query.subnet_cache=0 \ No newline at end of file
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/reset/extended2.txt b/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/reset/extended2.txt
new file mode 100644
index 000000000..08ff128b3
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/reset/extended2.txt
@@ -0,0 +1,156 @@
+thread0.num.queries=0
+thread0.num.queries_ip_ratelimited=0
+thread0.num.cachehits=0
+thread0.num.cachemiss=0
+thread0.num.prefetch=0
+thread0.num.expired=0
+thread0.num.zero_ttl=0
+thread0.num.recursivereplies=0
+thread0.num.dnscrypt.crypted=0
+thread0.num.dnscrypt.cert=0
+thread0.num.dnscrypt.cleartext=0
+thread0.num.dnscrypt.malformed=0
+thread0.requestlist.avg=0
+thread0.requestlist.max=0
+thread0.requestlist.overwritten=0
+thread0.requestlist.exceeded=0
+thread0.requestlist.current.all=0
+thread0.requestlist.current.user=0
+thread0.recursion.time.avg=0.000000
+thread0.recursion.time.median=0
+thread0.tcpusage=0
+thread1.num.queries=0
+thread1.num.queries_ip_ratelimited=0
+thread1.num.cachehits=0
+thread1.num.cachemiss=0
+thread1.num.prefetch=0
+thread1.num.expired=0
+thread1.num.zero_ttl=0
+thread1.num.recursivereplies=0
+thread1.num.dnscrypt.crypted=0
+thread1.num.dnscrypt.cert=0
+thread1.num.dnscrypt.cleartext=0
+thread1.num.dnscrypt.malformed=0
+thread1.requestlist.avg=0
+thread1.requestlist.max=0
+thread1.requestlist.overwritten=0
+thread1.requestlist.exceeded=0
+thread1.requestlist.current.all=0
+thread1.requestlist.current.user=0
+thread1.recursion.time.avg=0.000000
+thread1.recursion.time.median=0
+thread1.tcpusage=0
+total.num.queries=0
+total.num.queries_ip_ratelimited=0
+total.num.cachehits=0
+total.num.cachemiss=0
+total.num.prefetch=0
+total.num.expired=0
+total.num.zero_ttl=0
+total.num.recursivereplies=0
+total.num.dnscrypt.crypted=0
+total.num.dnscrypt.cert=0
+total.num.dnscrypt.cleartext=0
+total.num.dnscrypt.malformed=0
+total.requestlist.avg=0
+total.requestlist.max=0
+total.requestlist.overwritten=0
+total.requestlist.exceeded=0
+total.requestlist.current.all=0
+total.requestlist.current.user=0
+total.recursion.time.avg=0.000000
+total.recursion.time.median=0
+total.tcpusage=0
+time.now=1574103671.543847
+time.up=71.835083
+time.elapsed=26.549953
+mem.cache.rrset=172757
+mem.cache.message=86064
+mem.mod.iterator=16588
+mem.mod.validator=79979
+mem.mod.respip=0
+mem.mod.subnet=74504
+mem.cache.dnscrypt_shared_secret=0
+mem.cache.dnscrypt_nonce=0
+mem.streamwait=0
+histogram.000000.000000.to.000000.000001=0
+histogram.000000.000001.to.000000.000002=0
+histogram.000000.000002.to.000000.000004=0
+histogram.000000.000004.to.000000.000008=0
+histogram.000000.000008.to.000000.000016=0
+histogram.000000.000016.to.000000.000032=0
+histogram.000000.000032.to.000000.000064=0
+histogram.000000.000064.to.000000.000128=0
+histogram.000000.000128.to.000000.000256=0
+histogram.000000.000256.to.000000.000512=0
+histogram.000000.000512.to.000000.001024=0
+histogram.000000.001024.to.000000.002048=0
+histogram.000000.002048.to.000000.004096=0
+histogram.000000.004096.to.000000.008192=0
+histogram.000000.008192.to.000000.016384=0
+histogram.000000.016384.to.000000.032768=0
+histogram.000000.032768.to.000000.065536=0
+histogram.000000.065536.to.000000.131072=0
+histogram.000000.131072.to.000000.262144=0
+histogram.000000.262144.to.000000.524288=0
+histogram.000000.524288.to.000001.000000=0
+histogram.000001.000000.to.000002.000000=0
+histogram.000002.000000.to.000004.000000=0
+histogram.000004.000000.to.000008.000000=0
+histogram.000008.000000.to.000016.000000=0
+histogram.000016.000000.to.000032.000000=0
+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.tcp=0
+num.query.tcpout=0
+num.query.tls=0
+num.query.tls.resume=0
+num.query.ipv6=0
+num.query.flags.QR=0
+num.query.flags.AA=0
+num.query.flags.TC=0
+num.query.flags.RD=0
+num.query.flags.RA=0
+num.query.flags.Z=0
+num.query.flags.AD=0
+num.query.flags.CD=0
+num.query.edns.present=0
+num.query.edns.DO=0
+num.answer.rcode.NOERROR=0
+num.answer.rcode.FORMERR=0
+num.answer.rcode.SERVFAIL=0
+num.answer.rcode.NXDOMAIN=0
+num.answer.rcode.NOTIMPL=0
+num.answer.rcode.REFUSED=0
+num.query.ratelimited=0
+num.answer.secure=0
+num.answer.bogus=0
+num.rrset.bogus=0
+num.query.aggressive.NOERROR=0
+num.query.aggressive.NXDOMAIN=0
+unwanted.queries=0
+unwanted.replies=0
+msg.cache.count=67
+rrset.cache.count=303
+infra.cache.count=181
+key.cache.count=10
+dnscrypt_shared_secret.cache.count=0
+dnscrypt_nonce.cache.count=0
+num.query.dnscrypt.shared_secret.cachemiss=0
+num.query.dnscrypt.replay=0
+num.query.authzone.up=0
+num.query.authzone.down=0
+num.query.subnet=0
+num.query.subnet_cache=0 \ No newline at end of file
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/reset/extended3.txt b/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/reset/extended3.txt
new file mode 100644
index 000000000..45324bef9
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/testdata/stats/lifecycle/reset/extended3.txt
@@ -0,0 +1,163 @@
+thread0.num.queries=34
+thread0.num.queries_ip_ratelimited=0
+thread0.num.cachehits=30
+thread0.num.cachemiss=4
+thread0.num.prefetch=0
+thread0.num.expired=0
+thread0.num.zero_ttl=0
+thread0.num.recursivereplies=4
+thread0.num.dnscrypt.crypted=0
+thread0.num.dnscrypt.cert=0
+thread0.num.dnscrypt.cleartext=0
+thread0.num.dnscrypt.malformed=0
+thread0.requestlist.avg=0
+thread0.requestlist.max=0
+thread0.requestlist.overwritten=0
+thread0.requestlist.exceeded=0
+thread0.requestlist.current.all=0
+thread0.requestlist.current.user=0
+thread0.recursion.time.avg=0.541654
+thread0.recursion.time.median=0.098304
+thread0.tcpusage=0
+thread1.num.queries=36
+thread1.num.queries_ip_ratelimited=0
+thread1.num.cachehits=33
+thread1.num.cachemiss=3
+thread1.num.prefetch=0
+thread1.num.expired=0
+thread1.num.zero_ttl=0
+thread1.num.recursivereplies=3
+thread1.num.dnscrypt.crypted=0
+thread1.num.dnscrypt.cert=0
+thread1.num.dnscrypt.cleartext=0
+thread1.num.dnscrypt.malformed=0
+thread1.requestlist.avg=1.66667
+thread1.requestlist.max=5
+thread1.requestlist.overwritten=0
+thread1.requestlist.exceeded=0
+thread1.requestlist.current.all=0
+thread1.requestlist.current.user=0
+thread1.recursion.time.avg=0.062328
+thread1.recursion.time.median=0
+thread1.tcpusage=0
+total.num.queries=70
+total.num.queries_ip_ratelimited=0
+total.num.cachehits=63
+total.num.cachemiss=7
+total.num.prefetch=0
+total.num.expired=0
+total.num.zero_ttl=0
+total.num.recursivereplies=7
+total.num.dnscrypt.crypted=0
+total.num.dnscrypt.cert=0
+total.num.dnscrypt.cleartext=0
+total.num.dnscrypt.malformed=0
+total.requestlist.avg=0.714286
+total.requestlist.max=5
+total.requestlist.overwritten=0
+total.requestlist.exceeded=0
+total.requestlist.current.all=0
+total.requestlist.current.user=0
+total.recursion.time.avg=0.336228
+total.recursion.time.median=0.049152
+total.tcpusage=0
+time.now=1574103731.371896
+time.up=131.663132
+time.elapsed=59.828049
+mem.cache.rrset=235917
+mem.cache.message=105471
+mem.mod.iterator=16588
+mem.mod.validator=87270
+mem.mod.respip=0
+mem.mod.subnet=74504
+mem.cache.dnscrypt_shared_secret=0
+mem.cache.dnscrypt_nonce=0
+mem.streamwait=0
+histogram.000000.000000.to.000000.000001=0
+histogram.000000.000001.to.000000.000002=0
+histogram.000000.000002.to.000000.000004=0
+histogram.000000.000004.to.000000.000008=0
+histogram.000000.000008.to.000000.000016=0
+histogram.000000.000016.to.000000.000032=0
+histogram.000000.000032.to.000000.000064=0
+histogram.000000.000064.to.000000.000128=0
+histogram.000000.000128.to.000000.000256=0
+histogram.000000.000256.to.000000.000512=0
+histogram.000000.000512.to.000000.001024=0
+histogram.000000.001024.to.000000.002048=0
+histogram.000000.002048.to.000000.004096=0
+histogram.000000.004096.to.000000.008192=0
+histogram.000000.008192.to.000000.016384=0
+histogram.000000.016384.to.000000.032768=2
+histogram.000000.032768.to.000000.065536=1
+histogram.000000.065536.to.000000.131072=3
+histogram.000000.131072.to.000000.262144=0
+histogram.000000.262144.to.000000.524288=0
+histogram.000000.524288.to.000001.000000=0
+histogram.000001.000000.to.000002.000000=1
+histogram.000002.000000.to.000004.000000=0
+histogram.000004.000000.to.000008.000000=0
+histogram.000008.000000.to.000016.000000=0
+histogram.000016.000000.to.000032.000000=0
+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=20
+num.query.type.PTR=10
+num.query.type.MX=20
+num.query.type.AAAA=20
+num.query.class.IN=70
+num.query.opcode.QUERY=70
+num.query.tcp=0
+num.query.tcpout=0
+num.query.tls=0
+num.query.tls.resume=0
+num.query.ipv6=0
+num.query.flags.QR=0
+num.query.flags.AA=0
+num.query.flags.TC=0
+num.query.flags.RD=70
+num.query.flags.RA=0
+num.query.flags.Z=0
+num.query.flags.AD=0
+num.query.flags.CD=0
+num.query.edns.present=0
+num.query.edns.DO=0
+num.answer.rcode.NOERROR=60
+num.answer.rcode.FORMERR=0
+num.answer.rcode.SERVFAIL=0
+num.answer.rcode.NXDOMAIN=10
+num.answer.rcode.NOTIMPL=0
+num.answer.rcode.REFUSED=0
+num.answer.rcode.nodata=10
+num.query.ratelimited=0
+num.answer.secure=0
+num.answer.bogus=0
+num.rrset.bogus=0
+num.query.aggressive.NOERROR=2
+num.query.aggressive.NXDOMAIN=0
+unwanted.queries=0
+unwanted.replies=0
+msg.cache.count=127
+rrset.cache.count=501
+infra.cache.count=303
+key.cache.count=15
+dnscrypt_shared_secret.cache.count=0
+dnscrypt_nonce.cache.count=0
+num.query.dnscrypt.shared_secret.cachemiss=0
+num.query.dnscrypt.replay=0
+num.query.authzone.up=0
+num.query.authzone.down=0
+num.query.subnet=0
+num.query.subnet_cache=0 \ No newline at end of file
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/testdata/unbound.conf b/src/go/collectors/go.d.plugin/modules/unbound/testdata/unbound.conf
new file mode 100644
index 000000000..a061a3476
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/testdata/unbound.conf
@@ -0,0 +1,85 @@
+#
+# Example configuration file.
+#
+# See unbound.conf(5) man page, version 1.9.4.
+#
+# this is a comment.
+
+#Use this to include other text into the file.
+#include: "otherfile.conf"
+
+# The server clause sets the main parameters.
+server:
+ # whitespace is not necessary, but looks cleaner.
+
+ # verbosity number, 0 is least verbose. 1 is default.
+ # verbosity: 1
+
+ # print statistics to the log (for every thread) every N seconds.
+ # Set to "" or 0 to disable. Default is disabled.
+ # statistics-interval: 0
+
+ # enable shm for stats, default no. if you enable also enable
+ # statistics-interval, every time it also writes stats to the
+ # shared memory segment keyed with shm-key.
+ # shm-enable: no
+
+ # shm for stats uses this key, and key+1 for the shared mem segment.
+ # shm-key: 11777
+
+ # enable cumulative statistics, without clearing them after printing.
+ # statistics-cumulative: no
+ statistics-cumulative: yes
+
+ # enable extended statistics (query types, answer codes, status)
+ # printed from unbound-control. default off, because of speed.
+ # extended-statistics: no
+ # extended-statistics: yes
+
+ # number of threads to create. 1 disables threading.
+ # num-threads: 2
+
+# Python config section. To enable:
+# o use --with-pythonmodule to configure before compiling.
+# o list python in the module-config string (above) to enable.
+# It can be at the start, it gets validated results, or just before
+# the iterator and process before DNSSEC validation.
+# o and give a python-script to run.
+python:
+ # Script file to load
+ # python-script: "/etc/unbound/ubmodule-tst.py"
+
+# Remote control config section.
+remote-control:
+ # Enable remote control with unbound-control(8) here.
+ # set up the keys and certificates with unbound-control-setup.
+ control-enable: yes
+
+ # what interfaces are listened to for remote control.
+ # give 0.0.0.0 and ::0 to listen to all interfaces.
+ # set to an absolute path to use a unix local name pipe, certificates
+ # are not used for that, so key and cert files need not be present.
+ # control-interface: 127.0.0.1
+ control-interface: 10.0.0.1
+ # control-interface: ::1
+ # control-interface: /var/run/test.sock
+
+ # port number for remote control operations.
+ control-port: 8954
+
+ # for localhost, you can disable use of TLS by setting this to "no"
+ # For local sockets this option is ignored, and TLS is not used.
+ # control-use-cert: "yes"
+ control-use-cert: "no"
+
+ # unbound server key file.
+ # server-key-file: "/etc/unbound/unbound_server.key"
+
+ # unbound server certificate file.
+ # server-cert-file: "/etc/unbound/unbound_server.pem"
+
+ # unbound-control key file.
+ control-key-file: "/etc/unbound/unbound_control_other.key"
+
+ # unbound-control certificate file.
+ control-cert-file: "/etc/unbound/unbound_control_other.pem"
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/testdata/unbound_disabled.conf b/src/go/collectors/go.d.plugin/modules/unbound/testdata/unbound_disabled.conf
new file mode 100644
index 000000000..1cef549f8
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/testdata/unbound_disabled.conf
@@ -0,0 +1,85 @@
+#
+# Example configuration file.
+#
+# See unbound.conf(5) man page, version 1.9.4.
+#
+# this is a comment.
+
+#Use this to include other text into the file.
+#include: "otherfile.conf"
+
+# The server clause sets the main parameters.
+server:
+ # whitespace is not necessary, but looks cleaner.
+
+ # verbosity number, 0 is least verbose. 1 is default.
+ # verbosity: 1
+
+ # print statistics to the log (for every thread) every N seconds.
+ # Set to "" or 0 to disable. Default is disabled.
+ # statistics-interval: 0
+
+ # enable shm for stats, default no. if you enable also enable
+ # statistics-interval, every time it also writes stats to the
+ # shared memory segment keyed with shm-key.
+ # shm-enable: no
+
+ # shm for stats uses this key, and key+1 for the shared mem segment.
+ # shm-key: 11777
+
+ # enable cumulative statistics, without clearing them after printing.
+ # statistics-cumulative: no
+ statistics-cumulative: yes
+
+ # enable extended statistics (query types, answer codes, status)
+ # printed from unbound-control. default off, because of speed.
+ # extended-statistics: no
+ # extended-statistics: yes
+
+ # number of threads to create. 1 disables threading.
+ # num-threads: 2
+
+# Python config section. To enable:
+# o use --with-pythonmodule to configure before compiling.
+# o list python in the module-config string (above) to enable.
+# It can be at the start, it gets validated results, or just before
+# the iterator and process before DNSSEC validation.
+# o and give a python-script to run.
+python:
+ # Script file to load
+ # python-script: "/etc/unbound/ubmodule-tst.py"
+
+# Remote control config section.
+remote-control:
+ # Enable remote control with unbound-control(8) here.
+ # set up the keys and certificates with unbound-control-setup.
+ control-enable: no
+
+ # what interfaces are listened to for remote control.
+ # give 0.0.0.0 and ::0 to listen to all interfaces.
+ # set to an absolute path to use a unix local name pipe, certificates
+ # are not used for that, so key and cert files need not be present.
+ # control-interface: 127.0.0.1
+ control-interface: 0.0.0.0
+ # control-interface: ::1
+ # control-interface: /var/run/test.sock
+
+ # port number for remote control operations.
+ control-port: 8953
+
+ # for localhost, you can disable use of TLS by setting this to "no"
+ # For local sockets this option is ignored, and TLS is not used.
+ # control-use-cert: "yes"
+ control-use-cert: "yes"
+
+ # unbound server key file.
+ # server-key-file: "/etc/unbound/unbound_server.key"
+
+ # unbound server certificate file.
+ # server-cert-file: "/etc/unbound/unbound_server.pem"
+
+ # unbound-control key file.
+ control-key-file: "/etc/unbound/unbound_control.key"
+
+ # unbound-control certificate file.
+ control-cert-file: "/etc/unbound/unbound_control.pem"
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/testdata/unbound_empty.conf b/src/go/collectors/go.d.plugin/modules/unbound/testdata/unbound_empty.conf
new file mode 100644
index 000000000..a2d158376
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/testdata/unbound_empty.conf
@@ -0,0 +1,85 @@
+#
+# Example configuration file.
+#
+# See unbound.conf(5) man page, version 1.9.4.
+#
+# this is a comment.
+
+#Use this to include other text into the file.
+#include: "otherfile.conf"
+
+# The server clause sets the main parameters.
+server:
+ # whitespace is not necessary, but looks cleaner.
+
+ # verbosity number, 0 is least verbose. 1 is default.
+ # verbosity: 1
+
+ # print statistics to the log (for every thread) every N seconds.
+ # Set to "" or 0 to disable. Default is disabled.
+ # statistics-interval: 0
+
+ # enable shm for stats, default no. if you enable also enable
+ # statistics-interval, every time it also writes stats to the
+ # shared memory segment keyed with shm-key.
+ # shm-enable: no
+
+ # shm for stats uses this key, and key+1 for the shared mem segment.
+ # shm-key: 11777
+
+ # enable cumulative statistics, without clearing them after printing.
+ # statistics-cumulative: no
+ # statistics-cumulative: yes
+
+ # enable extended statistics (query types, answer codes, status)
+ # printed from unbound-control. default off, because of speed.
+ # extended-statistics: no
+ # extended-statistics: yes
+
+ # number of threads to create. 1 disables threading.
+ # num-threads: 2
+
+# Python config section. To enable:
+# o use --with-pythonmodule to configure before compiling.
+# o list python in the module-config string (above) to enable.
+# It can be at the start, it gets validated results, or just before
+# the iterator and process before DNSSEC validation.
+# o and give a python-script to run.
+python:
+ # Script file to load
+ # python-script: "/etc/unbound/ubmodule-tst.py"
+
+# Remote control config section.
+remote-control:
+ # Enable remote control with unbound-control(8) here.
+ # set up the keys and certificates with unbound-control-setup.
+ # control-enable: no
+
+ # what interfaces are listened to for remote control.
+ # give 0.0.0.0 and ::0 to listen to all interfaces.
+ # set to an absolute path to use a unix local name pipe, certificates
+ # are not used for that, so key and cert files need not be present.
+ # control-interface: 127.0.0.1
+ # control-interface: 0.0.0.0
+ # control-interface: ::1
+ # control-interface: /var/run/test.sock
+
+ # port number for remote control operations.
+ # control-port: 8953
+
+ # for localhost, you can disable use of TLS by setting this to "no"
+ # For local sockets this option is ignored, and TLS is not used.
+ # control-use-cert: "yes"
+ # control-use-cert: "yes"
+
+ # unbound server key file.
+ # server-key-file: "/etc/unbound/unbound_server.key"
+
+ # unbound server certificate file.
+ # server-cert-file: "/etc/unbound/unbound_server.pem"
+
+ # unbound-control key file.
+ # control-key-file: "/etc/unbound/unbound_control.key"
+
+ # unbound-control certificate file.
+ # control-cert-file: "/etc/unbound/unbound_control.pem"
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/unbound.go b/src/go/collectors/go.d.plugin/modules/unbound/unbound.go
new file mode 100644
index 000000000..7536aed3c
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/unbound.go
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package unbound
+
+import (
+ _ "embed"
+ "errors"
+ "time"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/socket"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/tlscfg"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/web"
+)
+
+//go:embed "config_schema.json"
+var configSchema string
+
+func init() {
+ module.Register("unbound", module.Creator{
+ JobConfigSchema: configSchema,
+ Create: func() module.Module { return New() },
+ Config: func() any { return &Config{} },
+ })
+}
+
+func New() *Unbound {
+ return &Unbound{
+ Config: Config{
+ Address: "127.0.0.1:8953",
+ ConfPath: "/etc/unbound/unbound.conf",
+ Timeout: web.Duration(time.Second),
+ Cumulative: false,
+ UseTLS: true,
+ TLSConfig: tlscfg.TLSConfig{
+ TLSCert: "/etc/unbound/unbound_control.pem",
+ TLSKey: "/etc/unbound/unbound_control.key",
+ InsecureSkipVerify: true,
+ },
+ },
+ curCache: newCollectCache(),
+ cache: newCollectCache(),
+ }
+}
+
+type Config struct {
+ UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
+ Address string `yaml:"address" json:"address"`
+ ConfPath string `yaml:"conf_path,omitempty" json:"conf_path"`
+ Timeout web.Duration `yaml:"timeout,omitempty" json:"timeout"`
+ Cumulative bool `yaml:"cumulative_stats" json:"cumulative_stats"`
+ UseTLS bool `yaml:"use_tls,omitempty" json:"use_tls"`
+ tlscfg.TLSConfig `yaml:",inline" json:""`
+}
+
+type Unbound struct {
+ module.Base
+ Config `yaml:",inline" json:""`
+
+ charts *module.Charts
+
+ client socket.Client
+
+ cache collectCache
+ curCache collectCache
+ prevCacheMiss float64 // needed for cumulative mode
+ extChartsCreated bool
+}
+
+func (u *Unbound) Configuration() any {
+ return u.Config
+}
+
+func (u *Unbound) Init() error {
+ if enabled := u.initConfig(); !enabled {
+ return errors.New("remote control is disabled in the configuration file")
+ }
+
+ if err := u.initClient(); err != nil {
+ u.Errorf("creating client: %v", err)
+ return err
+ }
+
+ u.charts = charts(u.Cumulative)
+
+ u.Debugf("using address: %s, cumulative: %v, use_tls: %v, timeout: %s", u.Address, u.Cumulative, u.UseTLS, u.Timeout)
+ if u.UseTLS {
+ u.Debugf("using tls_skip_verify: %v, tls_key: %s, tls_cert: %s", u.InsecureSkipVerify, u.TLSKey, u.TLSCert)
+ }
+
+ return nil
+}
+
+func (u *Unbound) Check() error {
+ mx, err := u.collect()
+ if err != nil {
+ u.Error(err)
+ return err
+ }
+ if len(mx) == 0 {
+ return errors.New("no metrics collected")
+ }
+ return nil
+}
+
+func (u *Unbound) Charts() *module.Charts {
+ return u.charts
+}
+
+func (u *Unbound) Collect() map[string]int64 {
+ mx, err := u.collect()
+ if err != nil {
+ u.Error(err)
+ }
+
+ if len(mx) == 0 {
+ return nil
+ }
+ return mx
+}
+
+func (u *Unbound) Cleanup() {
+ if u.client != nil {
+ _ = u.client.Disconnect()
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/unbound/unbound_test.go b/src/go/collectors/go.d.plugin/modules/unbound/unbound_test.go
new file mode 100644
index 000000000..2d24b67b1
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/unbound/unbound_test.go
@@ -0,0 +1,1288 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package unbound
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "os"
+ "strings"
+ "testing"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/socket"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/tlscfg"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+var (
+ dataConfigJSON, _ = os.ReadFile("testdata/config.json")
+ dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
+
+ dataCommonStats, _ = os.ReadFile("testdata/stats/common.txt")
+ dataExtendedStats, _ = os.ReadFile("testdata/stats/extended.txt")
+ dataLifeCycleCumulative1, _ = os.ReadFile("testdata/stats/lifecycle/cumulative/extended1.txt")
+ dataLifeCycleCumulative2, _ = os.ReadFile("testdata/stats/lifecycle/cumulative/extended2.txt")
+ dataLifeCycleCumulative3, _ = os.ReadFile("testdata/stats/lifecycle/cumulative/extended3.txt")
+ dataLifeCycleReset1, _ = os.ReadFile("testdata/stats/lifecycle/reset/extended1.txt")
+ dataLifeCycleReset2, _ = os.ReadFile("testdata/stats/lifecycle/reset/extended2.txt")
+ dataLifeCycleReset3, _ = os.ReadFile("testdata/stats/lifecycle/reset/extended3.txt")
+)
+
+func Test_testDataIsValid(t *testing.T) {
+ for name, data := range map[string][]byte{
+ "dataConfigJSON": dataConfigJSON,
+ "dataConfigYAML": dataConfigYAML,
+ "dataCommonStats": dataCommonStats,
+ "dataExtendedStats": dataExtendedStats,
+ "dataLifeCycleCumulative1": dataLifeCycleCumulative1,
+ "dataLifeCycleCumulative2": dataLifeCycleCumulative2,
+ "dataLifeCycleCumulative3": dataLifeCycleCumulative3,
+ "dataLifeCycleReset1": dataLifeCycleReset1,
+ "dataLifeCycleReset2": dataLifeCycleReset2,
+ "dataLifeCycleReset3": dataLifeCycleReset3,
+ } {
+ require.NotNil(t, data, name)
+ }
+}
+
+func TestUnbound_ConfigurationSerialize(t *testing.T) {
+ module.TestConfigurationSerialize(t, &Unbound{}, dataConfigJSON, dataConfigYAML)
+}
+
+func TestUnbound_Init(t *testing.T) {
+ unbound := prepareNonTLSUnbound()
+
+ assert.NoError(t, unbound.Init())
+}
+
+func TestUnbound_Init_SetEverythingFromUnboundConf(t *testing.T) {
+ unbound := New()
+ unbound.ConfPath = "testdata/unbound.conf"
+ expectedConfig := Config{
+ Address: "10.0.0.1:8954",
+ ConfPath: unbound.ConfPath,
+ Timeout: unbound.Timeout,
+ Cumulative: true,
+ UseTLS: false,
+ TLSConfig: tlscfg.TLSConfig{
+ TLSCert: "/etc/unbound/unbound_control_other.pem",
+ TLSKey: "/etc/unbound/unbound_control_other.key",
+ InsecureSkipVerify: unbound.TLSConfig.InsecureSkipVerify,
+ },
+ }
+
+ assert.NoError(t, unbound.Init())
+ assert.Equal(t, expectedConfig, unbound.Config)
+}
+
+func TestUnbound_Init_DisabledInUnboundConf(t *testing.T) {
+ unbound := prepareNonTLSUnbound()
+ unbound.ConfPath = "testdata/unbound_disabled.conf"
+
+ assert.Error(t, unbound.Init())
+}
+
+func TestUnbound_Init_HandleEmptyConfig(t *testing.T) {
+ unbound := prepareNonTLSUnbound()
+ unbound.ConfPath = "testdata/unbound_empty.conf"
+
+ assert.NoError(t, unbound.Init())
+}
+
+func TestUnbound_Init_HandleNonExistentConfig(t *testing.T) {
+ unbound := prepareNonTLSUnbound()
+ unbound.ConfPath = "testdata/unbound_non_existent.conf"
+
+ assert.NoError(t, unbound.Init())
+}
+
+func TestUnbound_Check(t *testing.T) {
+ unbound := prepareNonTLSUnbound()
+ require.NoError(t, unbound.Init())
+ unbound.client = mockUnboundClient{data: dataCommonStats, err: false}
+
+ assert.NoError(t, unbound.Check())
+}
+
+func TestUnbound_Check_ErrorDuringScrapingUnbound(t *testing.T) {
+ unbound := prepareNonTLSUnbound()
+ require.NoError(t, unbound.Init())
+ unbound.client = mockUnboundClient{err: true}
+
+ assert.Error(t, unbound.Check())
+}
+
+func TestUnbound_Cleanup(t *testing.T) {
+ New().Cleanup()
+}
+
+func TestUnbound_Charts(t *testing.T) {
+ unbound := prepareNonTLSUnbound()
+ require.NoError(t, unbound.Init())
+
+ assert.NotNil(t, unbound.Charts())
+}
+
+func TestUnbound_Collect(t *testing.T) {
+ unbound := prepareNonTLSUnbound()
+ require.NoError(t, unbound.Init())
+ unbound.client = mockUnboundClient{data: dataCommonStats, err: false}
+
+ collected := unbound.Collect()
+ assert.Equal(t, expectedCommon, collected)
+ testCharts(t, unbound, collected)
+}
+
+func TestUnbound_Collect_ExtendedStats(t *testing.T) {
+ unbound := prepareNonTLSUnbound()
+ require.NoError(t, unbound.Init())
+ unbound.client = mockUnboundClient{data: dataExtendedStats, err: false}
+
+ collected := unbound.Collect()
+ assert.Equal(t, expectedExtended, collected)
+ testCharts(t, unbound, collected)
+}
+
+func TestUnbound_Collect_LifeCycleCumulativeExtendedStats(t *testing.T) {
+ tests := []struct {
+ input []byte
+ expected map[string]int64
+ }{
+ {input: dataLifeCycleCumulative1, expected: expectedCumulative1},
+ {input: dataLifeCycleCumulative2, expected: expectedCumulative2},
+ {input: dataLifeCycleCumulative3, expected: expectedCumulative3},
+ }
+
+ unbound := prepareNonTLSUnbound()
+ unbound.Cumulative = true
+ require.NoError(t, unbound.Init())
+ ubClient := &mockUnboundClient{err: false}
+ unbound.client = ubClient
+
+ var collected map[string]int64
+ for i, test := range tests {
+ t.Run(fmt.Sprintf("run %d", i+1), func(t *testing.T) {
+ ubClient.data = test.input
+ collected = unbound.Collect()
+ assert.Equal(t, test.expected, collected)
+ })
+ }
+
+ testCharts(t, unbound, collected)
+}
+
+func TestUnbound_Collect_LifeCycleResetExtendedStats(t *testing.T) {
+ tests := []struct {
+ input []byte
+ expected map[string]int64
+ }{
+ {input: dataLifeCycleReset1, expected: expectedReset1},
+ {input: dataLifeCycleReset2, expected: expectedReset2},
+ {input: dataLifeCycleReset3, expected: expectedReset3},
+ }
+
+ unbound := prepareNonTLSUnbound()
+ unbound.Cumulative = false
+ require.NoError(t, unbound.Init())
+ ubClient := &mockUnboundClient{err: false}
+ unbound.client = ubClient
+
+ var collected map[string]int64
+ for i, test := range tests {
+ t.Run(fmt.Sprintf("run %d", i+1), func(t *testing.T) {
+ ubClient.data = test.input
+ collected = unbound.Collect()
+ assert.Equal(t, test.expected, collected)
+ })
+ }
+
+ testCharts(t, unbound, collected)
+}
+
+func TestUnbound_Collect_EmptyResponse(t *testing.T) {
+ unbound := prepareNonTLSUnbound()
+ require.NoError(t, unbound.Init())
+ unbound.client = mockUnboundClient{data: []byte{}, err: false}
+
+ assert.Nil(t, unbound.Collect())
+}
+
+func TestUnbound_Collect_ErrorResponse(t *testing.T) {
+ unbound := prepareNonTLSUnbound()
+ require.NoError(t, unbound.Init())
+ unbound.client = mockUnboundClient{data: []byte("error unknown command 'unknown'"), err: false}
+
+ assert.Nil(t, unbound.Collect())
+}
+
+func TestUnbound_Collect_ErrorOnSend(t *testing.T) {
+ unbound := prepareNonTLSUnbound()
+ require.NoError(t, unbound.Init())
+ unbound.client = mockUnboundClient{err: true}
+
+ assert.Nil(t, unbound.Collect())
+}
+
+func TestUnbound_Collect_ErrorOnParseBadSyntax(t *testing.T) {
+ unbound := prepareNonTLSUnbound()
+ require.NoError(t, unbound.Init())
+ data := strings.Repeat("zk_avg_latency 0\nzk_min_latency 0\nzk_mix_latency 0\n", 10)
+ unbound.client = mockUnboundClient{data: []byte(data), err: false}
+
+ assert.Nil(t, unbound.Collect())
+}
+
+func prepareNonTLSUnbound() *Unbound {
+ unbound := New()
+ unbound.ConfPath = ""
+ unbound.UseTLS = false
+
+ return unbound
+}
+
+type mockUnboundClient struct {
+ data []byte
+ err bool
+}
+
+func (m mockUnboundClient) Connect() error {
+ return nil
+}
+
+func (m mockUnboundClient) Disconnect() error {
+ return nil
+}
+
+func (m mockUnboundClient) Command(_ string, process socket.Processor) error {
+ if m.err {
+ return errors.New("mock send error")
+ }
+ s := bufio.NewScanner(bytes.NewReader(m.data))
+ for s.Scan() {
+ process(s.Bytes())
+ }
+ return nil
+}
+
+func testCharts(t *testing.T, unbound *Unbound, collected map[string]int64) {
+ t.Helper()
+ ensureChartsCreatedForEveryThread(t, unbound)
+ ensureExtendedChartsCreated(t, unbound)
+ ensureCollectedHasAllChartsDimsVarsIDs(t, unbound, collected)
+}
+
+func ensureChartsCreatedForEveryThread(t *testing.T, u *Unbound) {
+ for thread := range u.cache.threads {
+ for _, chart := range *threadCharts(thread, u.Cumulative) {
+ assert.Truef(t, u.Charts().Has(chart.ID), "chart '%s' is not created for '%s' thread", chart.ID, thread)
+ }
+ }
+}
+
+func ensureExtendedChartsCreated(t *testing.T, u *Unbound) {
+ if len(u.cache.answerRCode) == 0 {
+ return
+ }
+ for _, chart := range *extendedCharts(u.Cumulative) {
+ assert.Truef(t, u.Charts().Has(chart.ID), "chart '%s' is not added", chart.ID)
+ }
+
+ if chart := u.Charts().Get(queryTypeChart.ID); chart != nil {
+ for typ := range u.cache.queryType {
+ dimID := "num.query.type." + typ
+ assert.Truef(t, chart.HasDim(dimID), "chart '%s' has no dim for '%s' type, expected '%s'", chart.ID, typ, dimID)
+ }
+ }
+ if chart := u.Charts().Get(queryClassChart.ID); chart != nil {
+ for class := range u.cache.queryClass {
+ dimID := "num.query.class." + class
+ assert.Truef(t, chart.HasDim(dimID), "chart '%s' has no dim for '%s' class, expected '%s'", chart.ID, class, dimID)
+ }
+ }
+ if chart := u.Charts().Get(queryOpCodeChart.ID); chart != nil {
+ for opcode := range u.cache.queryOpCode {
+ dimID := "num.query.opcode." + opcode
+ assert.Truef(t, chart.HasDim(dimID), "chart '%s' has no dim for '%s' opcode, expected '%s'", chart.ID, opcode, dimID)
+ }
+ }
+ if chart := u.Charts().Get(answerRCodeChart.ID); chart != nil {
+ for rcode := range u.cache.answerRCode {
+ dimID := "num.answer.rcode." + rcode
+ assert.Truef(t, chart.HasDim(dimID), "chart '%s' has no dim for '%s' rcode, expected '%s'", chart.ID, rcode, dimID)
+ }
+ }
+}
+
+func ensureCollectedHasAllChartsDimsVarsIDs(t *testing.T, u *Unbound, collected map[string]int64) {
+ for _, chart := range *u.Charts() {
+ for _, dim := range chart.Dims {
+ if dim.ID == "mem.mod.ipsecmod" {
+ continue
+ }
+ _, ok := collected[dim.ID]
+ assert.Truef(t, ok, "collected metrics has no data for dim '%s' chart '%s'", dim.ID, chart.ID)
+ }
+ for _, v := range chart.Vars {
+ _, ok := collected[v.ID]
+ assert.Truef(t, ok, "collected metrics has no data for var '%s' chart '%s'", v.ID, chart.ID)
+ }
+ }
+}
+
+var (
+ expectedCommon = map[string]int64{
+ "thread0.num.cachehits": 21,
+ "thread0.num.cachemiss": 7,
+ "thread0.num.dnscrypt.cert": 0,
+ "thread0.num.dnscrypt.cleartext": 0,
+ "thread0.num.dnscrypt.crypted": 0,
+ "thread0.num.dnscrypt.malformed": 0,
+ "thread0.num.expired": 0,
+ "thread0.num.prefetch": 0,
+ "thread0.num.queries": 28,
+ "thread0.num.queries_ip_ratelimited": 0,
+ "thread0.num.recursivereplies": 7,
+ "thread0.num.zero_ttl": 0,
+ "thread0.recursion.time.avg": 1255,
+ "thread0.recursion.time.median": 480,
+ "thread0.requestlist.avg": 857,
+ "thread0.requestlist.current.all": 0,
+ "thread0.requestlist.current.user": 0,
+ "thread0.requestlist.exceeded": 0,
+ "thread0.requestlist.max": 6,
+ "thread0.requestlist.overwritten": 0,
+ "thread0.tcpusage": 0,
+ "thread1.num.cachehits": 13,
+ "thread1.num.cachemiss": 3,
+ "thread1.num.dnscrypt.cert": 0,
+ "thread1.num.dnscrypt.cleartext": 0,
+ "thread1.num.dnscrypt.crypted": 0,
+ "thread1.num.dnscrypt.malformed": 0,
+ "thread1.num.prefetch": 0,
+ "thread1.num.expired": 0,
+ "thread1.num.queries": 16,
+ "thread1.num.queries_ip_ratelimited": 0,
+ "thread1.num.recursivereplies": 3,
+ "thread1.num.zero_ttl": 0,
+ "thread1.recursion.time.avg": 93,
+ "thread1.recursion.time.median": 0,
+ "thread1.requestlist.avg": 0,
+ "thread1.requestlist.current.all": 0,
+ "thread1.requestlist.current.user": 0,
+ "thread1.requestlist.exceeded": 0,
+ "thread1.requestlist.max": 0,
+ "thread1.requestlist.overwritten": 0,
+ "thread1.tcpusage": 0,
+ "time.elapsed": 88,
+ "time.now": 1574094836,
+ "time.up": 88,
+ "total.num.cachehits": 34,
+ "total.num.cachemiss": 10,
+ "total.num.dnscrypt.cert": 0,
+ "total.num.dnscrypt.cleartext": 0,
+ "total.num.dnscrypt.crypted": 0,
+ "total.num.dnscrypt.malformed": 0,
+ "total.num.prefetch": 0,
+ "total.num.expired": 0,
+ "total.num.queries": 44,
+ "total.num.queries_ip_ratelimited": 0,
+ "total.num.recursivereplies": 10,
+ "total.num.zero_ttl": 0,
+ "total.recursion.time.avg": 907,
+ "total.recursion.time.median": 240,
+ "total.requestlist.avg": 600,
+ "total.requestlist.current.all": 0,
+ "total.requestlist.current.user": 0,
+ "total.requestlist.exceeded": 0,
+ "total.requestlist.max": 6,
+ "total.requestlist.overwritten": 0,
+ "total.tcpusage": 0,
+ }
+
+ expectedExtended = map[string]int64{
+ "dnscrypt_nonce.cache.count": 0,
+ "dnscrypt_shared_secret.cache.count": 0,
+ "infra.cache.count": 205,
+ "key.cache.count": 9,
+ "mem.cache.dnscrypt_nonce": 0,
+ "mem.cache.dnscrypt_shared_secret": 0,
+ "mem.cache.message": 90357,
+ "mem.cache.rrset": 178642,
+ "mem.mod.iterator": 16588,
+ "mem.mod.respip": 0,
+ "mem.mod.subnet": 74504,
+ "mem.mod.validator": 81059,
+ "mem.streamwait": 0,
+ "msg.cache.count": 81,
+ "num.answer.bogus": 0,
+ "num.answer.rcode.FORMERR": 0,
+ "num.answer.rcode.NOERROR": 40,
+ "num.answer.rcode.NOTIMPL": 0,
+ "num.answer.rcode.NXDOMAIN": 4,
+ "num.answer.rcode.REFUSED": 0,
+ "num.answer.rcode.SERVFAIL": 0,
+ "num.answer.secure": 0,
+ "num.query.aggressive.NOERROR": 2,
+ "num.query.aggressive.NXDOMAIN": 0,
+ "num.query.authzone.down": 0,
+ "num.query.authzone.up": 0,
+ "num.query.class.IN": 44,
+ "num.query.dnscrypt.replay": 0,
+ "num.query.dnscrypt.shared_secret.cachemiss": 0,
+ "num.query.edns.DO": 0,
+ "num.query.edns.present": 0,
+ "num.query.flags.AA": 0,
+ "num.query.flags.AD": 0,
+ "num.query.flags.CD": 0,
+ "num.query.flags.QR": 0,
+ "num.query.flags.RA": 0,
+ "num.query.flags.RD": 44,
+ "num.query.flags.TC": 0,
+ "num.query.flags.Z": 0,
+ "num.query.ipv6": 39,
+ "num.query.opcode.QUERY": 44,
+ "num.query.ratelimited": 0,
+ "num.query.subnet": 0,
+ "num.query.subnet_cache": 0,
+ "num.query.tcp": 0,
+ "num.query.tcpout": 1,
+ "num.query.tls": 0,
+ "num.query.tls.resume": 0,
+ "num.query.type.A": 13,
+ "num.query.type.AAAA": 13,
+ "num.query.type.MX": 13,
+ "num.query.type.PTR": 5,
+ "num.rrset.bogus": 0,
+ "rrset.cache.count": 314,
+ "thread0.num.cachehits": 21,
+ "thread0.num.cachemiss": 7,
+ "thread0.num.dnscrypt.cert": 0,
+ "thread0.num.dnscrypt.cleartext": 0,
+ "thread0.num.dnscrypt.crypted": 0,
+ "thread0.num.dnscrypt.malformed": 0,
+ "thread0.num.expired": 0,
+ "thread0.num.prefetch": 0,
+ "thread0.num.queries": 28,
+ "thread0.num.queries_ip_ratelimited": 0,
+ "thread0.num.recursivereplies": 7,
+ "thread0.num.zero_ttl": 0,
+ "thread0.recursion.time.avg": 1255,
+ "thread0.recursion.time.median": 480,
+ "thread0.requestlist.avg": 857,
+ "thread0.requestlist.current.all": 0,
+ "thread0.requestlist.current.user": 0,
+ "thread0.requestlist.exceeded": 0,
+ "thread0.requestlist.max": 6,
+ "thread0.requestlist.overwritten": 0,
+ "thread0.tcpusage": 0,
+ "thread1.num.cachehits": 13,
+ "thread1.num.cachemiss": 3,
+ "thread1.num.dnscrypt.cert": 0,
+ "thread1.num.dnscrypt.cleartext": 0,
+ "thread1.num.dnscrypt.crypted": 0,
+ "thread1.num.dnscrypt.malformed": 0,
+ "thread1.num.prefetch": 0,
+ "thread1.num.expired": 0,
+ "thread1.num.queries": 16,
+ "thread1.num.queries_ip_ratelimited": 0,
+ "thread1.num.recursivereplies": 3,
+ "thread1.num.zero_ttl": 0,
+ "thread1.recursion.time.avg": 93,
+ "thread1.recursion.time.median": 0,
+ "thread1.requestlist.avg": 0,
+ "thread1.requestlist.current.all": 0,
+ "thread1.requestlist.current.user": 0,
+ "thread1.requestlist.exceeded": 0,
+ "thread1.requestlist.max": 0,
+ "thread1.requestlist.overwritten": 0,
+ "thread1.tcpusage": 0,
+ "time.elapsed": 88,
+ "time.now": 1574094836,
+ "time.up": 88,
+ "total.num.cachehits": 34,
+ "total.num.cachemiss": 10,
+ "total.num.dnscrypt.cert": 0,
+ "total.num.dnscrypt.cleartext": 0,
+ "total.num.dnscrypt.crypted": 0,
+ "total.num.dnscrypt.malformed": 0,
+ "total.num.prefetch": 0,
+ "total.num.expired": 0,
+ "total.num.queries": 44,
+ "total.num.queries_ip_ratelimited": 0,
+ "total.num.recursivereplies": 10,
+ "total.num.zero_ttl": 0,
+ "total.recursion.time.avg": 907,
+ "total.recursion.time.median": 240,
+ "total.requestlist.avg": 600,
+ "total.requestlist.current.all": 0,
+ "total.requestlist.current.user": 0,
+ "total.requestlist.exceeded": 0,
+ "total.requestlist.max": 6,
+ "total.requestlist.overwritten": 0,
+ "total.tcpusage": 0,
+ "unwanted.queries": 0,
+ "unwanted.replies": 0,
+ }
+)
+
+var (
+ expectedCumulative1 = map[string]int64{
+ "dnscrypt_nonce.cache.count": 0,
+ "dnscrypt_shared_secret.cache.count": 0,
+ "infra.cache.count": 192,
+ "key.cache.count": 11,
+ "mem.cache.dnscrypt_nonce": 0,
+ "mem.cache.dnscrypt_shared_secret": 0,
+ "mem.cache.message": 93392,
+ "mem.cache.rrset": 175745,
+ "mem.mod.iterator": 16588,
+ "mem.mod.respip": 0,
+ "mem.mod.subnet": 74504,
+ "mem.mod.validator": 81479,
+ "mem.streamwait": 0,
+ "msg.cache.count": 94,
+ "num.answer.bogus": 0,
+ "num.answer.rcode.FORMERR": 0,
+ "num.answer.rcode.NOERROR": 184,
+ "num.answer.rcode.NOTIMPL": 0,
+ "num.answer.rcode.NXDOMAIN": 16,
+ "num.answer.rcode.REFUSED": 0,
+ "num.answer.rcode.SERVFAIL": 0,
+ "num.answer.secure": 0,
+ "num.query.aggressive.NOERROR": 1,
+ "num.query.aggressive.NXDOMAIN": 0,
+ "num.query.authzone.down": 0,
+ "num.query.authzone.up": 0,
+ "num.query.class.IN": 200,
+ "num.query.dnscrypt.replay": 0,
+ "num.query.dnscrypt.shared_secret.cachemiss": 0,
+ "num.query.edns.DO": 0,
+ "num.query.edns.present": 0,
+ "num.query.flags.AA": 0,
+ "num.query.flags.AD": 0,
+ "num.query.flags.CD": 0,
+ "num.query.flags.QR": 0,
+ "num.query.flags.RA": 0,
+ "num.query.flags.RD": 200,
+ "num.query.flags.TC": 0,
+ "num.query.flags.Z": 0,
+ "num.query.ipv6": 0,
+ "num.query.opcode.QUERY": 200,
+ "num.query.ratelimited": 0,
+ "num.query.subnet": 0,
+ "num.query.subnet_cache": 0,
+ "num.query.tcp": 0,
+ "num.query.tcpout": 0,
+ "num.query.tls": 0,
+ "num.query.tls.resume": 0,
+ "num.query.type.A": 60,
+ "num.query.type.AAAA": 60,
+ "num.query.type.MX": 60,
+ "num.query.type.PTR": 20,
+ "num.rrset.bogus": 0,
+ "rrset.cache.count": 304,
+ "thread0.num.cachehits": 80,
+ "thread0.num.cachemiss": 10,
+ "thread0.num.dnscrypt.cert": 0,
+ "thread0.num.dnscrypt.cleartext": 0,
+ "thread0.num.dnscrypt.crypted": 0,
+ "thread0.num.dnscrypt.malformed": 0,
+ "thread0.num.expired": 0,
+ "thread0.num.prefetch": 0,
+ "thread0.num.queries": 90,
+ "thread0.num.queries_ip_ratelimited": 0,
+ "thread0.num.recursivereplies": 10,
+ "thread0.num.zero_ttl": 0,
+ "thread0.recursion.time.avg": 222,
+ "thread0.recursion.time.median": 337,
+ "thread0.requestlist.avg": 100,
+ "thread0.requestlist.current.all": 0,
+ "thread0.requestlist.current.user": 0,
+ "thread0.requestlist.exceeded": 0,
+ "thread0.requestlist.max": 1,
+ "thread0.requestlist.overwritten": 0,
+ "thread0.tcpusage": 0,
+ "thread1.num.cachehits": 101,
+ "thread1.num.cachemiss": 9,
+ "thread1.num.dnscrypt.cert": 0,
+ "thread1.num.dnscrypt.cleartext": 0,
+ "thread1.num.dnscrypt.crypted": 0,
+ "thread1.num.dnscrypt.malformed": 0,
+ "thread1.num.expired": 0,
+ "thread1.num.prefetch": 0,
+ "thread1.num.queries": 110,
+ "thread1.num.queries_ip_ratelimited": 0,
+ "thread1.num.recursivereplies": 9,
+ "thread1.num.zero_ttl": 0,
+ "thread1.recursion.time.avg": 844,
+ "thread1.recursion.time.median": 360,
+ "thread1.requestlist.avg": 222,
+ "thread1.requestlist.current.all": 0,
+ "thread1.requestlist.current.user": 0,
+ "thread1.requestlist.exceeded": 0,
+ "thread1.requestlist.max": 1,
+ "thread1.requestlist.overwritten": 0,
+ "thread1.tcpusage": 0,
+ "time.elapsed": 122,
+ "time.now": 1574103378,
+ "time.up": 122,
+ "total.num.cachehits": 181,
+ "total.num.cachemiss": 19,
+ "total.num.dnscrypt.cert": 0,
+ "total.num.dnscrypt.cleartext": 0,
+ "total.num.dnscrypt.crypted": 0,
+ "total.num.dnscrypt.malformed": 0,
+ "total.num.expired": 0,
+ "total.num.prefetch": 0,
+ "total.num.queries": 200,
+ "total.num.queries_ip_ratelimited": 0,
+ "total.num.recursivereplies": 19,
+ "total.num.zero_ttl": 0,
+ "total.recursion.time.avg": 516,
+ "total.recursion.time.median": 348,
+ "total.requestlist.avg": 157,
+ "total.requestlist.current.all": 0,
+ "total.requestlist.current.user": 0,
+ "total.requestlist.exceeded": 0,
+ "total.requestlist.max": 1,
+ "total.requestlist.overwritten": 0,
+ "total.tcpusage": 0,
+ "unwanted.queries": 0,
+ "unwanted.replies": 0,
+ }
+
+ expectedCumulative2 = map[string]int64{
+ "dnscrypt_nonce.cache.count": 0,
+ "dnscrypt_shared_secret.cache.count": 0,
+ "infra.cache.count": 192,
+ "key.cache.count": 11,
+ "mem.cache.dnscrypt_nonce": 0,
+ "mem.cache.dnscrypt_shared_secret": 0,
+ "mem.cache.message": 93392,
+ "mem.cache.rrset": 175745,
+ "mem.mod.iterator": 16588,
+ "mem.mod.respip": 0,
+ "mem.mod.subnet": 74504,
+ "mem.mod.validator": 81479,
+ "mem.streamwait": 0,
+ "msg.cache.count": 94,
+ "num.answer.bogus": 0,
+ "num.answer.rcode.FORMERR": 0,
+ "num.answer.rcode.NOERROR": 274,
+ "num.answer.rcode.NOTIMPL": 0,
+ "num.answer.rcode.NXDOMAIN": 16,
+ "num.answer.rcode.REFUSED": 0,
+ "num.answer.rcode.SERVFAIL": 0,
+ "num.answer.secure": 0,
+ "num.query.aggressive.NOERROR": 1,
+ "num.query.aggressive.NXDOMAIN": 0,
+ "num.query.authzone.down": 0,
+ "num.query.authzone.up": 0,
+ "num.query.class.IN": 290,
+ "num.query.dnscrypt.replay": 0,
+ "num.query.dnscrypt.shared_secret.cachemiss": 0,
+ "num.query.edns.DO": 0,
+ "num.query.edns.present": 0,
+ "num.query.flags.AA": 0,
+ "num.query.flags.AD": 0,
+ "num.query.flags.CD": 0,
+ "num.query.flags.QR": 0,
+ "num.query.flags.RA": 0,
+ "num.query.flags.RD": 290,
+ "num.query.flags.TC": 0,
+ "num.query.flags.Z": 0,
+ "num.query.ipv6": 0,
+ "num.query.opcode.QUERY": 290,
+ "num.query.ratelimited": 0,
+ "num.query.subnet": 0,
+ "num.query.subnet_cache": 0,
+ "num.query.tcp": 0,
+ "num.query.tcpout": 0,
+ "num.query.tls": 0,
+ "num.query.tls.resume": 0,
+ "num.query.type.A": 90,
+ "num.query.type.AAAA": 90,
+ "num.query.type.MX": 90,
+ "num.query.type.PTR": 20,
+ "num.rrset.bogus": 0,
+ "rrset.cache.count": 304,
+ "thread0.num.cachehits": 123,
+ "thread0.num.cachemiss": 10,
+ "thread0.num.dnscrypt.cert": 0,
+ "thread0.num.dnscrypt.cleartext": 0,
+ "thread0.num.dnscrypt.crypted": 0,
+ "thread0.num.dnscrypt.malformed": 0,
+ "thread0.num.expired": 0,
+ "thread0.num.prefetch": 0,
+ "thread0.num.queries": 133,
+ "thread0.num.queries_ip_ratelimited": 0,
+ "thread0.num.recursivereplies": 10,
+ "thread0.num.zero_ttl": 0,
+ "thread0.recursion.time.avg": 0,
+ "thread0.recursion.time.median": 0,
+ "thread0.requestlist.avg": 0,
+ "thread0.requestlist.current.all": 0,
+ "thread0.requestlist.current.user": 0,
+ "thread0.requestlist.exceeded": 0,
+ "thread0.requestlist.max": 1,
+ "thread0.requestlist.overwritten": 0,
+ "thread0.tcpusage": 0,
+ "thread1.num.cachehits": 148,
+ "thread1.num.cachemiss": 9,
+ "thread1.num.dnscrypt.cert": 0,
+ "thread1.num.dnscrypt.cleartext": 0,
+ "thread1.num.dnscrypt.crypted": 0,
+ "thread1.num.dnscrypt.malformed": 0,
+ "thread1.num.prefetch": 0,
+ "thread1.num.expired": 0,
+ "thread1.num.queries": 157,
+ "thread1.num.queries_ip_ratelimited": 0,
+ "thread1.num.recursivereplies": 9,
+ "thread1.num.zero_ttl": 0,
+ "thread1.recursion.time.avg": 0,
+ "thread1.recursion.time.median": 0,
+ "thread1.requestlist.avg": 0,
+ "thread1.requestlist.current.all": 0,
+ "thread1.requestlist.current.user": 0,
+ "thread1.requestlist.exceeded": 0,
+ "thread1.requestlist.max": 1,
+ "thread1.requestlist.overwritten": 0,
+ "thread1.tcpusage": 0,
+ "time.elapsed": 82,
+ "time.now": 1574103461,
+ "time.up": 205,
+ "total.num.cachehits": 271,
+ "total.num.cachemiss": 19,
+ "total.num.dnscrypt.cert": 0,
+ "total.num.dnscrypt.cleartext": 0,
+ "total.num.dnscrypt.crypted": 0,
+ "total.num.dnscrypt.malformed": 0,
+ "total.num.prefetch": 0,
+ "total.num.expired": 0,
+ "total.num.queries": 290,
+ "total.num.queries_ip_ratelimited": 0,
+ "total.num.recursivereplies": 19,
+ "total.num.zero_ttl": 0,
+ "total.recursion.time.avg": 0,
+ "total.recursion.time.median": 0,
+ "total.requestlist.avg": 0,
+ "total.requestlist.current.all": 0,
+ "total.requestlist.current.user": 0,
+ "total.requestlist.exceeded": 0,
+ "total.requestlist.max": 1,
+ "total.requestlist.overwritten": 0,
+ "total.tcpusage": 0,
+ "unwanted.queries": 0,
+ "unwanted.replies": 0,
+ }
+
+ expectedCumulative3 = map[string]int64{
+ "dnscrypt_nonce.cache.count": 0,
+ "dnscrypt_shared_secret.cache.count": 0,
+ "infra.cache.count": 232,
+ "key.cache.count": 14,
+ "mem.cache.dnscrypt_nonce": 0,
+ "mem.cache.dnscrypt_shared_secret": 0,
+ "mem.cache.message": 101198,
+ "mem.cache.rrset": 208839,
+ "mem.mod.iterator": 16588,
+ "mem.mod.respip": 0,
+ "mem.mod.subnet": 74504,
+ "mem.mod.validator": 85725,
+ "mem.streamwait": 0,
+ "msg.cache.count": 119,
+ "num.answer.bogus": 0,
+ "num.answer.rcode.FORMERR": 0,
+ "num.answer.rcode.NOERROR": 334,
+ "num.answer.rcode.NOTIMPL": 0,
+ "num.answer.rcode.NXDOMAIN": 16,
+ "num.answer.rcode.REFUSED": 0,
+ "num.answer.rcode.SERVFAIL": 10,
+ "num.answer.rcode.nodata": 20,
+ "num.answer.secure": 0,
+ "num.query.aggressive.NOERROR": 1,
+ "num.query.aggressive.NXDOMAIN": 0,
+ "num.query.authzone.down": 0,
+ "num.query.authzone.up": 0,
+ "num.query.class.IN": 360,
+ "num.query.dnscrypt.replay": 0,
+ "num.query.dnscrypt.shared_secret.cachemiss": 0,
+ "num.query.edns.DO": 0,
+ "num.query.edns.present": 0,
+ "num.query.flags.AA": 0,
+ "num.query.flags.AD": 0,
+ "num.query.flags.CD": 0,
+ "num.query.flags.QR": 0,
+ "num.query.flags.RA": 0,
+ "num.query.flags.RD": 360,
+ "num.query.flags.TC": 0,
+ "num.query.flags.Z": 0,
+ "num.query.ipv6": 0,
+ "num.query.opcode.QUERY": 360,
+ "num.query.ratelimited": 0,
+ "num.query.subnet": 0,
+ "num.query.subnet_cache": 0,
+ "num.query.tcp": 0,
+ "num.query.tcpout": 0,
+ "num.query.tls": 0,
+ "num.query.tls.resume": 0,
+ "num.query.type.A": 120,
+ "num.query.type.AAAA": 110,
+ "num.query.type.MX": 110,
+ "num.query.type.PTR": 20,
+ "num.rrset.bogus": 0,
+ "rrset.cache.count": 401,
+ "thread0.num.cachehits": 150,
+ "thread0.num.cachemiss": 15,
+ "thread0.num.dnscrypt.cert": 0,
+ "thread0.num.dnscrypt.cleartext": 0,
+ "thread0.num.dnscrypt.crypted": 0,
+ "thread0.num.dnscrypt.malformed": 0,
+ "thread0.num.expired": 0,
+ "thread0.num.prefetch": 0,
+ "thread0.num.queries": 165,
+ "thread0.num.queries_ip_ratelimited": 0,
+ "thread0.num.recursivereplies": 15,
+ "thread0.num.zero_ttl": 0,
+ "thread0.recursion.time.avg": 261,
+ "thread0.recursion.time.median": 318,
+ "thread0.requestlist.avg": 66,
+ "thread0.requestlist.current.all": 0,
+ "thread0.requestlist.current.user": 0,
+ "thread0.requestlist.exceeded": 0,
+ "thread0.requestlist.max": 1,
+ "thread0.requestlist.overwritten": 0,
+ "thread0.tcpusage": 0,
+ "thread1.num.cachehits": 184,
+ "thread1.num.cachemiss": 11,
+ "thread1.num.dnscrypt.cert": 0,
+ "thread1.num.dnscrypt.cleartext": 0,
+ "thread1.num.dnscrypt.crypted": 0,
+ "thread1.num.dnscrypt.malformed": 0,
+ "thread1.num.prefetch": 0,
+ "thread1.num.expired": 0,
+ "thread1.num.queries": 195,
+ "thread1.num.queries_ip_ratelimited": 0,
+ "thread1.num.recursivereplies": 11,
+ "thread1.num.zero_ttl": 0,
+ "thread1.recursion.time.avg": 709,
+ "thread1.recursion.time.median": 294,
+ "thread1.requestlist.avg": 363,
+ "thread1.requestlist.current.all": 0,
+ "thread1.requestlist.current.user": 0,
+ "thread1.requestlist.exceeded": 0,
+ "thread1.requestlist.max": 2,
+ "thread1.requestlist.overwritten": 0,
+ "thread1.tcpusage": 0,
+ "time.elapsed": 82,
+ "time.now": 1574103543,
+ "time.up": 288,
+ "total.num.cachehits": 334,
+ "total.num.cachemiss": 26,
+ "total.num.dnscrypt.cert": 0,
+ "total.num.dnscrypt.cleartext": 0,
+ "total.num.dnscrypt.crypted": 0,
+ "total.num.dnscrypt.malformed": 0,
+ "total.num.prefetch": 0,
+ "total.num.expired": 0,
+ "total.num.queries": 360,
+ "total.num.queries_ip_ratelimited": 0,
+ "total.num.recursivereplies": 26,
+ "total.num.zero_ttl": 0,
+ "total.recursion.time.avg": 450,
+ "total.recursion.time.median": 306,
+ "total.requestlist.avg": 192,
+ "total.requestlist.current.all": 0,
+ "total.requestlist.current.user": 0,
+ "total.requestlist.exceeded": 0,
+ "total.requestlist.max": 2,
+ "total.requestlist.overwritten": 0,
+ "total.tcpusage": 0,
+ "unwanted.queries": 0,
+ "unwanted.replies": 0,
+ }
+)
+
+var (
+ expectedReset1 = map[string]int64{
+ "dnscrypt_nonce.cache.count": 0,
+ "dnscrypt_shared_secret.cache.count": 0,
+ "infra.cache.count": 181,
+ "key.cache.count": 10,
+ "mem.cache.dnscrypt_nonce": 0,
+ "mem.cache.dnscrypt_shared_secret": 0,
+ "mem.cache.message": 86064,
+ "mem.cache.rrset": 172757,
+ "mem.mod.iterator": 16588,
+ "mem.mod.respip": 0,
+ "mem.mod.subnet": 74504,
+ "mem.mod.validator": 79979,
+ "mem.streamwait": 0,
+ "msg.cache.count": 67,
+ "num.answer.bogus": 0,
+ "num.answer.rcode.FORMERR": 0,
+ "num.answer.rcode.NOERROR": 90,
+ "num.answer.rcode.NOTIMPL": 0,
+ "num.answer.rcode.NXDOMAIN": 10,
+ "num.answer.rcode.REFUSED": 0,
+ "num.answer.rcode.SERVFAIL": 0,
+ "num.answer.rcode.nodata": 10,
+ "num.answer.secure": 0,
+ "num.query.aggressive.NOERROR": 2,
+ "num.query.aggressive.NXDOMAIN": 0,
+ "num.query.authzone.down": 0,
+ "num.query.authzone.up": 0,
+ "num.query.class.IN": 100,
+ "num.query.dnscrypt.replay": 0,
+ "num.query.dnscrypt.shared_secret.cachemiss": 0,
+ "num.query.edns.DO": 0,
+ "num.query.edns.present": 0,
+ "num.query.flags.AA": 0,
+ "num.query.flags.AD": 0,
+ "num.query.flags.CD": 0,
+ "num.query.flags.QR": 0,
+ "num.query.flags.RA": 0,
+ "num.query.flags.RD": 100,
+ "num.query.flags.TC": 0,
+ "num.query.flags.Z": 0,
+ "num.query.ipv6": 0,
+ "num.query.opcode.QUERY": 100,
+ "num.query.ratelimited": 0,
+ "num.query.subnet": 0,
+ "num.query.subnet_cache": 0,
+ "num.query.tcp": 0,
+ "num.query.tcpout": 1,
+ "num.query.tls": 0,
+ "num.query.tls.resume": 0,
+ "num.query.type.A": 30,
+ "num.query.type.AAAA": 30,
+ "num.query.type.MX": 30,
+ "num.query.type.PTR": 10,
+ "num.rrset.bogus": 0,
+ "rrset.cache.count": 303,
+ "thread0.num.cachehits": 44,
+ "thread0.num.cachemiss": 7,
+ "thread0.num.dnscrypt.cert": 0,
+ "thread0.num.dnscrypt.cleartext": 0,
+ "thread0.num.dnscrypt.crypted": 0,
+ "thread0.num.dnscrypt.malformed": 0,
+ "thread0.num.expired": 0,
+ "thread0.num.prefetch": 0,
+ "thread0.num.queries": 51,
+ "thread0.num.queries_ip_ratelimited": 0,
+ "thread0.num.recursivereplies": 7,
+ "thread0.num.zero_ttl": 0,
+ "thread0.recursion.time.avg": 365,
+ "thread0.recursion.time.median": 57,
+ "thread0.requestlist.avg": 0,
+ "thread0.requestlist.current.all": 0,
+ "thread0.requestlist.current.user": 0,
+ "thread0.requestlist.exceeded": 0,
+ "thread0.requestlist.max": 0,
+ "thread0.requestlist.overwritten": 0,
+ "thread0.tcpusage": 0,
+ "thread1.num.cachehits": 46,
+ "thread1.num.cachemiss": 3,
+ "thread1.num.dnscrypt.cert": 0,
+ "thread1.num.dnscrypt.cleartext": 0,
+ "thread1.num.dnscrypt.crypted": 0,
+ "thread1.num.dnscrypt.malformed": 0,
+ "thread1.num.prefetch": 0,
+ "thread1.num.expired": 0,
+ "thread1.num.queries": 49,
+ "thread1.num.queries_ip_ratelimited": 0,
+ "thread1.num.recursivereplies": 3,
+ "thread1.num.zero_ttl": 0,
+ "thread1.recursion.time.avg": 1582,
+ "thread1.recursion.time.median": 0,
+ "thread1.requestlist.avg": 0,
+ "thread1.requestlist.current.all": 0,
+ "thread1.requestlist.current.user": 0,
+ "thread1.requestlist.exceeded": 0,
+ "thread1.requestlist.max": 0,
+ "thread1.requestlist.overwritten": 0,
+ "thread1.tcpusage": 0,
+ "time.elapsed": 45,
+ "time.now": 1574103644,
+ "time.up": 45,
+ "total.num.cachehits": 90,
+ "total.num.cachemiss": 10,
+ "total.num.dnscrypt.cert": 0,
+ "total.num.dnscrypt.cleartext": 0,
+ "total.num.dnscrypt.crypted": 0,
+ "total.num.dnscrypt.malformed": 0,
+ "total.num.prefetch": 0,
+ "total.num.expired": 0,
+ "total.num.queries": 100,
+ "total.num.queries_ip_ratelimited": 0,
+ "total.num.recursivereplies": 10,
+ "total.num.zero_ttl": 0,
+ "total.recursion.time.avg": 730,
+ "total.recursion.time.median": 28,
+ "total.requestlist.avg": 0,
+ "total.requestlist.current.all": 0,
+ "total.requestlist.current.user": 0,
+ "total.requestlist.exceeded": 0,
+ "total.requestlist.max": 0,
+ "total.requestlist.overwritten": 0,
+ "total.tcpusage": 0,
+ "unwanted.queries": 0,
+ "unwanted.replies": 0,
+ }
+ expectedReset2 = map[string]int64{
+ "dnscrypt_nonce.cache.count": 0,
+ "dnscrypt_shared_secret.cache.count": 0,
+ "infra.cache.count": 181,
+ "key.cache.count": 10,
+ "mem.cache.dnscrypt_nonce": 0,
+ "mem.cache.dnscrypt_shared_secret": 0,
+ "mem.cache.message": 86064,
+ "mem.cache.rrset": 172757,
+ "mem.mod.iterator": 16588,
+ "mem.mod.respip": 0,
+ "mem.mod.subnet": 74504,
+ "mem.mod.validator": 79979,
+ "mem.streamwait": 0,
+ "msg.cache.count": 67,
+ "num.answer.bogus": 0,
+ "num.answer.rcode.FORMERR": 0,
+ "num.answer.rcode.NOERROR": 0,
+ "num.answer.rcode.NOTIMPL": 0,
+ "num.answer.rcode.NXDOMAIN": 0,
+ "num.answer.rcode.REFUSED": 0,
+ "num.answer.rcode.SERVFAIL": 0,
+ "num.answer.rcode.nodata": 0,
+ "num.answer.secure": 0,
+ "num.query.aggressive.NOERROR": 0,
+ "num.query.aggressive.NXDOMAIN": 0,
+ "num.query.authzone.down": 0,
+ "num.query.authzone.up": 0,
+ "num.query.class.IN": 0,
+ "num.query.dnscrypt.replay": 0,
+ "num.query.dnscrypt.shared_secret.cachemiss": 0,
+ "num.query.edns.DO": 0,
+ "num.query.edns.present": 0,
+ "num.query.flags.AA": 0,
+ "num.query.flags.AD": 0,
+ "num.query.flags.CD": 0,
+ "num.query.flags.QR": 0,
+ "num.query.flags.RA": 0,
+ "num.query.flags.RD": 0,
+ "num.query.flags.TC": 0,
+ "num.query.flags.Z": 0,
+ "num.query.ipv6": 0,
+ "num.query.opcode.QUERY": 0,
+ "num.query.ratelimited": 0,
+ "num.query.subnet": 0,
+ "num.query.subnet_cache": 0,
+ "num.query.tcp": 0,
+ "num.query.tcpout": 0,
+ "num.query.tls": 0,
+ "num.query.tls.resume": 0,
+ "num.query.type.A": 0,
+ "num.query.type.AAAA": 0,
+ "num.query.type.MX": 0,
+ "num.query.type.PTR": 0,
+ "num.rrset.bogus": 0,
+ "rrset.cache.count": 303,
+ "thread0.num.cachehits": 0,
+ "thread0.num.cachemiss": 0,
+ "thread0.num.dnscrypt.cert": 0,
+ "thread0.num.dnscrypt.cleartext": 0,
+ "thread0.num.dnscrypt.crypted": 0,
+ "thread0.num.dnscrypt.malformed": 0,
+ "thread0.num.expired": 0,
+ "thread0.num.prefetch": 0,
+ "thread0.num.queries": 0,
+ "thread0.num.queries_ip_ratelimited": 0,
+ "thread0.num.recursivereplies": 0,
+ "thread0.num.zero_ttl": 0,
+ "thread0.recursion.time.avg": 0,
+ "thread0.recursion.time.median": 0,
+ "thread0.requestlist.avg": 0,
+ "thread0.requestlist.current.all": 0,
+ "thread0.requestlist.current.user": 0,
+ "thread0.requestlist.exceeded": 0,
+ "thread0.requestlist.max": 0,
+ "thread0.requestlist.overwritten": 0,
+ "thread0.tcpusage": 0,
+ "thread1.num.cachehits": 0,
+ "thread1.num.cachemiss": 0,
+ "thread1.num.dnscrypt.cert": 0,
+ "thread1.num.dnscrypt.cleartext": 0,
+ "thread1.num.dnscrypt.crypted": 0,
+ "thread1.num.dnscrypt.malformed": 0,
+ "thread1.num.prefetch": 0,
+ "thread1.num.expired": 0,
+ "thread1.num.queries": 0,
+ "thread1.num.queries_ip_ratelimited": 0,
+ "thread1.num.recursivereplies": 0,
+ "thread1.num.zero_ttl": 0,
+ "thread1.recursion.time.avg": 0,
+ "thread1.recursion.time.median": 0,
+ "thread1.requestlist.avg": 0,
+ "thread1.requestlist.current.all": 0,
+ "thread1.requestlist.current.user": 0,
+ "thread1.requestlist.exceeded": 0,
+ "thread1.requestlist.max": 0,
+ "thread1.requestlist.overwritten": 0,
+ "thread1.tcpusage": 0,
+ "time.elapsed": 26,
+ "time.now": 1574103671,
+ "time.up": 71,
+ "total.num.cachehits": 0,
+ "total.num.cachemiss": 0,
+ "total.num.dnscrypt.cert": 0,
+ "total.num.dnscrypt.cleartext": 0,
+ "total.num.dnscrypt.crypted": 0,
+ "total.num.dnscrypt.malformed": 0,
+ "total.num.prefetch": 0,
+ "total.num.expired": 0,
+ "total.num.queries": 0,
+ "total.num.queries_ip_ratelimited": 0,
+ "total.num.recursivereplies": 0,
+ "total.num.zero_ttl": 0,
+ "total.recursion.time.avg": 0,
+ "total.recursion.time.median": 0,
+ "total.requestlist.avg": 0,
+ "total.requestlist.current.all": 0,
+ "total.requestlist.current.user": 0,
+ "total.requestlist.exceeded": 0,
+ "total.requestlist.max": 0,
+ "total.requestlist.overwritten": 0,
+ "total.tcpusage": 0,
+ "unwanted.queries": 0,
+ "unwanted.replies": 0,
+ }
+
+ expectedReset3 = map[string]int64{
+ "dnscrypt_nonce.cache.count": 0,
+ "dnscrypt_shared_secret.cache.count": 0,
+ "infra.cache.count": 303,
+ "key.cache.count": 15,
+ "mem.cache.dnscrypt_nonce": 0,
+ "mem.cache.dnscrypt_shared_secret": 0,
+ "mem.cache.message": 105471,
+ "mem.cache.rrset": 235917,
+ "mem.mod.iterator": 16588,
+ "mem.mod.respip": 0,
+ "mem.mod.subnet": 74504,
+ "mem.mod.validator": 87270,
+ "mem.streamwait": 0,
+ "msg.cache.count": 127,
+ "num.answer.bogus": 0,
+ "num.answer.rcode.FORMERR": 0,
+ "num.answer.rcode.NOERROR": 60,
+ "num.answer.rcode.NOTIMPL": 0,
+ "num.answer.rcode.NXDOMAIN": 10,
+ "num.answer.rcode.REFUSED": 0,
+ "num.answer.rcode.SERVFAIL": 0,
+ "num.answer.rcode.nodata": 10,
+ "num.answer.secure": 0,
+ "num.query.aggressive.NOERROR": 2,
+ "num.query.aggressive.NXDOMAIN": 0,
+ "num.query.authzone.down": 0,
+ "num.query.authzone.up": 0,
+ "num.query.class.IN": 70,
+ "num.query.dnscrypt.replay": 0,
+ "num.query.dnscrypt.shared_secret.cachemiss": 0,
+ "num.query.edns.DO": 0,
+ "num.query.edns.present": 0,
+ "num.query.flags.AA": 0,
+ "num.query.flags.AD": 0,
+ "num.query.flags.CD": 0,
+ "num.query.flags.QR": 0,
+ "num.query.flags.RA": 0,
+ "num.query.flags.RD": 70,
+ "num.query.flags.TC": 0,
+ "num.query.flags.Z": 0,
+ "num.query.ipv6": 0,
+ "num.query.opcode.QUERY": 70,
+ "num.query.ratelimited": 0,
+ "num.query.subnet": 0,
+ "num.query.subnet_cache": 0,
+ "num.query.tcp": 0,
+ "num.query.tcpout": 0,
+ "num.query.tls": 0,
+ "num.query.tls.resume": 0,
+ "num.query.type.A": 20,
+ "num.query.type.AAAA": 20,
+ "num.query.type.MX": 20,
+ "num.query.type.PTR": 10,
+ "num.rrset.bogus": 0,
+ "rrset.cache.count": 501,
+ "thread0.num.cachehits": 30,
+ "thread0.num.cachemiss": 4,
+ "thread0.num.dnscrypt.cert": 0,
+ "thread0.num.dnscrypt.cleartext": 0,
+ "thread0.num.dnscrypt.crypted": 0,
+ "thread0.num.dnscrypt.malformed": 0,
+ "thread0.num.expired": 0,
+ "thread0.num.prefetch": 0,
+ "thread0.num.queries": 34,
+ "thread0.num.queries_ip_ratelimited": 0,
+ "thread0.num.recursivereplies": 4,
+ "thread0.num.zero_ttl": 0,
+ "thread0.recursion.time.avg": 541,
+ "thread0.recursion.time.median": 98,
+ "thread0.requestlist.avg": 0,
+ "thread0.requestlist.current.all": 0,
+ "thread0.requestlist.current.user": 0,
+ "thread0.requestlist.exceeded": 0,
+ "thread0.requestlist.max": 0,
+ "thread0.requestlist.overwritten": 0,
+ "thread0.tcpusage": 0,
+ "thread1.num.cachehits": 33,
+ "thread1.num.cachemiss": 3,
+ "thread1.num.dnscrypt.cert": 0,
+ "thread1.num.dnscrypt.cleartext": 0,
+ "thread1.num.dnscrypt.crypted": 0,
+ "thread1.num.dnscrypt.malformed": 0,
+ "thread1.num.prefetch": 0,
+ "thread1.num.expired": 0,
+ "thread1.num.queries": 36,
+ "thread1.num.queries_ip_ratelimited": 0,
+ "thread1.num.recursivereplies": 3,
+ "thread1.num.zero_ttl": 0,
+ "thread1.recursion.time.avg": 62,
+ "thread1.recursion.time.median": 0,
+ "thread1.requestlist.avg": 1666,
+ "thread1.requestlist.current.all": 0,
+ "thread1.requestlist.current.user": 0,
+ "thread1.requestlist.exceeded": 0,
+ "thread1.requestlist.max": 5,
+ "thread1.requestlist.overwritten": 0,
+ "thread1.tcpusage": 0,
+ "time.elapsed": 59,
+ "time.now": 1574103731,
+ "time.up": 131,
+ "total.num.cachehits": 63,
+ "total.num.cachemiss": 7,
+ "total.num.dnscrypt.cert": 0,
+ "total.num.dnscrypt.cleartext": 0,
+ "total.num.dnscrypt.crypted": 0,
+ "total.num.dnscrypt.malformed": 0,
+ "total.num.prefetch": 0,
+ "total.num.expired": 0,
+ "total.num.queries": 70,
+ "total.num.queries_ip_ratelimited": 0,
+ "total.num.recursivereplies": 7,
+ "total.num.zero_ttl": 0,
+ "total.recursion.time.avg": 336,
+ "total.recursion.time.median": 49,
+ "total.requestlist.avg": 714,
+ "total.requestlist.current.all": 0,
+ "total.requestlist.current.user": 0,
+ "total.requestlist.exceeded": 0,
+ "total.requestlist.max": 5,
+ "total.requestlist.overwritten": 0,
+ "total.tcpusage": 0,
+ "unwanted.queries": 0,
+ "unwanted.replies": 0,
+ }
+)