From e3989a265d3b012c077555fc52adc5c37db5f8ef Mon Sep 17 00:00:00 2001 From: Josh Powers Date: Thu, 16 May 2024 13:11:23 -0600 Subject: [PATCH 1/2] feat(inputs.statsd): Allow counters to report as float By default counters are stored as ints. However, if a user is using a value as both a float or a int it can lead to type conflicts. This maintains the interal representation as an int to avoid messing with the caching code, but allows the counter to be reported as a float when a metric is reported. fixes: #1978 --- plugins/inputs/statsd/sample.conf | 5 ++ plugins/inputs/statsd/statsd.go | 6 ++ plugins/inputs/statsd/statsd_test.go | 109 +++++++++++++++++++++++++++ 3 files changed, 120 insertions(+) diff --git a/plugins/inputs/statsd/sample.conf b/plugins/inputs/statsd/sample.conf index 58d39215ed5ae..7c63699b904e6 100644 --- a/plugins/inputs/statsd/sample.conf +++ b/plugins/inputs/statsd/sample.conf @@ -90,3 +90,8 @@ ## Replace dots (.) with underscore (_) and dashes (-) with ## double underscore (__) in metric names. # convert_names = false + + ## Convert all numeric counters to float + ## Enabling this would ensure that both counters and guages are both emitted + ## as floats. + # float_counters = false diff --git a/plugins/inputs/statsd/statsd.go b/plugins/inputs/statsd/statsd.go index 2184fe90d35a6..6579f0d3200a4 100644 --- a/plugins/inputs/statsd/statsd.go +++ b/plugins/inputs/statsd/statsd.go @@ -76,6 +76,7 @@ type Statsd struct { DeleteSets bool `toml:"delete_sets"` DeleteTimings bool `toml:"delete_timings"` ConvertNames bool `toml:"convert_names"` + FloatCounters bool `toml:"float_counters"` EnableAggregationTemporality bool `toml:"enable_aggregation_temporality"` @@ -285,6 +286,11 @@ func (s *Statsd) Gather(acc telegraf.Accumulator) error { m.fields["start_time"] = s.lastGatherTime.Format(time.RFC3339) } + if s.FloatCounters { + for key := range m.fields { + m.fields[key] = float64(m.fields[key].(int64)) + } + } acc.AddCounter(m.name, m.fields, m.tags, now) } if s.DeleteCounters { diff --git a/plugins/inputs/statsd/statsd_test.go b/plugins/inputs/statsd/statsd_test.go index 7fe6f892e012d..c8af8fa71712f 100644 --- a/plugins/inputs/statsd/statsd_test.go +++ b/plugins/inputs/statsd/statsd_test.go @@ -532,6 +532,115 @@ func TestParse_Counters(t *testing.T) { } } +func TestParse_CountersAsFloat(t *testing.T) { + s := NewTestStatsd() + s.FloatCounters = true + + // Test that counters work + validLines := []string{ + "small.inc:1|c", + "big.inc:100|c", + "big.inc:1|c", + "big.inc:100000|c", + "big.inc:1000000|c", + "small.inc:1|c", + "zero.init:0|c", + "sample.rate:1|c|@0.1", + "sample.rate:1|c", + "scientific.notation:4.696E+5|c", + "negative.test:100|c", + "negative.test:-5|c", + } + + for _, line := range validLines { + require.NoErrorf(t, s.parseStatsdLine(line), "Parsing line %s should not have resulted in an error", line) + } + + validations := []struct { + name string + value int64 + }{ + { + "scientific_notation", + 469600, + }, + { + "small_inc", + 2, + }, + { + "big_inc", + 1100101, + }, + { + "zero_init", + 0, + }, + { + "sample_rate", + 11, + }, + { + "negative_test", + 95, + }, + } + for _, test := range validations { + require.NoError(t, testValidateCounter(test.name, test.value, s.counters)) + } + + expected := []telegraf.Metric{ + testutil.MustMetric( + "small_inc", + map[string]string{"metric_type": "counter"}, + map[string]interface{}{"value": 2.0}, + time.Now(), + telegraf.Counter, + ), + testutil.MustMetric( + "big_inc", + map[string]string{"metric_type": "counter"}, + map[string]interface{}{"value": 1100101.0}, + time.Now(), + telegraf.Counter, + ), + testutil.MustMetric( + "zero_init", + map[string]string{"metric_type": "counter"}, + map[string]interface{}{"value": 0.0}, + time.Now(), + telegraf.Counter, + ), + testutil.MustMetric( + "sample_rate", + map[string]string{"metric_type": "counter"}, + map[string]interface{}{"value": 11.0}, + time.Now(), + telegraf.Counter, + ), + testutil.MustMetric( + "scientific_notation", + map[string]string{"metric_type": "counter"}, + map[string]interface{}{"value": 469600.0}, + time.Now(), + telegraf.Counter, + ), + testutil.MustMetric( + "negative_test", + map[string]string{"metric_type": "counter"}, + map[string]interface{}{"value": 95.0}, + time.Now(), + telegraf.Counter, + ), + } + + acc := &testutil.Accumulator{} + require.NoError(t, s.Gather(acc)) + metrics := acc.GetTelegrafMetrics() + testutil.PrintMetrics(metrics) + testutil.RequireMetricsEqual(t, expected, metrics, testutil.IgnoreTime(), testutil.SortMetrics()) +} + // Tests low-level functionality of timings func TestParse_Timings(t *testing.T) { s := NewTestStatsd() From 01accd7b3bd74e5ed26f7fd3c537bb016634da8c Mon Sep 17 00:00:00 2001 From: Josh Powers Date: Thu, 16 May 2024 13:41:11 -0600 Subject: [PATCH 2/2] make docs --- plugins/inputs/statsd/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/inputs/statsd/README.md b/plugins/inputs/statsd/README.md index f5a9b598d72c6..891c591980894 100644 --- a/plugins/inputs/statsd/README.md +++ b/plugins/inputs/statsd/README.md @@ -117,6 +117,11 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details. ## Replace dots (.) with underscore (_) and dashes (-) with ## double underscore (__) in metric names. # convert_names = false + + ## Convert all numeric counters to float + ## Enabling this would ensure that both counters and guages are both emitted + ## as floats. + # float_counters = false ``` ## Description