From a751049c1a368de141ad5a7287dcdb2435794e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Francisco=20Loff?= Date: Wed, 29 Jan 2025 21:32:41 +0000 Subject: [PATCH 1/4] Collect unbound histogram stats --- plugins/inputs/unbound/unbound.go | 24 ++++---- plugins/inputs/unbound/unbound_test.go | 84 +++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 15 deletions(-) diff --git a/plugins/inputs/unbound/unbound.go b/plugins/inputs/unbound/unbound.go index 9938a9a930f30..918f56f62675a 100644 --- a/plugins/inputs/unbound/unbound.go +++ b/plugins/inputs/unbound/unbound.go @@ -15,7 +15,6 @@ import ( "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/config" - "github.com/influxdata/telegraf/filter" "github.com/influxdata/telegraf/internal" "github.com/influxdata/telegraf/plugins/inputs" ) @@ -47,13 +46,6 @@ func (*Unbound) SampleConfig() string { // All the dots in stat name will replaced by underscores. Histogram statistics will not be collected. func (s *Unbound) Gather(acc telegraf.Accumulator) error { - // Always exclude histogram statistics - statExcluded := []string{"histogram.*"} - filterExcluded, err := filter.Compile(statExcluded) - if err != nil { - return err - } - out, err := s.run(*s) if err != nil { return fmt.Errorf("error gathering metrics: %w", err) @@ -75,11 +67,6 @@ func (s *Unbound) Gather(acc telegraf.Accumulator) error { stat := cols[0] value := cols[1] - // Filter value - if filterExcluded.Match(stat) { - continue - } - fieldValue, err := strconv.ParseFloat(value, 64) if err != nil { acc.AddError(fmt.Errorf("expected a numerical value for %s = %v", stat, value)) @@ -106,6 +93,17 @@ func (s *Unbound) Gather(acc telegraf.Accumulator) error { fieldsThreads[threadID][field] = fieldValue } } + } else if strings.HasPrefix(stat, "histogram") { + statTokens := strings.Split(stat, ".") + if len(statTokens) > 1 { + lbound, err := strconv.ParseFloat(strings.Join(statTokens[1:3], "."), 64) + if err != nil { + acc.AddError(fmt.Errorf("expected a numeric value for the histogram bucket lower bound: %s", strings.Join(statTokens[1:3], "."))) + continue + } + field := fmt.Sprintf("%s_%f", statTokens[0], lbound) + fields[field] = fieldValue + } } else { field := strings.ReplaceAll(stat, ".", "_") fields[field] = fieldValue diff --git a/plugins/inputs/unbound/unbound_test.go b/plugins/inputs/unbound/unbound_test.go index c2011a01a3da6..f9cfc8c84a390 100644 --- a/plugins/inputs/unbound/unbound_test.go +++ b/plugins/inputs/unbound/unbound_test.go @@ -27,7 +27,7 @@ func TestParseFullOutput(t *testing.T) { require.True(t, acc.HasMeasurement("unbound")) require.Len(t, acc.Metrics, 1) - require.Equal(t, 63, acc.NFields()) + require.Equal(t, 103, acc.NFields()) acc.AssertContainsFields(t, "unbound", parsedFullOutput) } @@ -46,7 +46,7 @@ func TestParseFullOutputThreadAsTag(t *testing.T) { require.True(t, acc.HasMeasurement("unbound_threads")) require.Len(t, acc.Metrics, 2) - require.Equal(t, 63, acc.NFields()) + require.Equal(t, 103, acc.NFields()) acc.AssertContainsFields(t, "unbound", parsedFullOutputThreadAsTagMeasurementUnbound) acc.AssertContainsFields(t, "unbound_threads", parsedFullOutputThreadAsTagMeasurementUnboundThreads) @@ -87,6 +87,46 @@ var parsedFullOutput = map[string]interface{}{ "mem_cache_message": float64(320000), "mem_mod_iterator": float64(16532), "mem_mod_validator": float64(112097), + "histogram_0.000000": float64(20), + "histogram_0.000001": float64(5), + "histogram_0.000002": float64(13), + "histogram_0.000004": float64(18), + "histogram_0.000008": float64(67), + "histogram_0.000016": float64(94), + "histogram_0.000032": float64(113), + "histogram_0.000064": float64(190), + "histogram_0.000128": float64(369), + "histogram_0.000256": float64(1034), + "histogram_0.000512": float64(5503), + "histogram_0.001024": float64(155724), + "histogram_0.002048": float64(107623), + "histogram_0.004096": float64(17739), + "histogram_0.008192": float64(4177), + "histogram_0.016384": float64(82021), + "histogram_0.032768": float64(33772), + "histogram_0.065536": float64(7159), + "histogram_0.131072": float64(1109), + "histogram_0.262144": float64(295), + "histogram_0.524288": float64(890), + "histogram_1.000000": float64(136), + "histogram_1024.000000": float64(0), + "histogram_128.000000": float64(0), + "histogram_131072.000000": float64(0), + "histogram_16.000000": float64(2), + "histogram_16384.000000": float64(0), + "histogram_2.000000": float64(233), + "histogram_2048.000000": float64(0), + "histogram_256.000000": float64(0), + "histogram_262144.000000": float64(0), + "histogram_32.000000": float64(0), + "histogram_32768.000000": float64(0), + "histogram_4.000000": float64(2), + "histogram_4096.000000": float64(0), + "histogram_512.000000": float64(0), + "histogram_64.000000": float64(0), + "histogram_65536.000000": float64(0), + "histogram_8.000000": float64(0), + "histogram_8192.000000": float64(0), "num_query_type_A": float64(7062688), "num_query_type_PTR": float64(43097), "num_query_type_TXT": float64(2998), @@ -156,6 +196,46 @@ var parsedFullOutputThreadAsTagMeasurementUnbound = map[string]interface{}{ "mem_cache_message": float64(320000), "mem_mod_iterator": float64(16532), "mem_mod_validator": float64(112097), + "histogram_0.000000": float64(20), + "histogram_0.000001": float64(5), + "histogram_0.000002": float64(13), + "histogram_0.000004": float64(18), + "histogram_0.000008": float64(67), + "histogram_0.000016": float64(94), + "histogram_0.000032": float64(113), + "histogram_0.000064": float64(190), + "histogram_0.000128": float64(369), + "histogram_0.000256": float64(1034), + "histogram_0.000512": float64(5503), + "histogram_0.001024": float64(155724), + "histogram_0.002048": float64(107623), + "histogram_0.004096": float64(17739), + "histogram_0.008192": float64(4177), + "histogram_0.016384": float64(82021), + "histogram_0.032768": float64(33772), + "histogram_0.065536": float64(7159), + "histogram_0.131072": float64(1109), + "histogram_0.262144": float64(295), + "histogram_0.524288": float64(890), + "histogram_1.000000": float64(136), + "histogram_1024.000000": float64(0), + "histogram_128.000000": float64(0), + "histogram_131072.000000": float64(0), + "histogram_16.000000": float64(2), + "histogram_16384.000000": float64(0), + "histogram_2.000000": float64(233), + "histogram_2048.000000": float64(0), + "histogram_256.000000": float64(0), + "histogram_262144.000000": float64(0), + "histogram_32.000000": float64(0), + "histogram_32768.000000": float64(0), + "histogram_4.000000": float64(2), + "histogram_4096.000000": float64(0), + "histogram_512.000000": float64(0), + "histogram_64.000000": float64(0), + "histogram_65536.000000": float64(0), + "histogram_8.000000": float64(0), + "histogram_8192.000000": float64(0), "num_query_type_A": float64(7062688), "num_query_type_PTR": float64(43097), "num_query_type_TXT": float64(2998), From c24d62581eb61e04eda827cd9ae51bcb3af114af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Francisco=20Loff?= Date: Fri, 31 Jan 2025 18:04:26 +0000 Subject: [PATCH 2/4] Feat(inputs.unbound): Add option to collect histogram statistics --- plugins/inputs/unbound/unbound.go | 4 +- plugins/inputs/unbound/unbound_test.go | 165 ++++++++++++++++++++++++- 2 files changed, 166 insertions(+), 3 deletions(-) diff --git a/plugins/inputs/unbound/unbound.go b/plugins/inputs/unbound/unbound.go index 918f56f62675a..dfa3acc439d39 100644 --- a/plugins/inputs/unbound/unbound.go +++ b/plugins/inputs/unbound/unbound.go @@ -34,6 +34,7 @@ type Unbound struct { Server string `toml:"server"` ThreadAsTag bool `toml:"thread_as_tag"` ConfigFile string `toml:"config_file"` + Histogram bool `toml:"histogram"` run runner } @@ -95,7 +96,7 @@ func (s *Unbound) Gather(acc telegraf.Accumulator) error { } } else if strings.HasPrefix(stat, "histogram") { statTokens := strings.Split(stat, ".") - if len(statTokens) > 1 { + if s.Histogram && len(statTokens) > 1 { lbound, err := strconv.ParseFloat(strings.Join(statTokens[1:3], "."), 64) if err != nil { acc.AddError(fmt.Errorf("expected a numeric value for the histogram bucket lower bound: %s", strings.Join(statTokens[1:3], "."))) @@ -183,6 +184,7 @@ func init() { Server: "", ThreadAsTag: false, ConfigFile: "", + Histogram: false, } }) } diff --git a/plugins/inputs/unbound/unbound_test.go b/plugins/inputs/unbound/unbound_test.go index f9cfc8c84a390..1fde702014a98 100644 --- a/plugins/inputs/unbound/unbound_test.go +++ b/plugins/inputs/unbound/unbound_test.go @@ -27,11 +27,29 @@ func TestParseFullOutput(t *testing.T) { require.True(t, acc.HasMeasurement("unbound")) require.Len(t, acc.Metrics, 1) - require.Equal(t, 103, acc.NFields()) + require.Equal(t, 63, acc.NFields()) acc.AssertContainsFields(t, "unbound", parsedFullOutput) } +func TestParseFullOutputHistogram(t *testing.T) { + acc := &testutil.Accumulator{} + v := &Unbound{ + run: unboundControl(fullOutput), + Histogram: true, + } + err := v.Gather(acc) + + require.NoError(t, err) + + require.True(t, acc.HasMeasurement("unbound")) + + require.Len(t, acc.Metrics, 1) + require.Equal(t, 103, acc.NFields()) + + acc.AssertContainsFields(t, "unbound", parsedFullOutputHistogram) +} + func TestParseFullOutputThreadAsTag(t *testing.T) { acc := &testutil.Accumulator{} v := &Unbound{ @@ -46,13 +64,102 @@ func TestParseFullOutputThreadAsTag(t *testing.T) { require.True(t, acc.HasMeasurement("unbound_threads")) require.Len(t, acc.Metrics, 2) - require.Equal(t, 103, acc.NFields()) + require.Equal(t, 63, acc.NFields()) acc.AssertContainsFields(t, "unbound", parsedFullOutputThreadAsTagMeasurementUnbound) acc.AssertContainsFields(t, "unbound_threads", parsedFullOutputThreadAsTagMeasurementUnboundThreads) } +func TestParseFullOutputThreadAsTagHistogram(t *testing.T) { + acc := &testutil.Accumulator{} + v := &Unbound{ + run: unboundControl(fullOutput), + ThreadAsTag: true, + Histogram: true, + } + err := v.Gather(acc) + + require.NoError(t, err) + + require.True(t, acc.HasMeasurement("unbound")) + require.True(t, acc.HasMeasurement("unbound_threads")) + + require.Len(t, acc.Metrics, 2) + require.Equal(t, 103, acc.NFields()) + + acc.AssertContainsFields(t, "unbound", parsedFullOutputThreadAsTagHistogramMeasurementUnbound) + acc.AssertContainsFields(t, "unbound_threads", parsedFullOutputThreadAsTagMeasurementUnboundThreads) +} + + + var parsedFullOutput = map[string]interface{}{ + "thread0_num_queries": float64(11907596), + "thread0_num_cachehits": float64(11489288), + "thread0_num_cachemiss": float64(418308), + "thread0_num_prefetch": float64(0), + "thread0_num_recursivereplies": float64(418308), + "thread0_requestlist_avg": float64(0.400229), + "thread0_requestlist_max": float64(11), + "thread0_requestlist_overwritten": float64(0), + "thread0_requestlist_exceeded": float64(0), + "thread0_requestlist_current_all": float64(0), + "thread0_requestlist_current_user": float64(0), + "thread0_recursion_time_avg": float64(0.015020), + "thread0_recursion_time_median": float64(0.00292343), + "total_num_queries": float64(11907596), + "total_num_cachehits": float64(11489288), + "total_num_cachemiss": float64(418308), + "total_num_prefetch": float64(0), + "total_num_recursivereplies": float64(418308), + "total_requestlist_avg": float64(0.400229), + "total_requestlist_max": float64(11), + "total_requestlist_overwritten": float64(0), + "total_requestlist_exceeded": float64(0), + "total_requestlist_current_all": float64(0), + "total_requestlist_current_user": float64(0), + "total_recursion_time_avg": float64(0.015020), + "total_recursion_time_median": float64(0.00292343), + "time_now": float64(1509968734.735180), + "time_up": float64(1472897.672099), + "time_elapsed": float64(1472897.672099), + "mem_total_sbrk": float64(7462912), + "mem_cache_rrset": float64(285056), + "mem_cache_message": float64(320000), + "mem_mod_iterator": float64(16532), + "mem_mod_validator": float64(112097), + "num_query_type_A": float64(7062688), + "num_query_type_PTR": float64(43097), + "num_query_type_TXT": float64(2998), + "num_query_type_AAAA": float64(4499711), + "num_query_type_SRV": float64(5691), + "num_query_type_ANY": float64(293411), + "num_query_class_IN": float64(11907596), + "num_query_opcode_QUERY": float64(11907596), + "num_query_tcp": float64(293411), + "num_query_ipv6": float64(0), + "num_query_flags_QR": float64(0), + "num_query_flags_AA": float64(0), + "num_query_flags_TC": float64(0), + "num_query_flags_RD": float64(11907596), + "num_query_flags_RA": float64(0), + "num_query_flags_Z": float64(0), + "num_query_flags_AD": float64(1), + "num_query_flags_CD": float64(0), + "num_query_edns_present": float64(6202), + "num_query_edns_DO": float64(6201), + "num_answer_rcode_NOERROR": float64(11857463), + "num_answer_rcode_SERVFAIL": float64(17), + "num_answer_rcode_NXDOMAIN": float64(50116), + "num_answer_rcode_nodata": float64(3914360), + "num_answer_secure": float64(44289), + "num_answer_bogus": float64(1), + "num_rrset_bogus": float64(0), + "unwanted_queries": float64(0), + "unwanted_replies": float64(0), +} + +var parsedFullOutputHistogram = map[string]interface{}{ "thread0_num_queries": float64(11907596), "thread0_num_cachehits": float64(11489288), "thread0_num_cachemiss": float64(418308), @@ -174,7 +281,61 @@ var parsedFullOutputThreadAsTagMeasurementUnboundThreads = map[string]interface{ "recursion_time_median": float64(0.00292343), } + var parsedFullOutputThreadAsTagMeasurementUnbound = map[string]interface{}{ + "total_num_queries": float64(11907596), + "total_num_cachehits": float64(11489288), + "total_num_cachemiss": float64(418308), + "total_num_prefetch": float64(0), + "total_num_recursivereplies": float64(418308), + "total_requestlist_avg": float64(0.400229), + "total_requestlist_max": float64(11), + "total_requestlist_overwritten": float64(0), + "total_requestlist_exceeded": float64(0), + "total_requestlist_current_all": float64(0), + "total_requestlist_current_user": float64(0), + "total_recursion_time_avg": float64(0.015020), + "total_recursion_time_median": float64(0.00292343), + "time_now": float64(1509968734.735180), + "time_up": float64(1472897.672099), + "time_elapsed": float64(1472897.672099), + "mem_total_sbrk": float64(7462912), + "mem_cache_rrset": float64(285056), + "mem_cache_message": float64(320000), + "mem_mod_iterator": float64(16532), + "mem_mod_validator": float64(112097), + "num_query_type_A": float64(7062688), + "num_query_type_PTR": float64(43097), + "num_query_type_TXT": float64(2998), + "num_query_type_AAAA": float64(4499711), + "num_query_type_SRV": float64(5691), + "num_query_type_ANY": float64(293411), + "num_query_class_IN": float64(11907596), + "num_query_opcode_QUERY": float64(11907596), + "num_query_tcp": float64(293411), + "num_query_ipv6": float64(0), + "num_query_flags_QR": float64(0), + "num_query_flags_AA": float64(0), + "num_query_flags_TC": float64(0), + "num_query_flags_RD": float64(11907596), + "num_query_flags_RA": float64(0), + "num_query_flags_Z": float64(0), + "num_query_flags_AD": float64(1), + "num_query_flags_CD": float64(0), + "num_query_edns_present": float64(6202), + "num_query_edns_DO": float64(6201), + "num_answer_rcode_NOERROR": float64(11857463), + "num_answer_rcode_SERVFAIL": float64(17), + "num_answer_rcode_NXDOMAIN": float64(50116), + "num_answer_rcode_nodata": float64(3914360), + "num_answer_secure": float64(44289), + "num_answer_bogus": float64(1), + "num_rrset_bogus": float64(0), + "unwanted_queries": float64(0), + "unwanted_replies": float64(0), +} + +var parsedFullOutputThreadAsTagHistogramMeasurementUnbound = map[string]interface{}{ "total_num_queries": float64(11907596), "total_num_cachehits": float64(11489288), "total_num_cachemiss": float64(418308), From 9abb265a77db6f73786ae0b364d72858533636de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Francisco=20Loff?= Date: Mon, 3 Feb 2025 09:20:43 +0000 Subject: [PATCH 3/4] feat(inputs.unbound): document the option --- plugins/inputs/unbound/README.md | 59 +++++++++++++++++++++++++++--- plugins/inputs/unbound/sample.conf | 3 ++ 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/plugins/inputs/unbound/README.md b/plugins/inputs/unbound/README.md index 726cec7538939..7dbab573cd994 100644 --- a/plugins/inputs/unbound/README.md +++ b/plugins/inputs/unbound/README.md @@ -39,6 +39,9 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details. ## true in a future version. It is recommended to set to true on new ## deployments. thread_as_tag = false + + ## Collect metrics with the histogram of the recursive query times: + histogram = false ``` ### Permissions @@ -83,11 +86,11 @@ Please use the solution you see as most appropriate. ## Metrics This is the full list of stats provided by unbound-control and potentially -collected depending of your unbound configuration. Histogram related statistics -will never be collected, extended statistics can also be imported -("extended-statistics: yes" in unbound configuration). In the output, the dots -in the unbound-control stat name are replaced by underscores(see - for details). +collected depending of your unbound configuration. Extended statistics can also +be imported ("extended-statistics: yes" in unbound configuration). In the +output, the dots in the unbound-control stat name are replaced by +underscores(see for +details). Shown metrics are with `thread_as_tag` enabled. @@ -162,6 +165,52 @@ Shown metrics are with `thread_as_tag` enabled. - recursion_time_avg - recursion_time_median +If `histogram` is set to true, the following metrics are also collected, with +the field name indicating the lower bound of each histogram bin: + +- unbound: + - fields: + histogram_0.000000 + histogram_0.000001 + histogram_0.000002 + histogram_0.000004 + histogram_0.000008 + histogram_0.000016 + histogram_0.000032 + histogram_0.000064 + histogram_0.000128 + histogram_0.000256 + histogram_0.000512 + histogram_0.001024 + histogram_0.002048 + histogram_0.004096 + histogram_0.008192 + histogram_0.016384 + histogram_0.032768 + histogram_0.065536 + histogram_0.131072 + histogram_0.262144 + histogram_0.524288 + histogram_1.000000 + histogram_2.000000 + histogram_4.000000 + histogram_8.000000 + histogram_16.000000 + histogram_32.000000 + histogram_64.000000 + histogram_128.000000 + histogram_256.000000 + histogram_512.000000 + histogram_1024.000000 + histogram_2048.000000 + histogram_4096.000000 + histogram_8192.000000 + histogram_16384.000000 + histogram_32768.000000 + histogram_65536.000000 + histogram_131072.000000 + histogram_262144.000000 + ## Example Output ```text diff --git a/plugins/inputs/unbound/sample.conf b/plugins/inputs/unbound/sample.conf index 095da9ddb4efb..a441e14052177 100644 --- a/plugins/inputs/unbound/sample.conf +++ b/plugins/inputs/unbound/sample.conf @@ -22,3 +22,6 @@ ## true in a future version. It is recommended to set to true on new ## deployments. thread_as_tag = false + + ## Collect metrics with the histogram of the recursive query times: + histogram = false From a73d630a7b3db92c291e07a0d8a51d85bf0e0517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Francisco=20Loff?= Date: Mon, 3 Feb 2025 09:37:16 +0000 Subject: [PATCH 4/4] feat(input.unbound): drop unneeded blank lines --- plugins/inputs/unbound/unbound_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/inputs/unbound/unbound_test.go b/plugins/inputs/unbound/unbound_test.go index 1fde702014a98..0c585c2911844 100644 --- a/plugins/inputs/unbound/unbound_test.go +++ b/plugins/inputs/unbound/unbound_test.go @@ -91,8 +91,6 @@ func TestParseFullOutputThreadAsTagHistogram(t *testing.T) { acc.AssertContainsFields(t, "unbound_threads", parsedFullOutputThreadAsTagMeasurementUnboundThreads) } - - var parsedFullOutput = map[string]interface{}{ "thread0_num_queries": float64(11907596), "thread0_num_cachehits": float64(11489288), @@ -281,7 +279,6 @@ var parsedFullOutputThreadAsTagMeasurementUnboundThreads = map[string]interface{ "recursion_time_median": float64(0.00292343), } - var parsedFullOutputThreadAsTagMeasurementUnbound = map[string]interface{}{ "total_num_queries": float64(11907596), "total_num_cachehits": float64(11489288),