Refactor handling of MinMax functionality into RunningAggregator

allows for easier addition of a sliding window at a later time.

Also makes `period` be a generic argument for all aggregator plugins.
This commit is contained in:
Cameron Sparr 2016-09-22 18:10:51 +01:00
parent ef885eda62
commit fead80844e
7 changed files with 252 additions and 297 deletions

View File

@ -259,9 +259,6 @@ func (a *Agent) flusher(shutdown chan struct{}, metricC chan telegraf.Metric) er
for { for {
select { select {
case <-shutdown: case <-shutdown:
for _, agg := range a.Config.Aggregators {
agg.Aggregator.Stop()
}
if len(outMetricC) > 0 { if len(outMetricC) > 0 {
// keep going until outMetricC is flushed // keep going until outMetricC is flushed
continue continue
@ -273,7 +270,7 @@ func (a *Agent) flusher(shutdown chan struct{}, metricC chan telegraf.Metric) er
var dropOriginal bool var dropOriginal bool
if !m.IsAggregate() { if !m.IsAggregate() {
for _, agg := range a.Config.Aggregators { for _, agg := range a.Config.Aggregators {
if ok := agg.Apply(copyMetric(m)); ok { if ok := agg.Add(copyMetric(m)); ok {
dropOriginal = true dropOriginal = true
} }
} }
@ -315,22 +312,6 @@ func (a *Agent) flusher(shutdown chan struct{}, metricC chan telegraf.Metric) er
} }
} }
func copyMetric(m telegraf.Metric) telegraf.Metric {
t := time.Time(m.Time())
tags := make(map[string]string)
fields := make(map[string]interface{})
for k, v := range m.Tags() {
tags[k] = v
}
for k, v := range m.Fields() {
fields[k] = v
}
out, _ := telegraf.NewMetric(m.Name(), tags, fields, t)
return out
}
// Run runs the agent daemon, gathering every Interval // Run runs the agent daemon, gathering every Interval
func (a *Agent) Run(shutdown chan struct{}) error { func (a *Agent) Run(shutdown chan struct{}) error {
var wg sync.WaitGroup var wg sync.WaitGroup
@ -367,18 +348,6 @@ func (a *Agent) Run(shutdown chan struct{}) error {
time.Sleep(time.Duration(i - (time.Now().UnixNano() % i))) time.Sleep(time.Duration(i - (time.Now().UnixNano() % i)))
} }
// Start all Aggregators
for _, aggregator := range a.Config.Aggregators {
acc := NewAccumulator(aggregator, metricC)
acc.SetPrecision(a.Config.Agent.Precision.Duration,
a.Config.Agent.Interval.Duration)
if err := aggregator.Aggregator.Start(acc); err != nil {
log.Printf("[%s] failed to start, exiting\n%s\n",
aggregator.Name(), err.Error())
return err
}
}
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
@ -403,6 +372,33 @@ func (a *Agent) Run(shutdown chan struct{}) error {
}(input, interval) }(input, interval)
} }
wg.Add(len(a.Config.Aggregators))
for _, aggregator := range a.Config.Aggregators {
go func(agg *models.RunningAggregator) {
defer wg.Done()
acc := NewAccumulator(agg, metricC)
acc.SetPrecision(a.Config.Agent.Precision.Duration,
a.Config.Agent.Interval.Duration)
agg.Run(acc, shutdown)
}(aggregator)
}
wg.Wait() wg.Wait()
return nil return nil
} }
func copyMetric(m telegraf.Metric) telegraf.Metric {
t := time.Time(m.Time())
tags := make(map[string]string)
fields := make(map[string]interface{})
for k, v := range m.Tags() {
tags[k] = v
}
for k, v := range m.Fields() {
fields[k] = v
}
out, _ := telegraf.NewMetric(m.Name(), tags, fields, t)
return out
}

View File

@ -1,16 +1,22 @@
package telegraf package telegraf
// Aggregator is an interface for implementing an Aggregator plugin.
// the RunningAggregator wraps this interface and guarantees that
// Add, Push, and Reset can not be called concurrently, so locking is not
// required when implementing an Aggregator plugin.
type Aggregator interface { type Aggregator interface {
// SampleConfig returns the default configuration of the Input // SampleConfig returns the default configuration of the Input.
SampleConfig() string SampleConfig() string
// Description returns a one-sentence description on the Input // Description returns a one-sentence description on the Input.
Description() string Description() string
// Apply the metric to the aggregator // Add the metric to the aggregator.
Apply(in Metric) Add(in Metric)
// Start starts the service filter with the given accumulator // Push pushes the current aggregates to the accumulator.
Start(acc Accumulator) error Push(acc Accumulator)
Stop()
// Reset resets the aggregators caches and aggregates.
Reset()
} }

View File

@ -693,7 +693,7 @@ func (c *Config) addAggregator(name string, table *ast.Table) error {
} }
aggregator := creator() aggregator := creator()
aggregatorConfig, err := buildAggregator(name, table) conf, err := buildAggregator(name, table)
if err != nil { if err != nil {
return err return err
} }
@ -702,12 +702,7 @@ func (c *Config) addAggregator(name string, table *ast.Table) error {
return err return err
} }
rf := &models.RunningAggregator{ c.Aggregators = append(c.Aggregators, models.NewRunningAggregator(aggregator, conf))
Aggregator: aggregator,
Config: aggregatorConfig,
}
c.Aggregators = append(c.Aggregators, rf)
return nil return nil
} }
@ -818,7 +813,6 @@ func (c *Config) addInput(name string, table *ast.Table) error {
// buildAggregator TODO doc // buildAggregator TODO doc
func buildAggregator(name string, tbl *ast.Table) (*models.AggregatorConfig, error) { func buildAggregator(name string, tbl *ast.Table) (*models.AggregatorConfig, error) {
conf := &models.AggregatorConfig{Name: name}
unsupportedFields := []string{"tagexclude", "taginclude"} unsupportedFields := []string{"tagexclude", "taginclude"}
for _, field := range unsupportedFields { for _, field := range unsupportedFields {
if _, ok := tbl.Fields[field]; ok { if _, ok := tbl.Fields[field]; ok {
@ -826,6 +820,34 @@ func buildAggregator(name string, tbl *ast.Table) (*models.AggregatorConfig, err
} }
} }
conf := &models.AggregatorConfig{Name: name}
if node, ok := tbl.Fields["period"]; ok {
if kv, ok := node.(*ast.KeyValue); ok {
if str, ok := kv.Value.(*ast.String); ok {
dur, err := time.ParseDuration(str.Value)
if err != nil {
return nil, err
}
conf.Period = dur
}
}
}
if node, ok := tbl.Fields["delay"]; ok {
if kv, ok := node.(*ast.KeyValue); ok {
if str, ok := kv.Value.(*ast.String); ok {
dur, err := time.ParseDuration(str.Value)
if err != nil {
return nil, err
}
conf.Delay = dur
}
}
}
if node, ok := tbl.Fields["drop_original"]; ok { if node, ok := tbl.Fields["drop_original"]; ok {
if kv, ok := node.(*ast.KeyValue); ok { if kv, ok := node.(*ast.KeyValue); ok {
if b, ok := kv.Value.(*ast.Boolean); ok { if b, ok := kv.Value.(*ast.Boolean); ok {
@ -871,6 +893,8 @@ func buildAggregator(name string, tbl *ast.Table) (*models.AggregatorConfig, err
} }
} }
delete(tbl.Fields, "period")
delete(tbl.Fields, "delay")
delete(tbl.Fields, "drop_original") delete(tbl.Fields, "drop_original")
delete(tbl.Fields, "name_prefix") delete(tbl.Fields, "name_prefix")
delete(tbl.Fields, "name_suffix") delete(tbl.Fields, "name_suffix")

View File

@ -7,8 +7,21 @@ import (
) )
type RunningAggregator struct { type RunningAggregator struct {
Aggregator telegraf.Aggregator a telegraf.Aggregator
Config *AggregatorConfig Config *AggregatorConfig
metrics chan telegraf.Metric
}
func NewRunningAggregator(
a telegraf.Aggregator,
conf *AggregatorConfig,
) *RunningAggregator {
return &RunningAggregator{
a: a,
Config: conf,
metrics: make(chan telegraf.Metric, 100),
}
} }
// AggregatorConfig containing configuration parameters for the running // AggregatorConfig containing configuration parameters for the running
@ -22,6 +35,9 @@ type AggregatorConfig struct {
MeasurementSuffix string MeasurementSuffix string
Tags map[string]string Tags map[string]string
Filter Filter Filter Filter
Period time.Duration
Delay time.Duration
} }
func (r *RunningAggregator) Name() string { func (r *RunningAggregator) Name() string {
@ -56,10 +72,10 @@ func (r *RunningAggregator) MakeMetric(
return m return m
} }
// Apply applies the given metric to the aggregator. // Add applies the given metric to the aggregator.
// Before applying to the plugin, it will run any defined filters on the metric. // Before applying to the plugin, it will run any defined filters on the metric.
// Apply returns true if the original metric should be dropped. // Apply returns true if the original metric should be dropped.
func (r *RunningAggregator) Apply(in telegraf.Metric) bool { func (r *RunningAggregator) Add(in telegraf.Metric) bool {
if r.Config.Filter.IsActive() { if r.Config.Filter.IsActive() {
// check if the aggregator should apply this metric // check if the aggregator should apply this metric
name := in.Name() name := in.Name()
@ -74,6 +90,49 @@ func (r *RunningAggregator) Apply(in telegraf.Metric) bool {
in, _ = telegraf.NewMetric(name, tags, fields, t) in, _ = telegraf.NewMetric(name, tags, fields, t)
} }
r.Aggregator.Apply(in) r.metrics <- in
return r.Config.DropOriginal return r.Config.DropOriginal
} }
func (r *RunningAggregator) add(in telegraf.Metric) {
r.a.Add(in)
}
func (r *RunningAggregator) push(acc telegraf.Accumulator) {
r.a.Push(acc)
}
func (r *RunningAggregator) reset() {
r.a.Reset()
}
func (r *RunningAggregator) Run(
acc telegraf.Accumulator,
shutdown chan struct{},
) {
if r.Config.Delay == 0 {
r.Config.Delay = time.Millisecond * 100
}
if r.Config.Period == 0 {
r.Config.Period = time.Second * 30
}
time.Sleep(r.Config.Delay)
periodT := time.NewTicker(r.Config.Period)
defer periodT.Stop()
for {
select {
case <-shutdown:
if len(r.metrics) > 0 {
// wait until metrics are flushed before exiting
continue
}
return
case m := <-r.metrics:
r.add(m)
case <-periodT.C:
r.push(acc)
r.reset()
}
}
}

View File

@ -2,26 +2,28 @@ package models
import ( import (
"fmt" "fmt"
"sync"
"sync/atomic"
"testing" "testing"
"time" "time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestApply(t *testing.T) { func TestAdd(t *testing.T) {
a := &TestAggregator{} a := &TestAggregator{}
ra := RunningAggregator{ ra := NewRunningAggregator(a, &AggregatorConfig{
Config: &AggregatorConfig{
Name: "TestRunningAggregator", Name: "TestRunningAggregator",
Filter: Filter{ Filter: Filter{
NamePass: []string{"*"}, NamePass: []string{"*"},
}, },
}, })
Aggregator: a,
}
assert.NoError(t, ra.Config.Filter.Compile()) assert.NoError(t, ra.Config.Filter.Compile())
acc := testutil.Accumulator{}
go ra.Run(&acc, make(chan struct{}))
m := ra.MakeMetric( m := ra.MakeMetric(
"RITest", "RITest",
@ -30,21 +32,64 @@ func TestApply(t *testing.T) {
telegraf.Untyped, telegraf.Untyped,
time.Now(), time.Now(),
) )
assert.False(t, ra.Apply(m)) assert.False(t, ra.Add(m))
assert.Equal(t, int64(101), a.sum)
for {
if atomic.LoadInt64(&a.sum) > 0 {
break
}
}
assert.Equal(t, int64(101), atomic.LoadInt64(&a.sum))
} }
func TestApplyDropOriginal(t *testing.T) { func TestAddAndPushOnePeriod(t *testing.T) {
ra := RunningAggregator{ a := &TestAggregator{}
Config: &AggregatorConfig{ ra := NewRunningAggregator(a, &AggregatorConfig{
Name: "TestRunningAggregator",
Filter: Filter{
NamePass: []string{"*"},
},
Period: time.Millisecond * 500,
})
assert.NoError(t, ra.Config.Filter.Compile())
acc := testutil.Accumulator{}
shutdown := make(chan struct{})
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
ra.Run(&acc, shutdown)
}()
m := ra.MakeMetric(
"RITest",
map[string]interface{}{"value": int(101)},
map[string]string{},
telegraf.Untyped,
time.Now(),
)
assert.False(t, ra.Add(m))
for {
if acc.NMetrics() > 0 {
break
}
}
acc.AssertContainsFields(t, "TestMetric", map[string]interface{}{"sum": int64(101)})
close(shutdown)
wg.Wait()
}
func TestAddDropOriginal(t *testing.T) {
ra := NewRunningAggregator(&TestAggregator{}, &AggregatorConfig{
Name: "TestRunningAggregator", Name: "TestRunningAggregator",
Filter: Filter{ Filter: Filter{
NamePass: []string{"RI*"}, NamePass: []string{"RI*"},
}, },
DropOriginal: true, DropOriginal: true,
}, })
Aggregator: &TestAggregator{},
}
assert.NoError(t, ra.Config.Filter.Compile()) assert.NoError(t, ra.Config.Filter.Compile())
m := ra.MakeMetric( m := ra.MakeMetric(
@ -54,9 +99,9 @@ func TestApplyDropOriginal(t *testing.T) {
telegraf.Untyped, telegraf.Untyped,
time.Now(), time.Now(),
) )
assert.True(t, ra.Apply(m)) assert.True(t, ra.Add(m))
// this metric name doesn't match the filter, so Apply will return false // this metric name doesn't match the filter, so Add will return false
m2 := ra.MakeMetric( m2 := ra.MakeMetric(
"foobar", "foobar",
map[string]interface{}{"value": int(101)}, map[string]interface{}{"value": int(101)},
@ -64,17 +109,15 @@ func TestApplyDropOriginal(t *testing.T) {
telegraf.Untyped, telegraf.Untyped,
time.Now(), time.Now(),
) )
assert.False(t, ra.Apply(m2)) assert.False(t, ra.Add(m2))
} }
// make an untyped, counter, & gauge metric // make an untyped, counter, & gauge metric
func TestMakeMetricA(t *testing.T) { func TestMakeMetricA(t *testing.T) {
now := time.Now() now := time.Now()
ra := RunningAggregator{ ra := NewRunningAggregator(&TestAggregator{}, &AggregatorConfig{
Config: &AggregatorConfig{
Name: "TestRunningAggregator", Name: "TestRunningAggregator",
}, })
}
assert.Equal(t, "aggregators.TestRunningAggregator", ra.Name()) assert.Equal(t, "aggregators.TestRunningAggregator", ra.Name())
m := ra.MakeMetric( m := ra.MakeMetric(
@ -138,13 +181,19 @@ type TestAggregator struct {
func (t *TestAggregator) Description() string { return "" } func (t *TestAggregator) Description() string { return "" }
func (t *TestAggregator) SampleConfig() string { return "" } func (t *TestAggregator) SampleConfig() string { return "" }
func (t *TestAggregator) Start(acc telegraf.Accumulator) error { return nil } func (t *TestAggregator) Reset() {}
func (t *TestAggregator) Stop() {}
func (t *TestAggregator) Apply(in telegraf.Metric) { func (t *TestAggregator) Push(acc telegraf.Accumulator) {
acc.AddFields("TestMetric",
map[string]interface{}{"sum": t.sum},
map[string]string{},
)
}
func (t *TestAggregator) Add(in telegraf.Metric) {
for _, v := range in.Fields() { for _, v := range in.Fields() {
if vi, ok := v.(int64); ok { if vi, ok := v.(int64); ok {
t.sum += vi atomic.AddInt64(&t.sum, vi)
} }
} }
} }

View File

@ -1,28 +1,21 @@
package minmax package minmax
import ( import (
"sync"
"time"
"github.com/influxdata/telegraf" "github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/aggregators" "github.com/influxdata/telegraf/plugins/aggregators"
) )
type MinMax struct { type MinMax struct {
Period internal.Duration
// metrics waiting to be processed
metrics chan telegraf.Metric
shutdown chan struct{}
wg sync.WaitGroup
// caches for metric fields, names, and tags // caches for metric fields, names, and tags
fieldCache map[uint64]map[string]minmax fieldCache map[uint64]map[string]minmax
nameCache map[uint64]string nameCache map[uint64]string
tagCache map[uint64]map[string]string tagCache map[uint64]map[string]string
}
acc telegraf.Accumulator func NewMinMax() telegraf.Aggregator {
mm := &MinMax{}
mm.Reset()
return mm
} }
type minmax struct { type minmax struct {
@ -43,11 +36,7 @@ func (m *MinMax) Description() string {
return "Keep the aggregate min/max of each metric passing through." return "Keep the aggregate min/max of each metric passing through."
} }
func (m *MinMax) Apply(in telegraf.Metric) { func (m *MinMax) Add(in telegraf.Metric) {
m.metrics <- in
}
func (m *MinMax) apply(in telegraf.Metric) {
id := in.HashID() id := in.HashID()
if _, ok := m.nameCache[id]; !ok { if _, ok := m.nameCache[id]; !ok {
// hit an uncached metric, create caches for first time: // hit an uncached metric, create caches for first time:
@ -90,84 +79,23 @@ func (m *MinMax) apply(in telegraf.Metric) {
} }
} }
func (m *MinMax) Start(acc telegraf.Accumulator) error { func (m *MinMax) Push(acc telegraf.Accumulator) {
m.metrics = make(chan telegraf.Metric, 10) for id, _ := range m.nameCache {
m.shutdown = make(chan struct{})
m.clearCache()
m.acc = acc
m.wg.Add(1)
if m.Period.Duration > 0 {
go m.periodHandler()
} else {
go m.continuousHandler()
}
return nil
}
func (m *MinMax) Stop() {
close(m.shutdown)
m.wg.Wait()
}
func (m *MinMax) addfields(id uint64) {
fields := map[string]interface{}{} fields := map[string]interface{}{}
for k, v := range m.fieldCache[id] { for k, v := range m.fieldCache[id] {
fields[k+"_min"] = v.min fields[k+"_min"] = v.min
fields[k+"_max"] = v.max fields[k+"_max"] = v.max
} }
m.acc.AddFields(m.nameCache[id], fields, m.tagCache[id]) acc.AddFields(m.nameCache[id], fields, m.tagCache[id])
}
} }
func (m *MinMax) clearCache() { func (m *MinMax) Reset() {
m.fieldCache = make(map[uint64]map[string]minmax) m.fieldCache = make(map[uint64]map[string]minmax)
m.nameCache = make(map[uint64]string) m.nameCache = make(map[uint64]string)
m.tagCache = make(map[uint64]map[string]string) m.tagCache = make(map[uint64]map[string]string)
} }
// periodHandler only adds the aggregate metrics on the configured Period.
// thus if telegraf's collection interval is 10s, and period is 30s, there
// will only be one aggregate sent every 3 metrics.
func (m *MinMax) periodHandler() {
// TODO make this sleep less of a hack!
time.Sleep(time.Millisecond * 200)
defer m.wg.Done()
ticker := time.NewTicker(m.Period.Duration)
defer ticker.Stop()
for {
select {
case in := <-m.metrics:
m.apply(in)
case <-m.shutdown:
if len(m.metrics) > 0 {
continue
}
return
case <-ticker.C:
for id, _ := range m.nameCache {
m.addfields(id)
}
m.clearCache()
}
}
}
// continuousHandler sends one metric for every metric that passes through it.
func (m *MinMax) continuousHandler() {
defer m.wg.Done()
for {
select {
case in := <-m.metrics:
m.apply(in)
m.addfields(in.HashID())
case <-m.shutdown:
if len(m.metrics) > 0 {
continue
}
return
}
}
}
func compare(a, b float64) int { func compare(a, b float64) int {
if a < b { if a < b {
return -1 return -1
@ -190,6 +118,6 @@ func convert(in interface{}) (float64, bool) {
func init() { func init() {
aggregators.Add("minmax", func() telegraf.Aggregator { aggregators.Add("minmax", func() telegraf.Aggregator {
return &MinMax{} return NewMinMax()
}) })
} }

View File

@ -5,10 +5,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"
) )
var m1, _ = telegraf.NewMetric("m1", var m1, _ = telegraf.NewMetric("m1",
@ -48,34 +45,22 @@ var m2, _ = telegraf.NewMetric("m1",
) )
func BenchmarkApply(b *testing.B) { func BenchmarkApply(b *testing.B) {
minmax := MinMax{} minmax := NewMinMax()
minmax.clearCache()
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
minmax.apply(m1) minmax.Add(m1)
minmax.apply(m2) minmax.Add(m2)
} }
} }
// Test two metrics getting added, when running with a period, and the metrics // Test two metrics getting added.
// are added in the same period.
func TestMinMaxWithPeriod(t *testing.T) { func TestMinMaxWithPeriod(t *testing.T) {
acc := testutil.Accumulator{} acc := testutil.Accumulator{}
minmax := MinMax{ minmax := NewMinMax()
Period: internal.Duration{Duration: time.Millisecond * 500},
}
assert.NoError(t, minmax.Start(&acc))
defer minmax.Stop()
minmax.Apply(m1) minmax.Add(m1)
minmax.Apply(m2) minmax.Add(m2)
minmax.Push(&acc)
for {
if acc.NMetrics() > 0 {
break
}
time.Sleep(time.Millisecond)
}
expectedFields := map[string]interface{}{ expectedFields := map[string]interface{}{
"a_max": float64(1), "a_max": float64(1),
@ -107,23 +92,14 @@ func TestMinMaxWithPeriod(t *testing.T) {
acc.AssertContainsTaggedFields(t, "m1", expectedFields, expectedTags) acc.AssertContainsTaggedFields(t, "m1", expectedFields, expectedTags)
} }
// Test two metrics getting added, when running with a period, and the metrics // Test two metrics getting added with a push/reset in between (simulates
// are added in two different periods. // getting added in different periods.)
func TestMinMaxDifferentPeriods(t *testing.T) { func TestMinMaxDifferentPeriods(t *testing.T) {
acc := testutil.Accumulator{} acc := testutil.Accumulator{}
minmax := MinMax{ minmax := NewMinMax()
Period: internal.Duration{Duration: time.Millisecond * 100},
}
assert.NoError(t, minmax.Start(&acc))
defer minmax.Stop()
minmax.Apply(m1) minmax.Add(m1)
for { minmax.Push(&acc)
if acc.NMetrics() > 0 {
break
}
time.Sleep(time.Millisecond)
}
expectedFields := map[string]interface{}{ expectedFields := map[string]interface{}{
"a_max": float64(1), "a_max": float64(1),
"a_min": float64(1), "a_min": float64(1),
@ -152,13 +128,9 @@ func TestMinMaxDifferentPeriods(t *testing.T) {
acc.AssertContainsTaggedFields(t, "m1", expectedFields, expectedTags) acc.AssertContainsTaggedFields(t, "m1", expectedFields, expectedTags)
acc.ClearMetrics() acc.ClearMetrics()
minmax.Apply(m2) minmax.Reset()
for { minmax.Add(m2)
if acc.NMetrics() > 0 { minmax.Push(&acc)
break
}
time.Sleep(time.Millisecond)
}
expectedFields = map[string]interface{}{ expectedFields = map[string]interface{}{
"a_max": float64(1), "a_max": float64(1),
"a_min": float64(1), "a_min": float64(1),
@ -188,82 +160,3 @@ func TestMinMaxDifferentPeriods(t *testing.T) {
} }
acc.AssertContainsTaggedFields(t, "m1", expectedFields, expectedTags) acc.AssertContainsTaggedFields(t, "m1", expectedFields, expectedTags)
} }
// Test two metrics getting added, when running without a period.
func TestMinMaxWithoutPeriod(t *testing.T) {
acc := testutil.Accumulator{}
minmax := MinMax{}
assert.NoError(t, minmax.Start(&acc))
defer minmax.Stop()
minmax.Apply(m1)
for {
if acc.NMetrics() > 0 {
break
}
time.Sleep(time.Millisecond)
}
expectedFields := map[string]interface{}{
"a_max": float64(1),
"a_min": float64(1),
"b_max": float64(1),
"b_min": float64(1),
"c_max": float64(1),
"c_min": float64(1),
"d_max": float64(1),
"d_min": float64(1),
"e_max": float64(1),
"e_min": float64(1),
"f_max": float64(2),
"f_min": float64(2),
"g_max": float64(2),
"g_min": float64(2),
"h_max": float64(2),
"h_min": float64(2),
"i_max": float64(2),
"i_min": float64(2),
"j_max": float64(3),
"j_min": float64(3),
}
expectedTags := map[string]string{
"foo": "bar",
}
acc.AssertContainsTaggedFields(t, "m1", expectedFields, expectedTags)
acc.ClearMetrics()
minmax.Apply(m2)
for {
if acc.NMetrics() > 0 {
break
}
time.Sleep(time.Millisecond)
}
expectedFields = map[string]interface{}{
"a_max": float64(1),
"a_min": float64(1),
"b_max": float64(3),
"b_min": float64(1),
"c_max": float64(3),
"c_min": float64(1),
"d_max": float64(3),
"d_min": float64(1),
"e_max": float64(3),
"e_min": float64(1),
"f_max": float64(2),
"f_min": float64(1),
"g_max": float64(2),
"g_min": float64(1),
"h_max": float64(2),
"h_min": float64(1),
"i_max": float64(2),
"i_min": float64(1),
"j_max": float64(3),
"j_min": float64(1),
"k_max": float64(200),
"k_min": float64(200),
}
expectedTags = map[string]string{
"foo": "bar",
}
acc.AssertContainsTaggedFields(t, "m1", expectedFields, expectedTags)
}