feat(inputs.unbound): Collect histogram statistics (#16452)

Co-authored-by: Sven Rebhan <36194019+srebhan@users.noreply.github.com>
Co-authored-by: Sven Rebhan <srebhan@influxdata.com>
This commit is contained in:
Zé Loff 2025-03-18 17:47:32 +00:00 committed by GitHub
parent 1406f41ce8
commit 129ee12871
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 305 additions and 18 deletions

View File

@ -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 ## true in a future version. It is recommended to set to true on new
## deployments. ## deployments.
thread_as_tag = false thread_as_tag = false
## Collect metrics with the histogram of the recursive query times:
# histogram = false
``` ```
### Permissions ### Permissions
@ -83,11 +86,11 @@ Please use the solution you see as most appropriate.
## Metrics ## Metrics
This is the full list of stats provided by unbound-control and potentially This is the full list of stats provided by unbound-control and potentially
collected depending of your unbound configuration. Histogram related statistics collected depending of your unbound configuration. Extended statistics can also
will never be collected, extended statistics can also be imported be imported ("extended-statistics: yes" in unbound configuration). In the
("extended-statistics: yes" in unbound configuration). In the output, the dots output, the dots in the unbound-control stat name are replaced by
in the unbound-control stat name are replaced by underscores(see underscores(see <https://www.unbound.net/documentation/unbound-control.html> for
<https://www.unbound.net/documentation/unbound-control.html> for details). details).
Shown metrics are with `thread_as_tag` enabled. 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_avg
- recursion_time_median - 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_.000000
histogram_.000001
histogram_.000002
histogram_.000004
histogram_.000008
histogram_.000016
histogram_.000032
histogram_.000064
histogram_.000128
histogram_.000256
histogram_.000512
histogram_.001024
histogram_.002048
histogram_.004096
histogram_.008192
histogram_.016384
histogram_.032768
histogram_.065536
histogram_.131072
histogram_.262144
histogram_.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 ## Example Output
```text ```text

View File

@ -22,3 +22,6 @@
## true in a future version. It is recommended to set to true on new ## true in a future version. It is recommended to set to true on new
## deployments. ## deployments.
thread_as_tag = false thread_as_tag = false
## Collect metrics with the histogram of the recursive query times:
# histogram = false

View File

@ -15,7 +15,6 @@ import (
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config" "github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/filter"
"github.com/influxdata/telegraf/internal" "github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs" "github.com/influxdata/telegraf/plugins/inputs"
) )
@ -35,6 +34,7 @@ type Unbound struct {
Server string `toml:"server"` Server string `toml:"server"`
ThreadAsTag bool `toml:"thread_as_tag"` ThreadAsTag bool `toml:"thread_as_tag"`
ConfigFile string `toml:"config_file"` ConfigFile string `toml:"config_file"`
Histogram bool `toml:"histogram"`
run runner run runner
} }
@ -47,13 +47,6 @@ func (*Unbound) SampleConfig() string {
// All the dots in stat name will replaced by underscores. Histogram statistics will not be collected. // All the dots in stat name will replaced by underscores. Histogram statistics will not be collected.
func (s *Unbound) Gather(acc telegraf.Accumulator) error { 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) out, err := s.run(*s)
if err != nil { if err != nil {
return fmt.Errorf("error gathering metrics: %w", err) return fmt.Errorf("error gathering metrics: %w", err)
@ -75,11 +68,6 @@ func (s *Unbound) Gather(acc telegraf.Accumulator) error {
stat := cols[0] stat := cols[0]
value := cols[1] value := cols[1]
// Filter value
if filterExcluded.Match(stat) {
continue
}
fieldValue, err := strconv.ParseFloat(value, 64) fieldValue, err := strconv.ParseFloat(value, 64)
if err != nil { if err != nil {
acc.AddError(fmt.Errorf("expected a numerical value for %s = %v", stat, value)) acc.AddError(fmt.Errorf("expected a numerical value for %s = %v", stat, value))
@ -106,6 +94,15 @@ func (s *Unbound) Gather(acc telegraf.Accumulator) error {
fieldsThreads[threadID][field] = fieldValue fieldsThreads[threadID][field] = fieldValue
} }
} }
} else if suffix, found := strings.CutPrefix(stat, "histogram."); found {
if s.Histogram {
suffix, _, _ := strings.Cut(suffix, ".to.")
suffix = strings.TrimLeft(suffix, "0")
if strings.HasPrefix(suffix, ".") {
suffix = "0" + suffix
}
fields["histogram_"+suffix] = fieldValue
}
} else { } else {
field := strings.ReplaceAll(stat, ".", "_") field := strings.ReplaceAll(stat, ".", "_")
fields[field] = fieldValue fields[field] = fieldValue
@ -185,6 +182,7 @@ func init() {
Server: "", Server: "",
ThreadAsTag: false, ThreadAsTag: false,
ConfigFile: "", ConfigFile: "",
Histogram: false,
} }
}) })
} }

View File

@ -32,6 +32,24 @@ func TestParseFullOutput(t *testing.T) {
acc.AssertContainsFields(t, "unbound", parsedFullOutput) 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) { func TestParseFullOutputThreadAsTag(t *testing.T) {
acc := &testutil.Accumulator{} acc := &testutil.Accumulator{}
v := &Unbound{ v := &Unbound{
@ -52,6 +70,26 @@ func TestParseFullOutputThreadAsTag(t *testing.T) {
acc.AssertContainsFields(t, "unbound_threads", parsedFullOutputThreadAsTagMeasurementUnboundThreads) acc.AssertContainsFields(t, "unbound_threads", parsedFullOutputThreadAsTagMeasurementUnboundThreads)
} }
func TestParseFullOutputThreadAsTagHistogram(t *testing.T) {
acc := &testutil.Accumulator{}
v := &Unbound{
run: unboundControl(fullOutput),
ThreadAsTag: true,
Histogram: true,
}
require.NoError(t, v.Gather(acc))
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{}{ var parsedFullOutput = map[string]interface{}{
"thread0_num_queries": float64(11907596), "thread0_num_queries": float64(11907596),
"thread0_num_cachehits": float64(11489288), "thread0_num_cachehits": float64(11489288),
@ -118,6 +156,112 @@ var parsedFullOutput = map[string]interface{}{
"unwanted_replies": 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),
"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),
"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),
"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 parsedFullOutputThreadAsTagMeasurementUnboundThreads = map[string]interface{}{ var parsedFullOutputThreadAsTagMeasurementUnboundThreads = map[string]interface{}{
"num_queries": float64(11907596), "num_queries": float64(11907596),
"num_cachehits": float64(11489288), "num_cachehits": float64(11489288),
@ -187,6 +331,99 @@ var parsedFullOutputThreadAsTagMeasurementUnbound = map[string]interface{}{
"unwanted_replies": 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),
"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),
"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),
"num_query_type_AAAA": float64(4499711),
"num_query_type_SRV": float64(5691),
"num_query_type_ANY": float64(293411),
"num_query_class_IN": float64(11907596),
"num_query_opcode_QUERY": float64(11907596),
"num_query_tcp": float64(293411),
"num_query_ipv6": float64(0),
"num_query_flags_QR": float64(0),
"num_query_flags_AA": float64(0),
"num_query_flags_TC": float64(0),
"num_query_flags_RD": float64(11907596),
"num_query_flags_RA": float64(0),
"num_query_flags_Z": float64(0),
"num_query_flags_AD": float64(1),
"num_query_flags_CD": float64(0),
"num_query_edns_present": float64(6202),
"num_query_edns_DO": float64(6201),
"num_answer_rcode_NOERROR": float64(11857463),
"num_answer_rcode_SERVFAIL": float64(17),
"num_answer_rcode_NXDOMAIN": float64(50116),
"num_answer_rcode_nodata": float64(3914360),
"num_answer_secure": float64(44289),
"num_answer_bogus": float64(1),
"num_rrset_bogus": float64(0),
"unwanted_queries": float64(0),
"unwanted_replies": float64(0),
}
var fullOutput = `thread0.num.queries=11907596 var fullOutput = `thread0.num.queries=11907596
thread0.num.cachehits=11489288 thread0.num.cachehits=11489288
thread0.num.cachemiss=418308 thread0.num.cachemiss=418308