package signalfx import ( "context" "errors" "reflect" "testing" "time" "github.com/signalfx/golib/v3/datapoint" "github.com/signalfx/golib/v3/event" "github.com/stretchr/testify/require" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/metric" "github.com/influxdata/telegraf/plugins/outputs" "github.com/influxdata/telegraf/testutil" ) type sink struct { datapoints []*datapoint.Datapoint events []*event.Event } func (s *sink) AddDatapoints(_ context.Context, points []*datapoint.Datapoint) error { s.datapoints = append(s.datapoints, points...) return nil } func (s *sink) AddEvents(_ context.Context, events []*event.Event) error { s.events = append(s.events, events...) return nil } type errorsink struct { datapoints []*datapoint.Datapoint events []*event.Event } func (*errorsink) AddDatapoints(context.Context, []*datapoint.Datapoint) error { return errors.New("not sending datapoints") } func (*errorsink) AddEvents(context.Context, []*event.Event) error { return errors.New("not sending events") } func TestSignalFx_SignalFx(t *testing.T) { type measurement struct { name string tags map[string]string fields map[string]interface{} time time.Time tp telegraf.ValueType } type fields struct { IncludedEvents []string } tests := []struct { name string fields fields measurements []*measurement want errorsink }{ { name: "add datapoints of all types", fields: fields{}, measurements: []*measurement{ { name: "datapoint", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": float64(3.14)}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Counter, }, { name: "datapoint", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": float64(3.14)}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Gauge, }, { name: "datapoint", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": float64(3.14)}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Summary, }, { name: "datapoint", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": float64(3.14)}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Histogram, }, { name: "datapoint", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": float64(3.14)}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Untyped, }, { name: "datapoint", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": float64(3.14)}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), }, { name: "datapoint", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"myboolmeasurement": true}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), }, { name: "datapoint", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"myboolmeasurement": false}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), }, }, want: errorsink{ datapoints: []*datapoint.Datapoint{ datapoint.New( "datapoint.mymeasurement", map[string]string{ "host": "192.168.0.1", }, datapoint.NewFloatValue(float64(3.14)), datapoint.Counter, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)), datapoint.New( "datapoint.mymeasurement", map[string]string{ "host": "192.168.0.1", }, datapoint.NewFloatValue(float64(3.14)), datapoint.Gauge, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)), datapoint.New( "datapoint.mymeasurement", map[string]string{ "host": "192.168.0.1", }, datapoint.NewFloatValue(float64(3.14)), datapoint.Gauge, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)), datapoint.New( "datapoint.mymeasurement", map[string]string{ "host": "192.168.0.1", }, datapoint.NewFloatValue(float64(3.14)), datapoint.Gauge, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)), datapoint.New( "datapoint.mymeasurement", map[string]string{ "host": "192.168.0.1", }, datapoint.NewFloatValue(float64(3.14)), datapoint.Gauge, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)), datapoint.New( "datapoint.mymeasurement", map[string]string{ "host": "192.168.0.1", }, datapoint.NewFloatValue(float64(3.14)), datapoint.Gauge, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)), datapoint.New( "datapoint.myboolmeasurement", map[string]string{ "host": "192.168.0.1", }, datapoint.NewIntValue(int64(1)), datapoint.Gauge, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)), datapoint.New( "datapoint.myboolmeasurement", map[string]string{ "host": "192.168.0.1", }, datapoint.NewIntValue(int64(0)), datapoint.Gauge, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)), }, events: make([]*event.Event, 0), }, }, { name: "add events of all types", fields: fields{ IncludedEvents: []string{"event.mymeasurement"}, }, measurements: []*measurement{ { name: "event", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": "hello world"}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Counter, }, { name: "event", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": "hello world"}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Gauge, }, { name: "event", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": "hello world"}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Summary, }, { name: "event", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": "hello world"}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Histogram, }, { name: "event", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": "hello world"}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Untyped, }, { name: "event", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": "hello world"}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), }, }, want: errorsink{ datapoints: make([]*datapoint.Datapoint, 0), events: []*event.Event{ event.NewWithProperties( "event.mymeasurement", event.AGENT, map[string]string{ "host": "192.168.0.1", }, map[string]interface{}{ "message": "hello world", }, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)), event.NewWithProperties( "event.mymeasurement", event.AGENT, map[string]string{ "host": "192.168.0.1", }, map[string]interface{}{ "message": "hello world", }, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)), event.NewWithProperties( "event.mymeasurement", event.AGENT, map[string]string{ "host": "192.168.0.1", }, map[string]interface{}{ "message": "hello world", }, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)), event.NewWithProperties( "event.mymeasurement", event.AGENT, map[string]string{ "host": "192.168.0.1", }, map[string]interface{}{ "message": "hello world", }, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)), event.NewWithProperties( "event.mymeasurement", event.AGENT, map[string]string{ "host": "192.168.0.1", }, map[string]interface{}{ "message": "hello world", }, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)), event.NewWithProperties( "event.mymeasurement", event.AGENT, map[string]string{ "host": "192.168.0.1", }, map[string]interface{}{ "message": "hello world", }, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)), }, }, }, { name: "exclude events by default", fields: fields{}, measurements: []*measurement{ { name: "event", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"value": "hello world"}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Gauge, }, }, want: errorsink{ datapoints: make([]*datapoint.Datapoint, 0), events: make([]*event.Event, 0), }, }, { name: "add datapoint with field named value", fields: fields{}, measurements: []*measurement{ { name: "datapoint", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"value": float64(3.14)}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Gauge, }, }, want: errorsink{ datapoints: []*datapoint.Datapoint{ datapoint.New( "datapoint", map[string]string{ "host": "192.168.0.1", }, datapoint.NewFloatValue(float64(3.14)), datapoint.Gauge, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)), }, events: make([]*event.Event, 0), }, }, { name: "add event", fields: fields{ IncludedEvents: []string{"event.mymeasurement"}, }, measurements: []*measurement{ { name: "event", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": "hello world"}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Untyped, }, }, want: errorsink{ datapoints: make([]*datapoint.Datapoint, 0), events: []*event.Event{ event.NewWithProperties( "event.mymeasurement", event.AGENT, map[string]string{ "host": "192.168.0.1", }, map[string]interface{}{ "message": "hello world", }, time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)), }, }, }, { name: "exclude events that are not explicitly included", fields: fields{}, measurements: []*measurement{ { name: "event", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"value": "hello world"}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Gauge, }, }, want: errorsink{ datapoints: make([]*datapoint.Datapoint, 0), events: make([]*event.Event, 0), }, }, { name: "malformed metadata event", fields: fields{}, measurements: []*measurement{ { name: "event", tags: map[string]string{"host": "192.168.0.1", "sf_metric": "objects.host-meta-data"}, fields: map[string]interface{}{"value": "hello world"}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Gauge, }, }, want: errorsink{ datapoints: make([]*datapoint.Datapoint, 0), events: make([]*event.Event, 0), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := outputs.Outputs["signalfx"]().(*SignalFx) s.IncludedEventNames = tt.fields.IncludedEvents s.SignalFxRealm = "test" s.Log = testutil.Logger{} require.NoError(t, s.Connect()) s.client = &sink{ datapoints: make([]*datapoint.Datapoint, 0), events: make([]*event.Event, 0), } measurements := make([]telegraf.Metric, 0, len(tt.measurements)) for _, measurement := range tt.measurements { measurements = append(measurements, metric.New(measurement.name, measurement.tags, measurement.fields, measurement.time, measurement.tp)) } err := s.Write(measurements) require.NoError(t, err) require.Eventually(t, func() bool { return len(s.client.(*sink).datapoints) == len(tt.want.datapoints) }, 5*time.Second, 10*time.Millisecond) require.Eventually(t, func() bool { return len(s.client.(*sink).events) == len(tt.want.events) }, 5*time.Second, 10*time.Millisecond) if !reflect.DeepEqual(s.client.(*sink).datapoints, tt.want.datapoints) { t.Errorf("Collected datapoints do not match desired. Collected: %v Desired: %v", s.client.(*sink).datapoints, tt.want.datapoints) } if !reflect.DeepEqual(s.client.(*sink).events, tt.want.events) { t.Errorf("Collected events do not match desired. Collected: %v Desired: %v", s.client.(*sink).events, tt.want.events) } }) } } func TestSignalFx_Errors(t *testing.T) { type measurement struct { name string tags map[string]string fields map[string]interface{} time time.Time tp telegraf.ValueType } type fields struct { IncludedEvents []string } type want struct { datapoints []*datapoint.Datapoint events []*event.Event } tests := []struct { name string fields fields measurements []*measurement want want }{ { name: "add datapoints of all types", fields: fields{}, measurements: []*measurement{ { name: "datapoint", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": float64(3.14)}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Counter, }, { name: "datapoint", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": float64(3.14)}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Gauge, }, { name: "datapoint", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": float64(3.14)}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Summary, }, { name: "datapoint", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": float64(3.14)}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Histogram, }, { name: "datapoint", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": float64(3.14)}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Untyped, }, { name: "datapoint", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": float64(3.14)}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), }, }, want: want{ datapoints: make([]*datapoint.Datapoint, 0), events: make([]*event.Event, 0), }, }, { name: "add events of all types", fields: fields{ IncludedEvents: []string{"event.mymeasurement"}, }, measurements: []*measurement{ { name: "event", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": "hello world"}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Counter, }, { name: "event", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": "hello world"}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Gauge, }, { name: "event", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": "hello world"}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Summary, }, { name: "event", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": "hello world"}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Histogram, }, { name: "event", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": "hello world"}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), tp: telegraf.Untyped, }, { name: "event", tags: map[string]string{"host": "192.168.0.1"}, fields: map[string]interface{}{"mymeasurement": "hello world"}, time: time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC), }, }, want: want{ datapoints: make([]*datapoint.Datapoint, 0), events: make([]*event.Event, 0), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := outputs.Outputs["signalfx"]().(*SignalFx) // constrain the buffer to cover code that emits when batch size is met s.IncludedEventNames = tt.fields.IncludedEvents s.SignalFxRealm = "test" s.Log = testutil.Logger{} require.NoError(t, s.Connect()) s.client = &errorsink{ datapoints: make([]*datapoint.Datapoint, 0), events: make([]*event.Event, 0), } for _, measurement := range tt.measurements { m := metric.New( measurement.name, measurement.tags, measurement.fields, measurement.time, measurement.tp, ) err := s.Write([]telegraf.Metric{m}) require.Error(t, err) } for len(s.client.(*errorsink).datapoints) != len(tt.want.datapoints) || len(s.client.(*errorsink).events) != len(tt.want.events) { time.Sleep(1 * time.Second) } if !reflect.DeepEqual(s.client.(*errorsink).datapoints, tt.want.datapoints) { t.Errorf("Collected datapoints do not match desired. Collected: %v Desired: %v", s.client.(*errorsink).datapoints, tt.want.datapoints) } if !reflect.DeepEqual(s.client.(*errorsink).events, tt.want.events) { t.Errorf("Collected events do not match desired. Collected: %v Desired: %v", s.client.(*errorsink).events, tt.want.events) } }) } } func TestGetMetricName(t *testing.T) { type args struct { metric string field string dims map[string]string } tests := []struct { name string args args want string wantsfx bool }{ { name: "fields that equal value should not be append to metricname", args: args{ metric: "datapoint", field: "value", dims: map[string]string{ "testDimKey": "testDimVal", }, }, want: "datapoint", }, { name: "fields other than 'value' with out sf_metric dim should return measurement.fieldname as metric name", args: args{ metric: "datapoint", field: "test", dims: map[string]string{ "testDimKey": "testDimVal", }, }, want: "datapoint.test", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := getMetricName(tt.args.metric, tt.args.field) if got != tt.want { t.Errorf("getMetricName() got = %v, want %v", got, tt.want) } }) } }