From bb0be0d76de50077af2bfc0c8d31602d9205703d Mon Sep 17 00:00:00 2001 From: Stephen Kwong Date: Mon, 18 Jan 2016 11:39:14 -0800 Subject: [PATCH 1/7] Add Cloudwatch output --- plugins/outputs/all/all.go | 1 + plugins/outputs/cloudwatch/README.md | 31 ++++ plugins/outputs/cloudwatch/cloudwatch.go | 188 +++++++++++++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 plugins/outputs/cloudwatch/README.md create mode 100644 plugins/outputs/cloudwatch/cloudwatch.go diff --git a/plugins/outputs/all/all.go b/plugins/outputs/all/all.go index 7eedb592a29ae..85d5e6cad02fc 100644 --- a/plugins/outputs/all/all.go +++ b/plugins/outputs/all/all.go @@ -3,6 +3,7 @@ package all import ( _ "github.com/influxdb/telegraf/plugins/outputs/amon" _ "github.com/influxdb/telegraf/plugins/outputs/amqp" + _ "github.com/influxdb/telegraf/plugins/outputs/cloudwatch" _ "github.com/influxdb/telegraf/plugins/outputs/datadog" _ "github.com/influxdb/telegraf/plugins/outputs/graphite" _ "github.com/influxdb/telegraf/plugins/outputs/influxdb" diff --git a/plugins/outputs/cloudwatch/README.md b/plugins/outputs/cloudwatch/README.md new file mode 100644 index 0000000000000..5b29a1bbbded5 --- /dev/null +++ b/plugins/outputs/cloudwatch/README.md @@ -0,0 +1,31 @@ +## Amazon CloudWatch Output for Telegraf + +This plugin will send points to Amazon CloudWatch. + +## Amazon Authentication + +This plugin uses a credential chain for Authentication with the Kinesis API endpoint. In the following order the plugin +will attempt to authenticate. +1. [IAMS Role](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html) +2. [Environment Variables](https://github.com/aws/aws-sdk-go/wiki/configuring-sdk) +3. [Shared Credentials](https://github.com/aws/aws-sdk-go/wiki/configuring-sdk) + +## Config + +For this output plugin to function correctly the following variables must be configured. + +* region +* namespace + +### region + +The region is the Amazon region that you wish to connect to. Examples include but are not limited to +* us-west-1 +* us-west-2 +* us-east-1 +* ap-southeast-1 +* ap-southeast-2 + +### namespace + +The namespace used for AWS CloudWatch metrics. diff --git a/plugins/outputs/cloudwatch/cloudwatch.go b/plugins/outputs/cloudwatch/cloudwatch.go new file mode 100644 index 0000000000000..63d7a821c98d1 --- /dev/null +++ b/plugins/outputs/cloudwatch/cloudwatch.go @@ -0,0 +1,188 @@ +package cloudwatch + +import ( + "errors" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" + "github.com/aws/aws-sdk-go/aws/ec2metadata" + "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/aws/aws-sdk-go/aws/session" + + "github.com/influxdb/influxdb/client/v2" + "github.com/influxdb/telegraf/plugins/outputs" + + "github.com/meirf/gopart" +) + +type CloudWatchOutput struct { + Region string // AWS Region + Namespace string // CloudWatch Metrics Namespace + svc *cloudwatch.CloudWatch +} + +var sampleConfig = ` + # Amazon REGION + region = 'us-east-1' + + # Namespace for the CloudWatch MetricDatums + namespace = 'InfluxData/Telegraf' +` + +func (c *CloudWatchOutput) SampleConfig() string { + return sampleConfig +} + +func (c *CloudWatchOutput) Description() string { + return "Configuration for AWS CloudWatch output." +} + +func (c *CloudWatchOutput) Connect() error { + Config := &aws.Config{ + Region: aws.String(c.Region), + Credentials: credentials.NewChainCredentials( + []credentials.Provider{ + &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(session.New())}, + &credentials.EnvProvider{}, + &credentials.SharedCredentialsProvider{}, + }), + } + + svc := cloudwatch.New(session.New(Config)) + + params := &cloudwatch.ListMetricsInput{ + Namespace: aws.String(c.Namespace), + } + + _, err := svc.ListMetrics(params) // Try a read-only call to test connection. + + if err != nil { + log.Printf("cloudwatch: Error in ListMetrics API call : %+v \n", err.Error()) + } + + c.svc = svc + + return err +} + +func (c *CloudWatchOutput) Close() error { + return errors.New("Error") +} + +func (c *CloudWatchOutput) Write(points []*client.Point) error { + for _, pt := range points { + err := c.WriteSinglePoint(pt) + if err != nil { + return err + } + } + + return nil +} + +// Write data for a single point. A point can have many fields and one field +// is equal to one MetricDatum. There is a limit on how many MetricDatums a +// request can have so we process one Point at a time. +func (c *CloudWatchOutput) WriteSinglePoint(point *client.Point) error { + datums := buildMetricDatum(point) + + const maxDatumsPerCall = 20 // PutMetricData only supports up to 20 data points per call + + for idxRange := range gopart.Partition(len(datums), maxDatumsPerCall) { + err := c.WriteToCloudWatch(datums[idxRange.Low:idxRange.High]) + + if err != nil { + return err + } + } + + return nil +} + +func (c *CloudWatchOutput) WriteToCloudWatch(datums []*cloudwatch.MetricDatum) error { + params := &cloudwatch.PutMetricDataInput{ + MetricData: datums, + Namespace: aws.String(c.Namespace), + } + + _, err := c.svc.PutMetricData(params) + + if err != nil { + log.Printf("CloudWatch: Unable to write to CloudWatch : %+v \n", err.Error()) + } + + return err +} + +// Make a MetricDatum for each field in a Point. Only fields with values that can be +// converted to float64 are supported. Non-supported fields are skipped. +func buildMetricDatum(point *client.Point) []*cloudwatch.MetricDatum { + datums := make([]*cloudwatch.MetricDatum, len(point.Fields())) + i := 0 + + var value float64 + + for k, v := range point.Fields() { + switch t := v.(type) { + case int: + value = float64(t) + case int32: + value = float64(t) + case int64: + value = float64(t) + case float64: + value = t + case bool: + if t { + value = 1 + } else { + value = 0 + } + case time.Time: + value = float64(t.Unix()) + default: + // Skip unsupported type. + datums = datums[:len(datums)-1] + continue + } + + datums[i] = &cloudwatch.MetricDatum{ + MetricName: aws.String(strings.Join([]string{point.Name(), k}, "_")), + Value: aws.Float64(value), + Dimensions: buildDimensions(point.Tags()), + Timestamp: aws.Time(point.Time()), + } + + i += 1 + } + + return datums +} + +// Make a list of Dimensions by using a Point's tags. +func buildDimensions(ptTags map[string]string) []*cloudwatch.Dimension { + + dimensions := make([]*cloudwatch.Dimension, len(ptTags)) + i := 0 + + for k, v := range ptTags { + dimensions[i] = &cloudwatch.Dimension{ + Name: aws.String(k), + Value: aws.String(v), + } + + i += 1 + } + + return dimensions +} + +func init() { + outputs.Add("cloudwatch", func() outputs.Output { + return &CloudWatchOutput{} + }) +} From 262abb4d25b69096e366c98928d1ff1b0773f65a Mon Sep 17 00:00:00 2001 From: Stephen Kwong Date: Tue, 19 Jan 2016 14:32:27 -0800 Subject: [PATCH 2/7] Fix typo in README and format in cloudwatch output --- plugins/outputs/all/all.go | 2 +- plugins/outputs/cloudwatch/README.md | 2 +- plugins/outputs/cloudwatch/cloudwatch.go | 36 ++++++++++++------------ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/plugins/outputs/all/all.go b/plugins/outputs/all/all.go index 85d5e6cad02fc..82af83dabfca0 100644 --- a/plugins/outputs/all/all.go +++ b/plugins/outputs/all/all.go @@ -3,7 +3,7 @@ package all import ( _ "github.com/influxdb/telegraf/plugins/outputs/amon" _ "github.com/influxdb/telegraf/plugins/outputs/amqp" - _ "github.com/influxdb/telegraf/plugins/outputs/cloudwatch" + _ "github.com/influxdb/telegraf/plugins/outputs/cloudwatch" _ "github.com/influxdb/telegraf/plugins/outputs/datadog" _ "github.com/influxdb/telegraf/plugins/outputs/graphite" _ "github.com/influxdb/telegraf/plugins/outputs/influxdb" diff --git a/plugins/outputs/cloudwatch/README.md b/plugins/outputs/cloudwatch/README.md index 5b29a1bbbded5..dc1b13a95170b 100644 --- a/plugins/outputs/cloudwatch/README.md +++ b/plugins/outputs/cloudwatch/README.md @@ -4,7 +4,7 @@ This plugin will send points to Amazon CloudWatch. ## Amazon Authentication -This plugin uses a credential chain for Authentication with the Kinesis API endpoint. In the following order the plugin +This plugin uses a credential chain for Authentication with the CloudWatch API endpoint. In the following order the plugin will attempt to authenticate. 1. [IAMS Role](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html) 2. [Environment Variables](https://github.com/aws/aws-sdk-go/wiki/configuring-sdk) diff --git a/plugins/outputs/cloudwatch/cloudwatch.go b/plugins/outputs/cloudwatch/cloudwatch.go index 63d7a821c98d1..447a40bcf83e1 100644 --- a/plugins/outputs/cloudwatch/cloudwatch.go +++ b/plugins/outputs/cloudwatch/cloudwatch.go @@ -10,8 +10,8 @@ import ( "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" "github.com/aws/aws-sdk-go/aws/ec2metadata" - "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/influxdb/influxdb/client/v2" "github.com/influxdb/telegraf/plugins/outputs" @@ -20,12 +20,12 @@ import ( ) type CloudWatchOutput struct { - Region string // AWS Region - Namespace string // CloudWatch Metrics Namespace - svc *cloudwatch.CloudWatch + Region string // AWS Region + Namespace string // CloudWatch Metrics Namespace + svc *cloudwatch.CloudWatch } -var sampleConfig = ` +var sampleConfig = ` # Amazon REGION region = 'us-east-1' @@ -43,13 +43,13 @@ func (c *CloudWatchOutput) Description() string { func (c *CloudWatchOutput) Connect() error { Config := &aws.Config{ - Region: aws.String(c.Region), - Credentials: credentials.NewChainCredentials( - []credentials.Provider{ - &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(session.New())}, - &credentials.EnvProvider{}, - &credentials.SharedCredentialsProvider{}, - }), + Region: aws.String(c.Region), + Credentials: credentials.NewChainCredentials( + []credentials.Provider{ + &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(session.New())}, + &credentials.EnvProvider{}, + &credentials.SharedCredentialsProvider{}, + }), } svc := cloudwatch.New(session.New(Config)) @@ -58,7 +58,7 @@ func (c *CloudWatchOutput) Connect() error { Namespace: aws.String(c.Namespace), } - _, err := svc.ListMetrics(params) // Try a read-only call to test connection. + _, err := svc.ListMetrics(params) // Try a read-only call to test connection. if err != nil { log.Printf("cloudwatch: Error in ListMetrics API call : %+v \n", err.Error()) @@ -90,7 +90,7 @@ func (c *CloudWatchOutput) Write(points []*client.Point) error { func (c *CloudWatchOutput) WriteSinglePoint(point *client.Point) error { datums := buildMetricDatum(point) - const maxDatumsPerCall = 20 // PutMetricData only supports up to 20 data points per call + const maxDatumsPerCall = 20 // PutMetricData only supports up to 20 data points per call for idxRange := range gopart.Partition(len(datums), maxDatumsPerCall) { err := c.WriteToCloudWatch(datums[idxRange.Low:idxRange.High]) @@ -106,7 +106,7 @@ func (c *CloudWatchOutput) WriteSinglePoint(point *client.Point) error { func (c *CloudWatchOutput) WriteToCloudWatch(datums []*cloudwatch.MetricDatum) error { params := &cloudwatch.PutMetricDataInput{ MetricData: datums, - Namespace: aws.String(c.Namespace), + Namespace: aws.String(c.Namespace), } _, err := c.svc.PutMetricData(params) @@ -152,9 +152,9 @@ func buildMetricDatum(point *client.Point) []*cloudwatch.MetricDatum { datums[i] = &cloudwatch.MetricDatum{ MetricName: aws.String(strings.Join([]string{point.Name(), k}, "_")), - Value: aws.Float64(value), + Value: aws.Float64(value), Dimensions: buildDimensions(point.Tags()), - Timestamp: aws.Time(point.Time()), + Timestamp: aws.Time(point.Time()), } i += 1 @@ -171,7 +171,7 @@ func buildDimensions(ptTags map[string]string) []*cloudwatch.Dimension { for k, v := range ptTags { dimensions[i] = &cloudwatch.Dimension{ - Name: aws.String(k), + Name: aws.String(k), Value: aws.String(v), } From 2b5ea8c17a823a94ac20add603be3055e82181c5 Mon Sep 17 00:00:00 2001 From: Stephen Kwong Date: Tue, 19 Jan 2016 15:05:54 -0800 Subject: [PATCH 3/7] Rename CloudWatchOutput to just CloudWatch. Also fix CloudWatch.Close() to return nil instead of error --- plugins/outputs/cloudwatch/cloudwatch.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/outputs/cloudwatch/cloudwatch.go b/plugins/outputs/cloudwatch/cloudwatch.go index 447a40bcf83e1..58dffb737dc8a 100644 --- a/plugins/outputs/cloudwatch/cloudwatch.go +++ b/plugins/outputs/cloudwatch/cloudwatch.go @@ -19,7 +19,7 @@ import ( "github.com/meirf/gopart" ) -type CloudWatchOutput struct { +type CloudWatch struct { Region string // AWS Region Namespace string // CloudWatch Metrics Namespace svc *cloudwatch.CloudWatch @@ -33,15 +33,15 @@ var sampleConfig = ` namespace = 'InfluxData/Telegraf' ` -func (c *CloudWatchOutput) SampleConfig() string { +func (c *CloudWatch) SampleConfig() string { return sampleConfig } -func (c *CloudWatchOutput) Description() string { +func (c *CloudWatch) Description() string { return "Configuration for AWS CloudWatch output." } -func (c *CloudWatchOutput) Connect() error { +func (c *CloudWatch) Connect() error { Config := &aws.Config{ Region: aws.String(c.Region), Credentials: credentials.NewChainCredentials( @@ -69,11 +69,11 @@ func (c *CloudWatchOutput) Connect() error { return err } -func (c *CloudWatchOutput) Close() error { - return errors.New("Error") +func (c *CloudWatch) Close() error { + return nil } -func (c *CloudWatchOutput) Write(points []*client.Point) error { +func (c *CloudWatch) Write(points []*client.Point) error { for _, pt := range points { err := c.WriteSinglePoint(pt) if err != nil { @@ -87,7 +87,7 @@ func (c *CloudWatchOutput) Write(points []*client.Point) error { // Write data for a single point. A point can have many fields and one field // is equal to one MetricDatum. There is a limit on how many MetricDatums a // request can have so we process one Point at a time. -func (c *CloudWatchOutput) WriteSinglePoint(point *client.Point) error { +func (c *CloudWatch) WriteSinglePoint(point *client.Point) error { datums := buildMetricDatum(point) const maxDatumsPerCall = 20 // PutMetricData only supports up to 20 data points per call @@ -103,7 +103,7 @@ func (c *CloudWatchOutput) WriteSinglePoint(point *client.Point) error { return nil } -func (c *CloudWatchOutput) WriteToCloudWatch(datums []*cloudwatch.MetricDatum) error { +func (c *CloudWatch) WriteToCloudWatch(datums []*cloudwatch.MetricDatum) error { params := &cloudwatch.PutMetricDataInput{ MetricData: datums, Namespace: aws.String(c.Namespace), @@ -183,6 +183,6 @@ func buildDimensions(ptTags map[string]string) []*cloudwatch.Dimension { func init() { outputs.Add("cloudwatch", func() outputs.Output { - return &CloudWatchOutput{} + return &CloudWatch{} }) } From 9e34d03018ca7d29c0c97b984e3ac70d48f47cce Mon Sep 17 00:00:00 2001 From: Stephen Kwong Date: Tue, 19 Jan 2016 15:10:35 -0800 Subject: [PATCH 4/7] Removed unused import --- plugins/outputs/cloudwatch/cloudwatch.go | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/outputs/cloudwatch/cloudwatch.go b/plugins/outputs/cloudwatch/cloudwatch.go index 58dffb737dc8a..589b5ddd4d23e 100644 --- a/plugins/outputs/cloudwatch/cloudwatch.go +++ b/plugins/outputs/cloudwatch/cloudwatch.go @@ -1,7 +1,6 @@ package cloudwatch import ( - "errors" "log" "strings" "time" From 881af1f07868238421dc73504447ad34c4c26646 Mon Sep 17 00:00:00 2001 From: Stephen Kwong Date: Tue, 19 Jan 2016 16:34:17 -0800 Subject: [PATCH 5/7] Limit number of CloudWatch Dimensions to 10. --- plugins/outputs/cloudwatch/cloudwatch.go | 36 ++++++++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/plugins/outputs/cloudwatch/cloudwatch.go b/plugins/outputs/cloudwatch/cloudwatch.go index 589b5ddd4d23e..cb8375d091c08 100644 --- a/plugins/outputs/cloudwatch/cloudwatch.go +++ b/plugins/outputs/cloudwatch/cloudwatch.go @@ -2,6 +2,8 @@ package cloudwatch import ( "log" + "math" + "sort" "strings" "time" @@ -162,21 +164,43 @@ func buildMetricDatum(point *client.Point) []*cloudwatch.MetricDatum { return datums } -// Make a list of Dimensions by using a Point's tags. +// Make a list of Dimensions by using a Point's tags. CloudWatch supports up to +// 10 dimensions per metric so we only keep up to the first 10 alphabetically. +// This always includes the "host" tag if it exists. func buildDimensions(ptTags map[string]string) []*cloudwatch.Dimension { - dimensions := make([]*cloudwatch.Dimension, len(ptTags)) + const MaxDimensions = 10 + dimensions := make([]*cloudwatch.Dimension, int(math.Min(float64(len(ptTags)), MaxDimensions))) + i := 0 - for k, v := range ptTags { + // This is pretty ugly but we always want to include the "host" tag if it exists. + if host, ok := ptTags["host"]; ok { dimensions[i] = &cloudwatch.Dimension{ - Name: aws.String(k), - Value: aws.String(v), + Name: aws.String("host"), + Value: aws.String(host), } - + delete(ptTags, "host") i += 1 } + var keys []string + for k := range ptTags { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, k := range keys { + if i <= MaxDimensions { + break + } + + dimensions[i] = &cloudwatch.Dimension{ + Name: aws.String(k), + Value: aws.String(ptTags[k]), + } + } + return dimensions } From 02ee480396bd9d3af86ee64b73e6415415ad5716 Mon Sep 17 00:00:00 2001 From: Stephen Kwong Date: Wed, 20 Jan 2016 17:09:46 -0800 Subject: [PATCH 6/7] Use own partitioning and add unit tests --- plugins/outputs/cloudwatch/cloudwatch.go | 47 +++++++--- plugins/outputs/cloudwatch/cloudwatch_test.go | 88 +++++++++++++++++++ 2 files changed, 124 insertions(+), 11 deletions(-) create mode 100644 plugins/outputs/cloudwatch/cloudwatch_test.go diff --git a/plugins/outputs/cloudwatch/cloudwatch.go b/plugins/outputs/cloudwatch/cloudwatch.go index cb8375d091c08..9c72af7db538b 100644 --- a/plugins/outputs/cloudwatch/cloudwatch.go +++ b/plugins/outputs/cloudwatch/cloudwatch.go @@ -16,8 +16,6 @@ import ( "github.com/influxdb/influxdb/client/v2" "github.com/influxdb/telegraf/plugins/outputs" - - "github.com/meirf/gopart" ) type CloudWatch struct { @@ -89,12 +87,12 @@ func (c *CloudWatch) Write(points []*client.Point) error { // is equal to one MetricDatum. There is a limit on how many MetricDatums a // request can have so we process one Point at a time. func (c *CloudWatch) WriteSinglePoint(point *client.Point) error { - datums := buildMetricDatum(point) + datums := BuildMetricDatum(point) const maxDatumsPerCall = 20 // PutMetricData only supports up to 20 data points per call - for idxRange := range gopart.Partition(len(datums), maxDatumsPerCall) { - err := c.WriteToCloudWatch(datums[idxRange.Low:idxRange.High]) + for _, partition := range PartitionDatums(maxDatumsPerCall, datums) { + err := c.WriteToCloudWatch(partition) if err != nil { return err @@ -119,9 +117,33 @@ func (c *CloudWatch) WriteToCloudWatch(datums []*cloudwatch.MetricDatum) error { return err } +// Partition the MetricDatums into smaller slices of a max size so that are under the limit +// for the AWS API calls. +func PartitionDatums(size int, datums []*cloudwatch.MetricDatum) [][]*cloudwatch.MetricDatum { + + numberOfPartitions := len(datums) / size + if len(datums)%size != 0 { + numberOfPartitions += 1 + } + + partitions := make([][]*cloudwatch.MetricDatum, numberOfPartitions) + + for i := 0; i < numberOfPartitions; i++ { + start := size * i + end := size * (i + 1) + if end > len(datums) { + end = len(datums) + } + + partitions[i] = datums[start:end] + } + + return partitions +} + // Make a MetricDatum for each field in a Point. Only fields with values that can be // converted to float64 are supported. Non-supported fields are skipped. -func buildMetricDatum(point *client.Point) []*cloudwatch.MetricDatum { +func BuildMetricDatum(point *client.Point) []*cloudwatch.MetricDatum { datums := make([]*cloudwatch.MetricDatum, len(point.Fields())) i := 0 @@ -154,7 +176,7 @@ func buildMetricDatum(point *client.Point) []*cloudwatch.MetricDatum { datums[i] = &cloudwatch.MetricDatum{ MetricName: aws.String(strings.Join([]string{point.Name(), k}, "_")), Value: aws.Float64(value), - Dimensions: buildDimensions(point.Tags()), + Dimensions: BuildDimensions(point.Tags()), Timestamp: aws.Time(point.Time()), } @@ -167,7 +189,7 @@ func buildMetricDatum(point *client.Point) []*cloudwatch.MetricDatum { // Make a list of Dimensions by using a Point's tags. CloudWatch supports up to // 10 dimensions per metric so we only keep up to the first 10 alphabetically. // This always includes the "host" tag if it exists. -func buildDimensions(ptTags map[string]string) []*cloudwatch.Dimension { +func BuildDimensions(ptTags map[string]string) []*cloudwatch.Dimension { const MaxDimensions = 10 dimensions := make([]*cloudwatch.Dimension, int(math.Min(float64(len(ptTags)), MaxDimensions))) @@ -180,18 +202,19 @@ func buildDimensions(ptTags map[string]string) []*cloudwatch.Dimension { Name: aws.String("host"), Value: aws.String(host), } - delete(ptTags, "host") i += 1 } var keys []string for k := range ptTags { - keys = append(keys, k) + if k != "host" { + keys = append(keys, k) + } } sort.Strings(keys) for _, k := range keys { - if i <= MaxDimensions { + if i >= MaxDimensions { break } @@ -199,6 +222,8 @@ func buildDimensions(ptTags map[string]string) []*cloudwatch.Dimension { Name: aws.String(k), Value: aws.String(ptTags[k]), } + + i += 1 } return dimensions diff --git a/plugins/outputs/cloudwatch/cloudwatch_test.go b/plugins/outputs/cloudwatch/cloudwatch_test.go new file mode 100644 index 0000000000000..3d1f5c4dbb048 --- /dev/null +++ b/plugins/outputs/cloudwatch/cloudwatch_test.go @@ -0,0 +1,88 @@ +package cloudwatch + +import ( + "sort" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudwatch" + + "github.com/influxdata/telegraf/testutil" + "github.com/influxdb/influxdb/client/v2" + + "github.com/stretchr/testify/assert" +) + +// Test that each tag becomes one dimension +func TestBuildDimensions(t *testing.T) { + const MaxDimensions = 10 + + assert := assert.New(t) + + testPoint := testutil.TestPoint(1) + dimensions := BuildDimensions(testPoint.Tags()) + + tagKeys := make([]string, len(testPoint.Tags())) + i := 0 + for k, _ := range testPoint.Tags() { + tagKeys[i] = k + i += 1 + } + + sort.Strings(tagKeys) + + if len(testPoint.Tags()) >= MaxDimensions { + assert.Equal(MaxDimensions, len(dimensions), "Number of dimensions should be less than MaxDimensions") + } else { + assert.Equal(len(testPoint.Tags()), len(dimensions), "Number of dimensions should be equal to number of tags") + } + + for i, key := range tagKeys { + if i >= 10 { + break + } + assert.Equal(key, *dimensions[i].Name, "Key should be equal") + assert.Equal(testPoint.Tags()[key], *dimensions[i].Value, "Value should be equal") + } +} + +// Test that points with valid values have a MetricDatum created where as non valid do not. +// Skips "time.Time" type as something is converting the value to string. +func TestBuildMetricDatums(t *testing.T) { + assert := assert.New(t) + + validPoints := []*client.Point{ + testutil.TestPoint(1), + testutil.TestPoint(int32(1)), + testutil.TestPoint(int64(1)), + testutil.TestPoint(float64(1)), + testutil.TestPoint(true), + } + + for _, point := range validPoints { + datums := BuildMetricDatum(point) + assert.Equal(1, len(datums), "Valid type should create a Datum") + } + + nonValidPoint := testutil.TestPoint("Foo") + + assert.Equal(0, len(BuildMetricDatum(nonValidPoint)), "Invalid type should not create a Datum") +} + +func TestPartitionDatums(t *testing.T) { + + assert := assert.New(t) + + testDatum := cloudwatch.MetricDatum{ + MetricName: aws.String("Foo"), + Value: aws.Float64(1), + } + + oneDatum := []*cloudwatch.MetricDatum{&testDatum} + twoDatum := []*cloudwatch.MetricDatum{&testDatum, &testDatum} + threeDatum := []*cloudwatch.MetricDatum{&testDatum, &testDatum, &testDatum} + + assert.Equal([][]*cloudwatch.MetricDatum{oneDatum}, PartitionDatums(2, oneDatum)) + assert.Equal([][]*cloudwatch.MetricDatum{twoDatum}, PartitionDatums(2, twoDatum)) + assert.Equal([][]*cloudwatch.MetricDatum{twoDatum, oneDatum}, PartitionDatums(2, threeDatum)) +} From 6d2b4e3b70ab1bfc9aa84c5b176404d27e4f5742 Mon Sep 17 00:00:00 2001 From: Stephen Kwong Date: Wed, 20 Jan 2016 17:24:28 -0800 Subject: [PATCH 7/7] Fix import for unit tests --- plugins/outputs/cloudwatch/cloudwatch_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/outputs/cloudwatch/cloudwatch_test.go b/plugins/outputs/cloudwatch/cloudwatch_test.go index 3d1f5c4dbb048..e37c2ae9ad9ec 100644 --- a/plugins/outputs/cloudwatch/cloudwatch_test.go +++ b/plugins/outputs/cloudwatch/cloudwatch_test.go @@ -7,8 +7,8 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudwatch" - "github.com/influxdata/telegraf/testutil" "github.com/influxdb/influxdb/client/v2" + "github.com/influxdb/telegraf/testutil" "github.com/stretchr/testify/assert" )