Support floats in statsd percentiles (#5572)

This commit is contained in:
Pitxyoki 2019-07-10 00:50:20 +01:00 committed by Daniel Nelson
parent 70e2ccce75
commit 72c2ac9648
6 changed files with 45 additions and 15 deletions

View File

@ -48,6 +48,10 @@ type Size struct {
Size int64 Size int64
} }
type Number struct {
Value float64
}
// SetVersion sets the telegraf agent version // SetVersion sets the telegraf agent version
func SetVersion(v string) error { func SetVersion(v string) error {
if version != "" { if version != "" {
@ -124,6 +128,16 @@ func (s *Size) UnmarshalTOML(b []byte) error {
return nil return nil
} }
func (n *Number) UnmarshalTOML(b []byte) error {
value, err := strconv.ParseFloat(string(b), 64)
if err != nil {
return err
}
n.Value = value
return nil
}
// ReadLines reads contents from a file and splits them by new lines. // ReadLines reads contents from a file and splits them by new lines.
// A convenience wrapper to ReadLinesOffsetN(filename, 0, -1). // A convenience wrapper to ReadLinesOffsetN(filename, 0, -1).
func ReadLines(filename string) ([]string, error) { func ReadLines(filename string) ([]string, error) {

View File

@ -34,8 +34,8 @@
## Reset timings & histograms every interval (default=true) ## Reset timings & histograms every interval (default=true)
delete_timings = true delete_timings = true
## Percentiles to calculate for timing & histogram stats ## Percentiles to calculate for timing & histogram stats.
percentiles = [90] percentiles = [50.0, 90.0, 99.0, 99.9, 99.95, 100.0]
## separator to use between elements of a statsd metric ## separator to use between elements of a statsd metric
metric_separator = "_" metric_separator = "_"

View File

@ -99,7 +99,7 @@ func (rs *RunningStats) Count() int64 {
return rs.n return rs.n
} }
func (rs *RunningStats) Percentile(n int) float64 { func (rs *RunningStats) Percentile(n float64) float64 {
if n > 100 { if n > 100 {
n = 100 n = 100
} }
@ -109,16 +109,16 @@ func (rs *RunningStats) Percentile(n int) float64 {
rs.sorted = true rs.sorted = true
} }
i := int(float64(len(rs.perc)) * float64(n) / float64(100)) i := float64(len(rs.perc)) * n / float64(100)
return rs.perc[clamp(i, 0, len(rs.perc)-1)] return rs.perc[clamp(i, 0, len(rs.perc)-1)]
} }
func clamp(i int, min int, max int) int { func clamp(i float64, min int, max int) int {
if i < min { if i < float64(min) {
return min return min
} }
if i > max { if i > float64(max) {
return max return max
} }
return i return int(i)
} }

View File

@ -26,6 +26,9 @@ func TestRunningStats_Single(t *testing.T) {
if rs.Percentile(100) != 10.1 { if rs.Percentile(100) != 10.1 {
t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(100)) t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(100))
} }
if rs.Percentile(99.95) != 10.1 {
t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(99.95))
}
if rs.Percentile(90) != 10.1 { if rs.Percentile(90) != 10.1 {
t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(90)) t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(90))
} }
@ -67,6 +70,9 @@ func TestRunningStats_Duplicate(t *testing.T) {
if rs.Percentile(100) != 10.1 { if rs.Percentile(100) != 10.1 {
t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(100)) t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(100))
} }
if rs.Percentile(99.95) != 10.1 {
t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(99.95))
}
if rs.Percentile(90) != 10.1 { if rs.Percentile(90) != 10.1 {
t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(90)) t.Errorf("Expected %v, got %v", 10.1, rs.Percentile(90))
} }
@ -108,12 +114,21 @@ func TestRunningStats(t *testing.T) {
if rs.Percentile(100) != 45 { if rs.Percentile(100) != 45 {
t.Errorf("Expected %v, got %v", 45, rs.Percentile(100)) t.Errorf("Expected %v, got %v", 45, rs.Percentile(100))
} }
if rs.Percentile(99.98) != 45 {
t.Errorf("Expected %v, got %v", 45, rs.Percentile(99.98))
}
if rs.Percentile(90) != 32 { if rs.Percentile(90) != 32 {
t.Errorf("Expected %v, got %v", 32, rs.Percentile(90)) t.Errorf("Expected %v, got %v", 32, rs.Percentile(90))
} }
if rs.Percentile(50.1) != 11 {
t.Errorf("Expected %v, got %v", 11, rs.Percentile(50.1))
}
if rs.Percentile(50) != 11 { if rs.Percentile(50) != 11 {
t.Errorf("Expected %v, got %v", 11, rs.Percentile(50)) t.Errorf("Expected %v, got %v", 11, rs.Percentile(50))
} }
if rs.Percentile(49.9) != 10 {
t.Errorf("Expected %v, got %v", 10, rs.Percentile(49.9))
}
if rs.Percentile(0) != 5 { if rs.Percentile(0) != 5 {
t.Errorf("Expected %v, got %v", 5, rs.Percentile(0)) t.Errorf("Expected %v, got %v", 5, rs.Percentile(0))
} }

View File

@ -55,7 +55,7 @@ type Statsd struct {
// Percentiles specifies the percentiles that will be calculated for timing // Percentiles specifies the percentiles that will be calculated for timing
// and histogram stats. // and histogram stats.
Percentiles []int Percentiles []internal.Number
PercentileLimit int PercentileLimit int
DeleteGauges bool DeleteGauges bool
@ -217,7 +217,7 @@ const sampleConfig = `
delete_timings = true delete_timings = true
## Percentiles to calculate for timing & histogram stats ## Percentiles to calculate for timing & histogram stats
percentiles = [90] percentiles = [50.0, 90.0, 99.0, 99.9, 99.95, 100.0]
## separator to use between elements of a statsd metric ## separator to use between elements of a statsd metric
metric_separator = "_" metric_separator = "_"
@ -271,8 +271,8 @@ func (s *Statsd) Gather(acc telegraf.Accumulator) error {
fields[prefix+"lower"] = stats.Lower() fields[prefix+"lower"] = stats.Lower()
fields[prefix+"count"] = stats.Count() fields[prefix+"count"] = stats.Count()
for _, percentile := range s.Percentiles { for _, percentile := range s.Percentiles {
name := fmt.Sprintf("%s%v_percentile", prefix, percentile) name := fmt.Sprintf("%s%v_percentile", prefix, percentile.Value)
fields[name] = stats.Percentile(percentile) fields[name] = stats.Percentile(percentile.Value)
} }
} }

View File

@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/testutil" "github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -397,7 +398,7 @@ func TestParse_Counters(t *testing.T) {
// Tests low-level functionality of timings // Tests low-level functionality of timings
func TestParse_Timings(t *testing.T) { func TestParse_Timings(t *testing.T) {
s := NewTestStatsd() s := NewTestStatsd()
s.Percentiles = []int{90} s.Percentiles = []internal.Number{{Value: 90.0}}
acc := &testutil.Accumulator{} acc := &testutil.Accumulator{}
// Test that counters work // Test that counters work
@ -1181,7 +1182,7 @@ func TestParse_MeasurementsWithMultipleValues(t *testing.T) {
func TestParse_TimingsMultipleFieldsWithTemplate(t *testing.T) { func TestParse_TimingsMultipleFieldsWithTemplate(t *testing.T) {
s := NewTestStatsd() s := NewTestStatsd()
s.Templates = []string{"measurement.field"} s.Templates = []string{"measurement.field"}
s.Percentiles = []int{90} s.Percentiles = []internal.Number{{Value: 90.0}}
acc := &testutil.Accumulator{} acc := &testutil.Accumulator{}
validLines := []string{ validLines := []string{
@ -1232,7 +1233,7 @@ func TestParse_TimingsMultipleFieldsWithTemplate(t *testing.T) {
func TestParse_TimingsMultipleFieldsWithoutTemplate(t *testing.T) { func TestParse_TimingsMultipleFieldsWithoutTemplate(t *testing.T) {
s := NewTestStatsd() s := NewTestStatsd()
s.Templates = []string{} s.Templates = []string{}
s.Percentiles = []int{90} s.Percentiles = []internal.Number{{Value: 90.0}}
acc := &testutil.Accumulator{} acc := &testutil.Accumulator{}
validLines := []string{ validLines := []string{