feat(aggregators.basicstats): Add "last" field (#15030)

This commit is contained in:
Dane Strandboge 2024-03-21 09:19:39 -05:00 committed by GitHub
parent e6a61f93b6
commit 9f085f6b7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 84 additions and 23 deletions

View File

@ -1,6 +1,6 @@
# BasicStats Aggregator Plugin # BasicStats Aggregator Plugin
The BasicStats aggregator plugin give us count, diff, max, min, mean, The BasicStats aggregator plugin gives count, diff, max, min, mean,
non_negative_diff, sum, s2(variance), stdev for a set of values, emitting the non_negative_diff, sum, s2(variance), stdev for a set of values, emitting the
aggregate every `period` seconds. aggregate every `period` seconds.
@ -26,14 +26,13 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
drop_original = false drop_original = false
## Configures which basic stats to push as fields ## Configures which basic stats to push as fields
# stats = ["count","diff","rate","min","max","mean","non_negative_diff","non_negative_rate","percent_change","stdev","s2","sum","interval"] # stats = ["count","diff","rate","min","max","mean","non_negative_diff","non_negative_rate","percent_change","stdev","s2","sum","interval","last"]
``` ```
- stats - stats
- If not specified, then `count`, `min`, `max`, `mean`, `stdev`, and `s2` are - If not specified, then `count`, `min`, `max`, `mean`, `stdev`, and `s2` are
aggregated and pushed as fields. `sum`, `diff`, `non_negative_diff`, aggregated and pushed as fields. Other fields are not aggregated by default
`percent_change` are not aggregated by default to maintain backwards to maintain backwards compatibility.
compatibility.
- If empty array, no stats are aggregated - If empty array, no stats are aggregated
## Measurements & Fields ## Measurements & Fields
@ -52,6 +51,7 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
- field1_s2 (variance) - field1_s2 (variance)
- field1_stdev (standard deviation) - field1_stdev (standard deviation)
- field1_interval (interval in nanoseconds) - field1_interval (interval in nanoseconds)
- field1_last (last aggregated value)
## Tags ## Tags
@ -62,8 +62,8 @@ No tags are applied by this aggregator.
```text ```text
system,host=tars load1=1 1475583980000000000 system,host=tars load1=1 1475583980000000000
system,host=tars load1=1 1475583990000000000 system,host=tars load1=1 1475583990000000000
system,host=tars load1_count=2,load1_diff=0,load1_rate=0,load1_max=1,load1_min=1,load1_mean=1,load1_sum=2,load1_s2=0,load1_stdev=0,load1_interval=10000000000i 1475584010000000000 system,host=tars load1_count=2,load1_diff=0,load1_rate=0,load1_max=1,load1_min=1,load1_mean=1,load1_sum=2,load1_s2=0,load1_stdev=0,load1_interval=10000000000i,load1_last=1 1475584010000000000
system,host=tars load1=1 1475584020000000000 system,host=tars load1=1 1475584020000000000
system,host=tars load1=3 1475584030000000000 system,host=tars load1=3 1475584030000000000
system,host=tars load1_count=2,load1_diff=2,load1_rate=0.2,load1_max=3,load1_min=1,load1_mean=2,load1_sum=4,load1_s2=2,load1_stdev=1.414162,load1_interval=10000000000i 1475584010000000000 system,host=tars load1_count=2,load1_diff=2,load1_rate=0.2,load1_max=3,load1_min=1,load1_mean=2,load1_sum=4,load1_s2=2,load1_stdev=1.414162,load1_interval=10000000000i,load1_last=3 1475584010000000000
``` ```

View File

@ -35,6 +35,7 @@ type configuredStats struct {
nonNegativeRate bool nonNegativeRate bool
percentChange bool percentChange bool
interval bool interval bool
last bool
} }
func NewBasicStats() *BasicStats { func NewBasicStats() *BasicStats {
@ -58,8 +59,9 @@ type basicstats struct {
diff float64 diff float64
rate float64 rate float64
interval time.Duration interval time.Duration
last float64
M2 float64 //intermediate value for variance/stdev M2 float64 //intermediate value for variance/stdev
LAST float64 //intermediate value for diff PREVIOUS float64 //intermediate value for diff
TIME time.Time //intermediate value for rate TIME time.Time //intermediate value for rate
} }
@ -79,16 +81,17 @@ func (b *BasicStats) Add(in telegraf.Metric) {
for _, field := range in.FieldList() { for _, field := range in.FieldList() {
if fv, ok := convert(field.Value); ok { if fv, ok := convert(field.Value); ok {
a.fields[field.Key] = basicstats{ a.fields[field.Key] = basicstats{
count: 1, count: 1,
min: fv, min: fv,
max: fv, max: fv,
mean: fv, mean: fv,
sum: fv, sum: fv,
diff: 0.0, diff: 0.0,
rate: 0.0, rate: 0.0,
M2: 0.0, last: fv,
LAST: fv, M2: 0.0,
TIME: in.Time(), PREVIOUS: fv,
TIME: in.Time(),
} }
} }
} }
@ -107,8 +110,9 @@ func (b *BasicStats) Add(in telegraf.Metric) {
diff: 0.0, diff: 0.0,
rate: 0.0, rate: 0.0,
interval: 0, interval: 0,
last: fv,
M2: 0.0, M2: 0.0,
LAST: fv, PREVIOUS: fv,
TIME: in.Time(), TIME: in.Time(),
} }
continue continue
@ -139,13 +143,15 @@ func (b *BasicStats) Add(in telegraf.Metric) {
//sum compute //sum compute
tmp.sum += fv tmp.sum += fv
//diff compute //diff compute
tmp.diff = fv - tmp.LAST tmp.diff = fv - tmp.PREVIOUS
//interval compute //interval compute
tmp.interval = in.Time().Sub(tmp.TIME) tmp.interval = in.Time().Sub(tmp.TIME)
//rate compute //rate compute
if !in.Time().Equal(tmp.TIME) { if !in.Time().Equal(tmp.TIME) {
tmp.rate = tmp.diff / tmp.interval.Seconds() tmp.rate = tmp.diff / tmp.interval.Seconds()
} }
//last compute
tmp.last = fv
//store final data //store final data
b.cache[id].fields[field.Key] = tmp b.cache[id].fields[field.Key] = tmp
} }
@ -172,6 +178,9 @@ func (b *BasicStats) Push(acc telegraf.Accumulator) {
if b.statsConfig.sum { if b.statsConfig.sum {
fields[k+"_sum"] = v.sum fields[k+"_sum"] = v.sum
} }
if b.statsConfig.last {
fields[k+"_last"] = v.last
}
//v.count always >=1 //v.count always >=1
if v.count > 1 { if v.count > 1 {
@ -193,7 +202,7 @@ func (b *BasicStats) Push(acc telegraf.Accumulator) {
fields[k+"_rate"] = v.rate fields[k+"_rate"] = v.rate
} }
if b.statsConfig.percentChange { if b.statsConfig.percentChange {
fields[k+"_percent_change"] = v.diff / v.LAST * 100 fields[k+"_percent_change"] = v.diff / v.PREVIOUS * 100
} }
if b.statsConfig.nonNegativeRate && v.diff >= 0 { if b.statsConfig.nonNegativeRate && v.diff >= 0 {
fields[k+"_non_negative_rate"] = v.rate fields[k+"_non_negative_rate"] = v.rate
@ -243,6 +252,8 @@ func (b *BasicStats) parseStats() *configuredStats {
parsed.percentChange = true parsed.percentChange = true
case "interval": case "interval":
parsed.interval = true parsed.interval = true
case "last":
parsed.last = true
default: default:
b.Log.Warnf("Unrecognized basic stat %q, ignoring", name) b.Log.Warnf("Unrecognized basic stat %q, ignoring", name)
} }
@ -261,10 +272,13 @@ func (b *BasicStats) getConfiguredStats() {
variance: true, variance: true,
stdev: true, stdev: true,
sum: false, sum: false,
diff: false,
nonNegativeDiff: false, nonNegativeDiff: false,
rate: false, rate: false,
nonNegativeRate: false, nonNegativeRate: false,
percentChange: false, percentChange: false,
interval: false,
last: false,
} }
} else { } else {
b.statsConfig = b.parseStats() b.statsConfig = b.parseStats()

View File

@ -111,6 +111,7 @@ func TestBasicStatsWithPeriod(t *testing.T) {
func TestBasicStatsDifferentPeriods(t *testing.T) { func TestBasicStatsDifferentPeriods(t *testing.T) {
acc := testutil.Accumulator{} acc := testutil.Accumulator{}
minmax := NewBasicStats() minmax := NewBasicStats()
minmax.Stats = []string{"count", "max", "min", "mean", "last"}
minmax.Log = testutil.Logger{} minmax.Log = testutil.Logger{}
minmax.getConfiguredStats() minmax.getConfiguredStats()
@ -121,22 +122,27 @@ func TestBasicStatsDifferentPeriods(t *testing.T) {
"a_max": float64(1), "a_max": float64(1),
"a_min": float64(1), "a_min": float64(1),
"a_mean": float64(1), "a_mean": float64(1),
"a_last": float64(1),
"b_count": float64(1), //b "b_count": float64(1), //b
"b_max": float64(1), "b_max": float64(1),
"b_min": float64(1), "b_min": float64(1),
"b_mean": float64(1), "b_mean": float64(1),
"b_last": float64(1),
"c_count": float64(1), //c "c_count": float64(1), //c
"c_max": float64(2), "c_max": float64(2),
"c_min": float64(2), "c_min": float64(2),
"c_mean": float64(2), "c_mean": float64(2),
"c_last": float64(2),
"d_count": float64(1), //d "d_count": float64(1), //d
"d_max": float64(2), "d_max": float64(2),
"d_min": float64(2), "d_min": float64(2),
"d_mean": float64(2), "d_mean": float64(2),
"d_last": float64(2),
"g_count": float64(1), //g "g_count": float64(1), //g
"g_max": float64(3), "g_max": float64(3),
"g_min": float64(3), "g_min": float64(3),
"g_mean": float64(3), "g_mean": float64(3),
"g_last": float64(3),
} }
expectedTags := map[string]string{ expectedTags := map[string]string{
"foo": "bar", "foo": "bar",
@ -152,30 +158,37 @@ func TestBasicStatsDifferentPeriods(t *testing.T) {
"a_max": float64(1), "a_max": float64(1),
"a_min": float64(1), "a_min": float64(1),
"a_mean": float64(1), "a_mean": float64(1),
"a_last": float64(1),
"b_count": float64(1), //b "b_count": float64(1), //b
"b_max": float64(3), "b_max": float64(3),
"b_min": float64(3), "b_min": float64(3),
"b_mean": float64(3), "b_mean": float64(3),
"b_last": float64(3),
"c_count": float64(1), //c "c_count": float64(1), //c
"c_max": float64(4), "c_max": float64(4),
"c_min": float64(4), "c_min": float64(4),
"c_mean": float64(4), "c_mean": float64(4),
"c_last": float64(4),
"d_count": float64(1), //d "d_count": float64(1), //d
"d_max": float64(6), "d_max": float64(6),
"d_min": float64(6), "d_min": float64(6),
"d_mean": float64(6), "d_mean": float64(6),
"d_last": float64(6),
"e_count": float64(1), //e "e_count": float64(1), //e
"e_max": float64(200), "e_max": float64(200),
"e_min": float64(200), "e_min": float64(200),
"e_mean": float64(200), "e_mean": float64(200),
"e_last": float64(200),
"f_count": float64(1), //f "f_count": float64(1), //f
"f_max": float64(200), "f_max": float64(200),
"f_min": float64(200), "f_min": float64(200),
"f_mean": float64(200), "f_mean": float64(200),
"f_last": float64(200),
"g_count": float64(1), //g "g_count": float64(1), //g
"g_max": float64(1), "g_max": float64(1),
"g_min": float64(1), "g_min": float64(1),
"g_mean": float64(1), "g_mean": float64(1),
"g_last": float64(1),
} }
expectedTags = map[string]string{ expectedTags = map[string]string{
"foo": "bar", "foo": "bar",
@ -616,7 +629,7 @@ func TestBasicStatsWithAllStats(t *testing.T) {
acc := testutil.Accumulator{} acc := testutil.Accumulator{}
minmax := NewBasicStats() minmax := NewBasicStats()
minmax.Log = testutil.Logger{} minmax.Log = testutil.Logger{}
minmax.Stats = []string{"count", "min", "max", "mean", "stdev", "s2", "sum"} minmax.Stats = []string{"count", "min", "max", "mean", "stdev", "s2", "sum", "last"}
minmax.getConfiguredStats() minmax.getConfiguredStats()
minmax.Add(m1) minmax.Add(m1)
@ -631,12 +644,14 @@ func TestBasicStatsWithAllStats(t *testing.T) {
"a_stdev": float64(0), "a_stdev": float64(0),
"a_s2": float64(0), "a_s2": float64(0),
"a_sum": float64(2), "a_sum": float64(2),
"a_last": float64(1),
"b_count": float64(2), //b "b_count": float64(2), //b
"b_max": float64(3), "b_max": float64(3),
"b_min": float64(1), "b_min": float64(1),
"b_mean": float64(2), "b_mean": float64(2),
"b_s2": float64(2), "b_s2": float64(2),
"b_sum": float64(4), "b_sum": float64(4),
"b_last": float64(3),
"b_stdev": math.Sqrt(2), "b_stdev": math.Sqrt(2),
"c_count": float64(2), //c "c_count": float64(2), //c
"c_max": float64(4), "c_max": float64(4),
@ -645,6 +660,7 @@ func TestBasicStatsWithAllStats(t *testing.T) {
"c_s2": float64(2), "c_s2": float64(2),
"c_stdev": math.Sqrt(2), "c_stdev": math.Sqrt(2),
"c_sum": float64(6), "c_sum": float64(6),
"c_last": float64(4),
"d_count": float64(2), //d "d_count": float64(2), //d
"d_max": float64(6), "d_max": float64(6),
"d_min": float64(2), "d_min": float64(2),
@ -652,16 +668,19 @@ func TestBasicStatsWithAllStats(t *testing.T) {
"d_s2": float64(8), "d_s2": float64(8),
"d_stdev": math.Sqrt(8), "d_stdev": math.Sqrt(8),
"d_sum": float64(8), "d_sum": float64(8),
"d_last": float64(6),
"e_count": float64(1), //e "e_count": float64(1), //e
"e_max": float64(200), "e_max": float64(200),
"e_min": float64(200), "e_min": float64(200),
"e_mean": float64(200), "e_mean": float64(200),
"e_sum": float64(200), "e_sum": float64(200),
"e_last": float64(200),
"f_count": float64(1), //f "f_count": float64(1), //f
"f_max": float64(200), "f_max": float64(200),
"f_min": float64(200), "f_min": float64(200),
"f_mean": float64(200), "f_mean": float64(200),
"f_sum": float64(200), "f_sum": float64(200),
"f_last": float64(200),
"g_count": float64(2), //g "g_count": float64(2), //g
"g_max": float64(3), "g_max": float64(3),
"g_min": float64(1), "g_min": float64(1),
@ -669,6 +688,7 @@ func TestBasicStatsWithAllStats(t *testing.T) {
"g_s2": float64(2), "g_s2": float64(2),
"g_stdev": math.Sqrt(2), "g_stdev": math.Sqrt(2),
"g_sum": float64(4), "g_sum": float64(4),
"g_last": float64(1),
} }
expectedTags := map[string]string{ expectedTags := map[string]string{
"foo": "bar", "foo": "bar",
@ -731,3 +751,30 @@ func TestBasicStatsWithDefaultStats(t *testing.T) {
require.True(t, acc.HasField("m1", "a_s2")) require.True(t, acc.HasField("m1", "a_s2"))
require.False(t, acc.HasField("m1", "a_sum")) require.False(t, acc.HasField("m1", "a_sum"))
} }
func TestBasicStatsWithOnlyLast(t *testing.T) {
aggregator := NewBasicStats()
aggregator.Stats = []string{"last"}
aggregator.Log = testutil.Logger{}
aggregator.getConfiguredStats()
aggregator.Add(m1)
aggregator.Add(m2)
acc := testutil.Accumulator{}
aggregator.Push(&acc)
expectedFields := map[string]interface{}{
"a_last": float64(1),
"b_last": float64(3),
"c_last": float64(4),
"d_last": float64(6),
"e_last": float64(200),
"f_last": float64(200),
"g_last": float64(1),
}
expectedTags := map[string]string{
"foo": "bar",
}
acc.AssertContainsTaggedFields(t, "m1", expectedFields, expectedTags)
}

View File

@ -8,4 +8,4 @@
drop_original = false drop_original = false
## Configures which basic stats to push as fields ## Configures which basic stats to push as fields
# stats = ["count","diff","rate","min","max","mean","non_negative_diff","non_negative_rate","percent_change","stdev","s2","sum","interval"] # stats = ["count","diff","rate","min","max","mean","non_negative_diff","non_negative_rate","percent_change","stdev","s2","sum","interval","last"]