Skip to content

Commit

Permalink
Start on tests for config
Browse files Browse the repository at this point in the history
  • Loading branch information
lewis262626 committed Feb 18, 2023
1 parent c73718c commit ed181c2
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 19 deletions.
31 changes: 24 additions & 7 deletions receiver/awscloudwatchmetricsreceiver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,15 @@ This receiver uses the [AWS SDK](https://docs.aws.amazon.com/sdk-for-go/v1/devel
| --------------- | ---------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `region` | *required* | string | The AWS recognized region string |
| `profile` | *optional* | string | The AWS profile used to authenticate, if none is specified the default is chosen from the list of profiles |
| `imds_endpoint` | *optional* | string | A way of specifying a custom URL to be used by the EC2 IMDS client to validate the session. If unset, and the environment variable `AWS_EC2_METADATA_SERVICE_ENDPOINT` has a value the client will use the value of the environment variable as the endpoint for operation calls. |

| `metrics` | *optional* | `Metrics` | Configuration for metrics ingestion of this receiver |

### Metrics Parameters

| Parameter | Notes | type | Description |
| ------------------------ | ------------ | ---------------------- | ------------------------------------------------------------------------------------------ |
| `poll_interval` | `default=1m` | duration | The duration waiting in between requests. |
| `max_events_per_request` | `default=50` | int | The maximum number of events to process per request to Cloudwatch |
| `groups` | *optional* | `See Group Parameters` | Configuration for Log Groups, by default all Log Groups and Log Streams will be collected. |
| `named` | *optional* | `See Group Parameters` | Configuration for Log Groups, by default no metrics will be collected |

### Group Parameters

Expand All @@ -46,16 +45,34 @@ This receiver uses the [AWS SDK](https://docs.aws.amazon.com/sdk-for-go/v1/devel

```yaml
awscloudwatchmetrics:
region: us-west-1
poll_interval: 5m
region: us-east-1
poll_interval: 1m
metrics:
named:
- namespace: AWS/EC2:
metric_names: [DiskWriteOps,DiskReadBytes]
- namespace: "AWS/EC2"
metric_name: "CPUUtilization"
period: "5m"
aws_aggregation: "Sum"
dimensions:
- Name: "InstanceId"
Value: "i-1234567890abcdef0"
- namespace: "AWS/S3"
metric_name: "BucketSizeBytes"
period: "5m"
aws_aggregation: "p99"
dimensions:
- Name: "BucketName"
Value: "OpenTelemetry"
- Name: "StorageType"
Value: "StandardStorage"
```
## Sample Configs
## AWS Costs
This receiver uses the [GetMetricData](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_GetMetricData.html) API call, this call is *not* in the AWS free tier. Please refer to [Amazon's pricing](https://aws.amazon.com/cloudwatch/pricing/) for futher information about expected costs. For `us-east-1`, the current pricing is $0.01 per 1,000 metrics requested as of February 2023.


[alpha]:https://github.com/open-telemetry/opentelemetry-collector#alpha
[contrib]:https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
Expand Down
104 changes: 94 additions & 10 deletions receiver/awscloudwatchmetricsreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"errors"
"fmt"
"net/url"
"strings"
"time"

"go.uber.org/multierr"
Expand All @@ -33,25 +34,46 @@ type Config struct {
Profile string `mapstructure:"profile"`
IMDSEndpoint string `mapstructure:"imds_endpoint"`
PollInterval time.Duration `mapstructure:"poll_interval"`
nilToZero bool `mapstrucuture:"nil_to_zero"` // Return 0 value if Cloudwatch returns no metrics at all. By default NaN will be reported
Metrics *MetricsConfig `mapstructure:"metrics"`
}

// MetricsConfig is the configuration for the metrics part of the receiver
// added this so we could expand to other inputs such as autodiscover
type MetricsConfig struct {
Names []NamesConfig `mapstructure:"named"`
Names []*NamedConfig `mapstructure:"named"`
}

// NamesConfig is the configuration for the metric namespace and metric names
// https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html
type NamesConfig struct {
Namespace string `mapstructure:"namespace"`
MetricNames []*string `mapstructure:"metric_names"`
type NamedConfig struct {
Namespace string `mapstructure:"namespace"`
MetricName string `mapstructure:"metric_name"`
Period time.Duration `mapstructure:"period"`
AwsAggregation string `mapstructure:"aws_aggregation"`
Dimensions []MetricDimensionsConfig `mapstructure:"dimensions"`
}

type MetricDimensionsConfig struct {
Name string `mapstructure:"Name"`
Value string `mapstructure:"Value"`
}

var (
errNoMetricsConfigured = errors.New("no metrics configured")
errNoRegion = errors.New("no region was specified")
errInvalidPollInterval = errors.New("poll interval is incorrect, it must be a duration greater than one second")

// https://docs.aws.amazon.com/cli/latest/reference/cloudwatch/get-metric-data.html
// GetMetricData supports up to 500 metrics per API call
errTooManyMetrics = errors.New("too many metrics defined")

// https://docs.aws.amazon.com/cli/latest/reference/cloudwatch/get-metric-data.html
errEmptyDimensions = errors.New("dimensions name and value is empty")
errTooManyDimensions = errors.New("you cannot define more than 30 dimensions for a metric")
errDimensionColonPrefix = errors.New("dimension name cannot start with a colon")

errInvalidAwsAggregation = errors.New("invalid AWS aggregation")
)

func (cfg *Config) Validate() error {
Expand All @@ -78,18 +100,80 @@ func (cfg *Config) validateMetricsConfig() error {
if cfg.Metrics == nil {
return errNoMetricsConfigured
}
return validate(cfg.Metrics.Names)
return cfg.validateNamedConfig()
}

func validate(g []NamesConfig) error {
for _, metric := range g {
if metric.Namespace == "" {
func (cfg *Config) validateNamedConfig() error {
if cfg.Metrics.Names == nil {
return errNoMetricsConfigured
}
return cfg.validateDimensionsConfig()
}

func (cfg *Config) validateDimensionsConfig() error {
var errs error

metricsNames := cfg.Metrics.Names
if len(metricsNames) > 500 {
return errTooManyMetrics
}
for _, name := range metricsNames {
if name.Namespace == "" {
return errNoMetricsConfigured
}

if len(metric.MetricNames) <= 0 {
err := validateAwsAggregation(name.AwsAggregation)
if err != nil {
return err
}
if name.MetricName == "" {
return errNoMetricsConfigured
}
errs = multierr.Append(errs, validate(name.Dimensions))
}
return errs
}

// https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Statistics-definitions.html
func validateAwsAggregation(agg string) error {
switch {
case agg == "SampleCount":
return nil
case agg == "Sum":
return nil
case agg == "Average":
return nil
case agg == "Minimum":
return nil
case agg == "Maximum":
return nil
case strings.HasPrefix(agg, "p"):
return nil
case strings.HasPrefix(agg, "TM"):
return nil
case agg == "IQM":
return nil
case strings.HasPrefix(agg, "PR"):
return nil
case strings.HasPrefix(agg, "TC"):
return nil
case strings.HasPrefix(agg, "TS"):
return nil
default:
return errInvalidAwsAggregation
}
}

func validate(nmd []MetricDimensionsConfig) error {
for _, dimensionConfig := range nmd {
if dimensionConfig.Name == "" || dimensionConfig.Value == "" {
return errEmptyDimensions
}
if strings.HasPrefix(dimensionConfig.Name, ":") {
return errDimensionColonPrefix
}
}
if len(nmd) > 30 {
return errTooManyDimensions
}
return nil
}
34 changes: 33 additions & 1 deletion receiver/awscloudwatchmetricsreceiver/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,36 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package awscloudwatchmetricsreceiver_test
package awscloudwatchmetricsreceiver

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestValidate(t *testing.T) {
cases := []struct {
name string
config Config
expectedErr error
}{
{
name: "Invalid region",
config: Config{
Region: "",
},
expectedErr: errNoRegion,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := tc.config.Validate()
if tc.expectedErr != nil {
require.ErrorContains(t, err, tc.expectedErr.Error())
} else {
require.NoError(t, err)
}
})
}
}
1 change: 1 addition & 0 deletions receiver/awscloudwatchmetricsreceiver/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func createMetricsRceiver(_ context.Context, params receiver.CreateSettings, bas
func createDefaultConfig() component.Config {
return &Config{
PollInterval: defaultPollInterval,
nilToZero: false,
Metrics: &MetricsConfig{},
}
}
6 changes: 5 additions & 1 deletion receiver/awscloudwatchmetricsreceiver/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/receiver/awsclo
go 1.19

require (
github.com/stretchr/testify v1.8.1
go.opentelemetry.io/collector v0.71.0
go.opentelemetry.io/collector/component v0.71.0
go.opentelemetry.io/collector/consumer v0.71.0
go.uber.org/multierr v1.9.0
go.uber.org/zap v1.24.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
Expand All @@ -19,18 +22,19 @@ require (
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/collector/confmap v0.71.0 // indirect
go.opentelemetry.io/collector/featuregate v0.71.0 // indirect
go.opentelemetry.io/collector/pdata v1.0.0-rc5 // indirect
go.opentelemetry.io/otel v1.13.0 // indirect
go.opentelemetry.io/otel/metric v0.36.0 // indirect
go.opentelemetry.io/otel/trace v1.13.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/text v0.6.0 // indirect
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect
google.golang.org/grpc v1.52.3 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
8 changes: 8 additions & 0 deletions receiver/awscloudwatchmetricsreceiver/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit ed181c2

Please sign in to comment.