From bd4843d5f29e032b3447f47cc403b38f2f1a68c6 Mon Sep 17 00:00:00 2001 From: Vebjorn Ljosa Date: Tue, 29 Mar 2016 10:58:33 -0400 Subject: [PATCH] Cloudwatch input plugin --- Godeps | 5 + plugins/inputs/all/all.go | 1 + plugins/inputs/cloudwatch/README.md | 85 +++++++ plugins/inputs/cloudwatch/cloudwatch.go | 164 +++++++++++++ plugins/inputs/cloudwatch/cloudwatch_test.go | 237 +++++++++++++++++++ 5 files changed, 492 insertions(+) create mode 100644 plugins/inputs/cloudwatch/README.md create mode 100644 plugins/inputs/cloudwatch/cloudwatch.go create mode 100644 plugins/inputs/cloudwatch/cloudwatch_test.go diff --git a/Godeps b/Godeps index 75cb813ba..7ecef36e2 100644 --- a/Godeps +++ b/Godeps @@ -3,6 +3,7 @@ github.com/Shopify/sarama 8aadb476e66ca998f2f6bb3c993e9a2daa3666b9 github.com/Sirupsen/logrus 219c8cb75c258c552e999735be6df753ffc7afdc github.com/amir/raidman 53c1b967405155bfc8758557863bf2e14f814687 github.com/aws/aws-sdk-go 13a12060f716145019378a10e2806c174356b857 +github.com/azer/snakecase 7a41237eda4ca971bdcbba00d459901be8221a19 github.com/beorn7/perks 3ac7bf7a47d159a033b107610db8a1b6575507a4 github.com/cenkalti/backoff 4dc77674aceaabba2c7e3da25d4c823edfb73f99 github.com/couchbase/go-couchbase cb664315a324d87d19c879d9cc67fda6be8c2ac1 @@ -13,6 +14,7 @@ github.com/davecgh/go-spew 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d github.com/eapache/go-resiliency b86b1ec0dd4209a588dc1285cdd471e73525c0b3 github.com/eapache/queue ded5959c0d4e360646dc9e9908cff48666781367 github.com/fsouza/go-dockerclient a49c8269a6899cae30da1f8a4b82e0ce945f9967 +github.com/go-ini/ini 776aa739ce9373377cd16f526cdf06cb4c89b40f github.com/go-sql-driver/mysql 1fca743146605a172a266e1654e01e5cd5669bee github.com/golang/protobuf 552c7b9542c194800fd493123b3798ef0a832032 github.com/golang/snappy 427fb6fc07997f43afa32f35e850833760e489a7 @@ -23,6 +25,7 @@ github.com/hailocab/go-hostpool e80d13ce29ede4452c43dea11e79b9bc8a15b478 github.com/influxdata/config b79f6829346b8d6e78ba73544b1e1038f1f1c9da github.com/influxdata/influxdb e3fef5593c21644f2b43af55d6e17e70910b0e48 github.com/influxdata/toml af4df43894b16e3fd2b788d01bd27ad0776ef2d0 +github.com/jmespath/go-jmespath 0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74 github.com/klauspost/crc32 19b0b332c9e4516a6370a0456e6182c3b5036720 github.com/lib/pq e182dc4027e2ded4b19396d638610f2653295f36 github.com/matttproud/golang_protobuf_extensions d0c3fe89de86839aecf2e0579c40ba3bb336a453 @@ -32,6 +35,7 @@ github.com/naoina/go-stringutil 6b638e95a32d0c1131db0e7fe83775cbea4a0d0b github.com/nats-io/nats b13fc9d12b0b123ebc374e6b808c6228ae4234a3 github.com/nats-io/nuid 4f84f5f3b2786224e336af2e13dba0a0a80b76fa github.com/nsqio/go-nsq 0b80d6f05e15ca1930e0c5e1d540ed627e299980 +github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2 github.com/prometheus/client_golang 18acf9993a863f4c4b40612e19cdd243e7c86831 github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6 github.com/prometheus/common e8eabff8812b05acf522b45fdcd725a785188e37 @@ -40,6 +44,7 @@ github.com/samuel/go-zookeeper 218e9c81c0dd8b3b18172b2bbfad92cc7d6db55f github.com/shirou/gopsutil 1f32ce1bb380845be7f5d174ac641a2c592c0c42 github.com/soniah/gosnmp b1b4f885b12c5dcbd021c5cee1c904110de6db7d github.com/streadway/amqp b4f3ceab0337f013208d31348b578d83c0064744 +github.com/stretchr/objx 1a9d0bb9f541897e62256577b352fdbc1fb4fd94 github.com/stretchr/testify 1f4a1643a57e798696635ea4c126e9127adb7d3c github.com/wvanbergen/kafka 1a8639a45164fcc245d5c7b4bd3ccfbd1a0ffbf3 github.com/wvanbergen/kazoo-go 0f768712ae6f76454f987c3356177e138df258f8 diff --git a/plugins/inputs/all/all.go b/plugins/inputs/all/all.go index 55a932df2..d403708af 100644 --- a/plugins/inputs/all/all.go +++ b/plugins/inputs/all/all.go @@ -4,6 +4,7 @@ import ( _ "github.com/influxdata/telegraf/plugins/inputs/aerospike" _ "github.com/influxdata/telegraf/plugins/inputs/apache" _ "github.com/influxdata/telegraf/plugins/inputs/bcache" + _ "github.com/influxdata/telegraf/plugins/inputs/cloudwatch" _ "github.com/influxdata/telegraf/plugins/inputs/couchbase" _ "github.com/influxdata/telegraf/plugins/inputs/couchdb" _ "github.com/influxdata/telegraf/plugins/inputs/disque" diff --git a/plugins/inputs/cloudwatch/README.md b/plugins/inputs/cloudwatch/README.md new file mode 100644 index 000000000..0abdf41ff --- /dev/null +++ b/plugins/inputs/cloudwatch/README.md @@ -0,0 +1,85 @@ +# Telegraf Plugin: Cloudwatch + +## Configuration: + +``` +# Read metrics from Cloudwatch +[[inputs.cloudwatch]] + ## AWS region + region = "us-east-1" + ## specify namespaces as strings + namespaces = ["AWS/EC2", "AWS/DynamoDB"] +``` + +In each iteration, the cloudwatch input plugin queries Cloudwatch for +datapoints that are timestamped since the last iteration. (The first +iteration does not retrieve any data.) + +Some AWS services post timepoints late, in the sense that new +timepoints are timestamped 2–3 minutes in the past. These points will +be missed by the cloudwatch input plugin if you run telegraf with a +short `interval`. Running telegraf with a longer interval, such as +`5m`, is recommended. + + +## Measurements: + +Each namespace becomes a measurement, converted to snake_case and +prefixed with `cloudwatch`. For instance, the namespace `AWS/EC2` +becomes the measurement `cloudwatch_aws_ec2`, and the namespace +`AWS/DynamoDB` becomes the measurement `cloudwatch_aws_dynamo_db`. + +### Fields + +The cloudwatch input plugin retrieves all statistics from all +measurements in the specified namespaces. Each combination of metric +and statistic becomes a field. Metric names are converted to +snake_case. As an example, the metric `DiskWriteBytes` leads to the fields + +* `disk_write_bytes_average`, +* `disk_write_bytes_maximum`, +* `disk_write_bytes_minimum`, +* `disk_write_bytes_sample_count`, and +* `disk_write_bytes_sum`. + +To learn about the metrics for a particular Cloudwatch namespace, and +to see units and example data, see the AWS documentation for the +service in question, or explore using the AWS Console or AWS CLI. + +### Tags + +The cloudwatch input plugin tags the metrics it collects with the AWS +region (key: `region`) and the cloudwatch dimensions of the metric in +question. The dimensions names are converted to snake_keys, but the +values are left as is. As an example, the `AWS/DynamoDB` namespace has +a metric called `ThrottledRequest` with the dimensions `Operation` and +`TableName`. An example set of tags for this metric could be: + +* `region`: `us-east-1` +* `operation`: `GetItem` +* `table_name`: `foo` + +## Example output + +``` +$ ~/src/go/bin/telegraf -config ~/tmp/kapacitor/telegraf.conf -input-filter cloudwatch -debug +2016/03/29 10:47:15 Attempting connection to output: influxdb +2016/03/29 10:47:16 Successfully connected to output: influxdb +2016/03/29 10:47:16 Starting Telegraf (version 0.11.1-54-ge07c792) +2016/03/29 10:47:16 Loaded outputs: influxdb +2016/03/29 10:47:16 Loaded inputs: cloudwatch +2016/03/29 10:47:16 Tags enabled: host=vljosa-yieldbot.local +2016/03/29 10:47:16 Agent Config: Interval:5m0s, Debug:true, Quiet:false, Hostname:"vljosa-yieldbot.local", Flush Interval:11.228634593s +2016/03/29 10:50:00 Found 26 cloudwatch metrics +2016/03/29 10:50:00 Gathered metrics, (5m0s interval), from 1 inputs in 709.03259ms +> cloudwatch_aws_dynamo_db,host=vljosa-yieldbot.local,table_name=prd-platform-datomic-config consumed_write_capacity_units_average=1,consumed_write_capacity_units_maximum=1,consumed_write_capacity_units_minimum=1,consumed_write_capacity_units_sample_count=65,consumed_write_capacity_units_sum=65 1459263300548895733 +> cloudwatch_aws_dynamo_db,host=vljosa-yieldbot.local,table_name=dev-platform-datomic-config consumed_read_capacity_units_average=1,consumed_read_capacity_units_maximum=1,consumed_read_capacity_units_minimum=1,consumed_read_capacity_units_sample_count=48,consumed_read_capacity_units_sum=48 1459263300642785107 +> cloudwatch_aws_dynamo_db,host=vljosa-yieldbot.local,operation=PutItem,table_name=dev-platform-datomic-config successful_request_latency_average=8.910156727272726,successful_request_latency_maximum=26.477,successful_request_latency_minimum=4.243,successful_request_latency_sample_count=44,successful_request_latency_sum=392.04689599999995 1459263300688946255 +> cloudwatch_aws_dynamo_db,host=vljosa-yieldbot.local,operation=GetItem,table_name=dev-platform-datomic-config successful_request_latency_average=6.982394130434781,successful_request_latency_maximum=38.125,successful_request_latency_minimum=2.156,successful_request_latency_sample_count=46,successful_request_latency_sum=321.19012999999995 1459263300727174463 +> cloudwatch_aws_dynamo_db,host=vljosa-yieldbot.local,table_name=dev-platform-datomic-config consumed_write_capacity_units_average=1,consumed_write_capacity_units_maximum=1,consumed_write_capacity_units_minimum=1,consumed_write_capacity_units_sample_count=48,consumed_write_capacity_units_sum=48 1459263300760742003 +> cloudwatch_aws_dynamo_db,host=vljosa-yieldbot.local,table_name=prd-platform-datomic-config consumed_read_capacity_units_average=1.8539288112827401,consumed_read_capacity_units_maximum=8,consumed_read_capacity_units_minimum=0.5,consumed_read_capacity_units_sample_count=1489,consumed_read_capacity_units_sum=2760.5 1459263300832984286 +> cloudwatch_aws_dynamo_db,host=vljosa-yieldbot.local,operation=GetItem,table_name=prd-platform-datomic-config successful_request_latency_average=5.014870193569996,successful_request_latency_maximum=56.839959,successful_request_latency_minimum=2.030115,successful_request_latency_sample_count=1493,successful_request_latency_sum=7487.201199000004 1459263300868485699 +> cloudwatch_aws_dynamo_db,host=vljosa-yieldbot.local,operation=PutItem,table_name=prd-platform-datomic-config successful_request_latency_average=9.664095158730158,successful_request_latency_maximum=25.262,successful_request_latency_minimum=3.533,successful_request_latency_sample_count=63,successful_request_latency_sum=608.837995 1459263300990375223 +2016/03/29 10:55:01 Gathered metrics, (5m0s interval), from 1 inputs in 1.048629086s +2016/03/29 10:55:03 Wrote 8 metrics to output influxdb in 90.0269ms +``` diff --git a/plugins/inputs/cloudwatch/cloudwatch.go b/plugins/inputs/cloudwatch/cloudwatch.go new file mode 100644 index 000000000..f7900de40 --- /dev/null +++ b/plugins/inputs/cloudwatch/cloudwatch.go @@ -0,0 +1,164 @@ +package cloudwatch + +import ( + "errors" + "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/azer/snakecase" + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/inputs" + "log" + "strings" + "time" +) + +type Cloudwatch struct { + Region string + Namespaces []string + + // List of metrics to gather statistics for. Computed the first time Gather() is called. + metrics []*cloudwatch.Metric + // Get metrics statistics since last time Gather() was called. + lastTime *time.Time +} + +var sampleConfig = ` + ## AWS region + region = "us-east-1" + ## specify namespaces as strings + namespaces = ["AWS/EC2", "AWS/DynamoDB"] +` + +func (r *Cloudwatch) SampleConfig() string { + return sampleConfig +} + +func (r *Cloudwatch) Description() string { + return "Read metrics from AWS CloudWatch" +} + +// Reads stats from AWS CloudWatch. Accumulates stats. +// Returns one of the errors encountered while gathering stats (if any). +func (r *Cloudwatch) Gather(acc telegraf.Accumulator) error { + svc := cloudwatch.New(session.New(), &aws.Config{Region: aws.String(r.Region)}) + var outerr error + now := time.Now() + if r.lastTime == nil { + r.metrics, outerr = listMetrics(svc, r.Namespaces) + log.Printf("Found %d cloudwatch metrics", len(r.metrics)) + } else { + for i := 0; i < len(r.metrics); i++ { + metric := r.metrics[i] + minutes := int64(now.Sub(*r.lastTime) / time.Minute) + if minutes == 0 { + minutes = 1 + } + period := minutes * 60 + outerr = gatherMetric(svc, r.Region, acc, metric, *r.lastTime, now, period) + } + } + r.lastTime = &now + return outerr +} + +type listMetricsAPI interface { + ListMetrics(*cloudwatch.ListMetricsInput) (*cloudwatch.ListMetricsOutput, error) +} + +func listMetrics(svc listMetricsAPI, namespaces []string) ([]*cloudwatch.Metric, error) { + metrics := []*cloudwatch.Metric{} + for i := 0; i < len(namespaces); i++ { + namespace := namespaces[i] + params := &cloudwatch.ListMetricsInput{ + Dimensions: []*cloudwatch.DimensionFilter{}, + MetricName: nil, + Namespace: aws.String(namespace), + NextToken: nil, + } + resp, err := svc.ListMetrics(params) + if err != nil { + return []*cloudwatch.Metric{}, err + } + metrics = append(metrics, resp.Metrics...) + } + return metrics, nil + +} + +type getMetricStatisticsAPI interface { + GetMetricStatistics(*cloudwatch.GetMetricStatisticsInput) (*cloudwatch.GetMetricStatisticsOutput, error) +} + +func gatherMetric(svc getMetricStatisticsAPI, region string, acc telegraf.Accumulator, metric *cloudwatch.Metric, startTime time.Time, endTime time.Time, period int64) error { + r, err := svc.GetMetricStatistics(&cloudwatch.GetMetricStatisticsInput{ + Namespace: metric.Namespace, + MetricName: metric.MetricName, + Dimensions: metric.Dimensions, + Statistics: []*string{ + aws.String("Average"), + aws.String("Maximum"), + aws.String("Minimum"), + aws.String("Sum"), + aws.String("SampleCount")}, + StartTime: &startTime, + EndTime: &endTime, + Period: &period, + }) + if err != nil { + return err + } + if len(r.Datapoints) == 0 { + return nil + } + if len(r.Datapoints) != 1 { + return errors.New(fmt.Sprintf("Expected one datapoint, received %d", len(r.Datapoints))) + } + dp := r.Datapoints[0] + fields := make(map[string]interface{}) + if dp.Average != nil { + fields[formatField(*metric.MetricName, "average")] = *dp.Average + } + if dp.Maximum != nil { + fields[formatField(*metric.MetricName, "maximum")] = *dp.Maximum + } + if dp.Minimum != nil { + fields[formatField(*metric.MetricName, "minimum")] = *dp.Minimum + } + if dp.Sum != nil { + fields[formatField(*metric.MetricName, "sum")] = *dp.Sum + } + if dp.SampleCount != nil { + fields[formatField(*metric.MetricName, "sample_count")] = *dp.SampleCount + } + tags := getTags(region, metric.Dimensions) + acc.AddFields(formatMeasurement(*metric.Namespace), fields, tags) + return nil +} + +func getTags(region string, dimensions []*cloudwatch.Dimension) map[string]string { + tags := make(map[string]string) + tags["region"] = region + for _, d := range dimensions { + tags[snakecase.SnakeCase(*d.Name)] = *d.Value + } + return tags +} + +func formatField(metricName string, statistic string) string { + return fmt.Sprintf("%s_%s", snakecase.SnakeCase(metricName), statistic) +} + +func formatMeasurement(namespace string) string { + ns := snakecase.SnakeCase(namespace) + ns = strings.Replace(ns, "/", "_", -1) + ns = strings.Replace(ns, "__", "_", -1) + return fmt.Sprintf("cloudwatch_%s", ns) +} + +func init() { + inputs.Add("cloudwatch", func() telegraf.Input { + return &Cloudwatch{} + }) +} diff --git a/plugins/inputs/cloudwatch/cloudwatch_test.go b/plugins/inputs/cloudwatch/cloudwatch_test.go new file mode 100644 index 000000000..301abcb48 --- /dev/null +++ b/plugins/inputs/cloudwatch/cloudwatch_test.go @@ -0,0 +1,237 @@ +package cloudwatch + +import ( + "errors" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/influxdata/telegraf/testutil" + "testing" + "time" +) + +type ListMetricsMock struct{} + +func (lmm *ListMetricsMock) ListMetrics(params *cloudwatch.ListMetricsInput) (*cloudwatch.ListMetricsOutput, error) { + switch *params.Namespace { + case "AWS/EC2": + return &cloudwatch.ListMetricsOutput{ + Metrics: []*cloudwatch.Metric{ + &cloudwatch.Metric{ + Dimensions: []*cloudwatch.Dimension{ + &cloudwatch.Dimension{ + Name: aws.String("InstanceId"), + Value: aws.String("i-a6c5c510"), + }, + }, + MetricName: aws.String("DiskReadBytes"), + Namespace: aws.String("AWS/EC2"), + }, + &cloudwatch.Metric{ + Dimensions: []*cloudwatch.Dimension{ + &cloudwatch.Dimension{ + Name: aws.String("InstanceId"), + Value: aws.String("i-10a2c094"), + }, + }, + MetricName: aws.String("DiskWriteBytes"), + Namespace: aws.String("AWS/EC2"), + }, + }, + }, nil + case "AWS/DynamoDB": + return &cloudwatch.ListMetricsOutput{ + Metrics: []*cloudwatch.Metric{ + &cloudwatch.Metric{ + Dimensions: []*cloudwatch.Dimension{ + &cloudwatch.Dimension{ + Name: aws.String("Operation"), + Value: aws.String("GetItem"), + }, + &cloudwatch.Dimension{ + Name: aws.String("TableName"), + Value: aws.String("foo"), + }, + }, + MetricName: aws.String("ThrottledRequests"), + Namespace: aws.String("AWS/DynamoDB"), + }, + &cloudwatch.Metric{ + Dimensions: []*cloudwatch.Dimension{ + &cloudwatch.Dimension{ + Name: aws.String("Operation"), + Value: aws.String("PutItem"), + }, + &cloudwatch.Dimension{ + Name: aws.String("TableName"), + Value: aws.String("foo"), + }, + }, + MetricName: aws.String("ThrottledRequests"), + Namespace: aws.String("AWS/DynamoDB"), + }, + }, + }, nil + default: + return nil, errors.New("No such namespace") + } +} + +func TestListMetrics(t *testing.T) { + var lmm ListMetricsMock + + metrics, err := listMetrics(&lmm, []string{"AWS/EC2"}) + if err != nil { + t.Fatal(err) + } + if len(metrics) != 2 { + t.Fatal("Expected 2 AWS/EC2 metrics, found ", len(metrics)) + } + + allMetrics, err := listMetrics(&lmm, []string{"AWS/EC2", "AWS/DynamoDB"}) + if err != nil { + t.Fatal(err) + } + if len(allMetrics) != 4 { + t.Fatal("Expected 4 AWS/EC2 and AWS/DynamoDB metrics, found ", len(allMetrics)) + } +} + +type GetMetricStatisticsMock struct{} + +func (gmsm *GetMetricStatisticsMock) GetMetricStatistics(input *cloudwatch.GetMetricStatisticsInput) (*cloudwatch.GetMetricStatisticsOutput, error) { + timestamp := time.Unix(1459201201, 0) + avg := float64(1.6295248026217786) + maximum := float64(8.0) + minimum := float64(0.5) + sum := float64(10939.0) + sampleCount := float64(6713.0) + return &cloudwatch.GetMetricStatisticsOutput{ + Datapoints: []*cloudwatch.Datapoint{ + &cloudwatch.Datapoint{ + Timestamp: ×tamp, + Average: &avg, + Maximum: &maximum, + Minimum: &minimum, + Sum: &sum, + SampleCount: &sampleCount, + Unit: aws.String("Count"), + }, + }, + Label: aws.String("ConsumedReadCapacityUnits"), + }, nil +} + +type GetMetricStatisticsMockPartial struct{} + +func (gmsm *GetMetricStatisticsMockPartial) GetMetricStatistics(input *cloudwatch.GetMetricStatisticsInput) (*cloudwatch.GetMetricStatisticsOutput, error) { + timestamp := time.Unix(1459201201, 0) + avg := float64(1.6295248026217786) + return &cloudwatch.GetMetricStatisticsOutput{ + Datapoints: []*cloudwatch.Datapoint{ + &cloudwatch.Datapoint{ + Timestamp: ×tamp, + Average: &avg, + Unit: aws.String("Count"), + }, + }, + Label: aws.String("ConsumedReadCapacityUnits"), + }, nil +} + +func TestGatherMetric(t *testing.T) { + var svc GetMetricStatisticsMock + var acc testutil.Accumulator + metric := &cloudwatch.Metric{ + Dimensions: []*cloudwatch.Dimension{ + &cloudwatch.Dimension{ + Name: aws.String("TableName"), + Value: aws.String("foo"), + }, + }, + MetricName: aws.String("ConsumedReadCapacityUnits"), + Namespace: aws.String("AWS/DynamoDB"), + } + bogusTime := time.Now() + err := gatherMetric(&svc, "us-east-1", &acc, metric, bogusTime, bogusTime, 300) + if err != nil { + t.Fatal(err) + } + acc.AssertContainsTaggedFields(t, "cloudwatch_aws_dynamo_db", + map[string]interface{}{"consumed_read_capacity_units_average": 1.6295248026217786, + "consumed_read_capacity_units_maximum": 8.0, + "consumed_read_capacity_units_minimum": 0.5, + "consumed_read_capacity_units_sum": 10939.0, + "consumed_read_capacity_units_sample_count": 6713.0, + }, + map[string]string{ + "region": "us-east-1", + "table_name": "foo", + }) +} + +// Test that it still works with only some statistics +func TestGatherMetricPartial(t *testing.T) { + var svc GetMetricStatisticsMockPartial + var acc testutil.Accumulator + metric := &cloudwatch.Metric{ + Dimensions: []*cloudwatch.Dimension{ + &cloudwatch.Dimension{ + Name: aws.String("TableName"), + Value: aws.String("foo"), + }, + }, + MetricName: aws.String("ConsumedReadCapacityUnits"), + Namespace: aws.String("AWS/DynamoDB"), + } + bogusTime := time.Now() + err := gatherMetric(&svc, "us-east-1", &acc, metric, bogusTime, bogusTime, 300) + if err != nil { + t.Fatal(err) + } + acc.AssertContainsTaggedFields(t, "cloudwatch_aws_dynamo_db", + map[string]interface{}{"consumed_read_capacity_units_average": 1.6295248026217786}, + map[string]string{ + "region": "us-east-1", + "table_name": "foo", + }) +} + +func TestGetTags(t *testing.T) { + tags := getTags("us-east-1", []*cloudwatch.Dimension{ + &cloudwatch.Dimension{ + Name: aws.String("Operation"), + Value: aws.String("GetItem"), + }, + &cloudwatch.Dimension{ + Name: aws.String("TableName"), + Value: aws.String("foo"), + }, + }) + if len(tags) != 3 { + t.Fatal("Expected 3 tags, not ", len(tags)) + } + if tags["region"] != "us-east-1" { + t.Fatal("Expected the tag `region` with value `us-east-1`") + } + if tags["operation"] != "GetItem" { + t.Fatal("Expected the tag `operation` with value `GetItem`") + } + if tags["table_name"] != "foo" { + t.Fatal("Expected the tag `table_name` with value `foo`") + } +} + +func TestFormatField(t *testing.T) { + if s := formatField("DiskWriteBytes", "average"); s != "disk_write_bytes_average" { + t.Fatal("Field should not be ", s) + } +} + +func TestFormatMeasurement(t *testing.T) { + if s := formatMeasurement("AWS/EC2"); s != "cloudwatch_aws_ec2" { + t.Fatal("Measurement should not be ", s) + } + if s := formatMeasurement("AWS/DynamoDB"); s != "cloudwatch_aws_dynamo_db" { + t.Fatal("Measurement should not be ", s) + } +}