Skip to content

Commit

Permalink
Enforcing limits on Application Signals metric dimension value
Browse files Browse the repository at this point in the history
  • Loading branch information
bjrara committed May 17, 2024
1 parent dc0e2a9 commit 60b8a4f
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,23 @@ import (
attr "github.com/aws/amazon-cloudwatch-agent/plugins/processors/awsapplicationsignals/internal/attributes"
)

const (
// Length limits from Application Signals SLOs
MaxEnvironmentLength = 259
MaxServiceNameLength = 255

// Length limits from CloudWatch Metrics
DefaultMetricAttributeLength = 1024
)

type attributesNormalizer struct {
logger *zap.Logger
}

var attributesRenamingForMetric = map[string]string{
attr.AWSLocalService: common.MetricAttributeLocalService,
attr.AWSLocalOperation: common.MetricAttributeLocalOperation,
attr.AWSLocalEnvironment: common.MetricAttributeEnvironment,
attr.AWSRemoteService: common.MetricAttributeRemoteService,
attr.AWSRemoteOperation: common.MetricAttributeRemoteOperation,
attr.AWSRemoteEnvironment: common.MetricAttributeRemoteEnvironment,
Expand All @@ -45,8 +55,7 @@ var resourceAttributesRenamingForTrace = map[string]string{
}

var attributesRenamingForTrace = map[string]string{
common.MetricAttributeEnvironment: attr.AWSLocalEnvironment,
attr.AWSRemoteTarget: attr.AWSRemoteResourceIdentifier,
attr.AWSRemoteTarget: attr.AWSRemoteResourceIdentifier,
}

var copyMapForMetric = map[string]string{
Expand Down Expand Up @@ -75,6 +84,10 @@ func NewAttributesNormalizer(logger *zap.Logger) *attributesNormalizer {

func (n *attributesNormalizer) Process(attributes, resourceAttributes pcommon.Map, isTrace bool) error {
n.copyResourceAttributesToAttributes(attributes, resourceAttributes, isTrace)
// It's assumed that all attributes are initially inserted as trace attribute, and attributesRenamingForMetric
// contains all attributes that will be used for CloudWatch metric dimension. Therefore, we iterate the keys
// for enforcing the limits on length.
truncateAttributesByLength(attributes)
n.renameAttributes(attributes, resourceAttributes, isTrace)
n.appendNewAttributes(attributes, resourceAttributes, isTrace)
return nil
Expand Down Expand Up @@ -160,3 +173,29 @@ func rename(attrs pcommon.Map, renameMap map[string]string) {
}
}
}

func truncateAttributesByLength(attributes pcommon.Map) {
for attrKey, _ := range attributesRenamingForMetric {
switch attrKey {
case attr.AWSLocalEnvironment, attr.AWSRemoteEnvironment:
if val, ok := attributes.Get(attrKey); ok {
attributes.PutStr(attrKey, truncateStringByLength(val.Str(), MaxEnvironmentLength))
}
case attr.AWSLocalService, attr.AWSRemoteService:
if val, ok := attributes.Get(attrKey); ok {
attributes.PutStr(attrKey, truncateStringByLength(val.Str(), MaxServiceNameLength))
}
default:
if val, ok := attributes.Get(attrKey); ok {
attributes.PutStr(attrKey, truncateStringByLength(val.Str(), DefaultMetricAttributeLength))
}
}
}
}

func truncateStringByLength(val string, length int) string {
if len(val) > length {
return val[:length]
}
return val
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"go.opentelemetry.io/collector/pdata/pcommon"
conventions "go.opentelemetry.io/collector/semconv/v1.6.1"
"go.uber.org/zap"

attr "github.com/aws/amazon-cloudwatch-agent/plugins/processors/awsapplicationsignals/internal/attributes"
)

func TestRenameAttributes_for_metric(t *testing.T) {
Expand Down Expand Up @@ -106,6 +108,32 @@ func TestCopyResourceAttributesToAttributes(t *testing.T) {
}
}

func TestTruncateAttributes(t *testing.T) {
attributes := pcommon.NewMap()

longValue := make([]byte, 300)
for i := 0; i < 300; i++ {
longValue[i] = 'a'
}
longStringValue := string(longValue)
for key, _ := range attributesRenamingForMetric {
attributes.PutStr(key, longStringValue)
}

truncateAttributesByLength(attributes)

val, _ := attributes.Get(attr.AWSLocalEnvironment)
assert.True(t, len(val.Str()) == MaxEnvironmentLength)
val, _ = attributes.Get(attr.AWSRemoteEnvironment)
assert.True(t, len(val.Str()) == MaxEnvironmentLength)
val, _ = attributes.Get(attr.AWSLocalService)
assert.True(t, len(val.Str()) == MaxServiceNameLength)
val, _ = attributes.Get(attr.AWSRemoteService)
assert.True(t, len(val.Str()) == MaxServiceNameLength)
val, _ = attributes.Get(attr.AWSRemoteResourceIdentifier)
assert.True(t, len(val.Str()) == 300)
}

func Test_attributesNormalizer_appendNewAttributes(t *testing.T) {
logger, _ := zap.NewDevelopment()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ const (
)

var GenericInheritedAttributes = map[string]string{
attr.AWSHostedInEnvironment: common.MetricAttributeEnvironment,
semconv1.AttributeDeploymentEnvironment: common.MetricAttributeEnvironment,
attr.AWSHostedInEnvironment: attr.AWSLocalEnvironment,
semconv1.AttributeDeploymentEnvironment: attr.AWSLocalEnvironment,
attr.ResourceDetectionHostName: attr.ResourceDetectionHostName,
}

// DefaultInheritedAttributes is an allow-list that also renames attributes from the resource detection processor
var DefaultInheritedAttributes = map[string]string{
attr.AWSHostedInEnvironment: common.MetricAttributeEnvironment,
semconv1.AttributeDeploymentEnvironment: common.MetricAttributeEnvironment,
attr.AWSHostedInEnvironment: attr.AWSLocalEnvironment,
semconv1.AttributeDeploymentEnvironment: attr.AWSLocalEnvironment,
attr.ResourceDetectionASG: common.AttributeEC2AutoScalingGroupName,
attr.ResourceDetectionHostId: common.AttributeEC2InstanceId,
attr.ResourceDetectionHostName: attr.ResourceDetectionHostName,
Expand Down Expand Up @@ -113,20 +113,20 @@ func (h *resourceAttributesResolver) Process(attributes, resourceAttributes pcom
attributes.PutStr(mappingKey, val.AsString())
}
}
if _, ok := attributes.Get(common.MetricAttributeEnvironment); !ok {
if _, ok := attributes.Get(attr.AWSLocalEnvironment); !ok {
if h.defaultEnvPrefix == appsignalsconfig.PlatformECS {
if clusterName, ok := getECSClusterName(resourceAttributes); ok {
attributes.PutStr(common.MetricAttributeEnvironment, GetDefaultEnvironment(h.defaultEnvPrefix, clusterName))
attributes.PutStr(attr.AWSLocalEnvironment, GetDefaultEnvironment(h.defaultEnvPrefix, clusterName))
}
}
if h.defaultEnvPrefix == appsignalsconfig.PlatformEC2 {
if asgAttr, ok := resourceAttributes.Get(attr.ResourceDetectionASG); ok {
attributes.PutStr(common.MetricAttributeEnvironment, GetDefaultEnvironment(h.defaultEnvPrefix, asgAttr.Str()))
attributes.PutStr(attr.AWSLocalEnvironment, GetDefaultEnvironment(h.defaultEnvPrefix, asgAttr.Str()))
}
}
}
if _, ok := attributes.Get(common.MetricAttributeEnvironment); !ok {
attributes.PutStr(common.MetricAttributeEnvironment, GetDefaultEnvironment(h.defaultEnvPrefix, AttributeEnvironmentDefault))
if _, ok := attributes.Get(attr.AWSLocalEnvironment); !ok {
attributes.PutStr(attr.AWSLocalEnvironment, GetDefaultEnvironment(h.defaultEnvPrefix, AttributeEnvironmentDefault))
}
attributes.PutStr(common.AttributePlatformType, h.platformType)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ func TestResourceAttributesResolverWithNoConfiguredName(t *testing.T) {

resolver.Process(attributes, resourceAttributes)

attr, ok := attributes.Get(common.AttributePlatformType)
attribute, ok := attributes.Get(common.AttributePlatformType)
assert.True(t, ok)
assert.Equal(t, tt.platformType, attr.Str())
assert.Equal(t, tt.platformType, attribute.Str())

attr, ok = attributes.Get(common.MetricAttributeEnvironment)
attribute, ok = attributes.Get(attr.AWSLocalEnvironment)
assert.True(t, ok)
assert.Equal(t, tt.platformCode+":default", attr.Str())
assert.Equal(t, tt.platformCode+":default", attribute.Str())
})
}
}
Expand All @@ -88,13 +88,13 @@ func TestResourceAttributesResolverWithECSClusterName(t *testing.T) {

resolver.Process(attributes, resourceAttributes)

attr, ok := attributes.Get(common.AttributePlatformType)
attribute, ok := attributes.Get(common.AttributePlatformType)
assert.True(t, ok)
assert.Equal(t, "Generic", attr.Str())
assert.Equal(t, "Generic", attribute.Str())

attr, ok = attributes.Get(common.MetricAttributeEnvironment)
attribute, ok = attributes.Get(attr.AWSLocalEnvironment)
assert.True(t, ok)
assert.Equal(t, "ecs:my-cluster", attr.Str())
assert.Equal(t, "ecs:my-cluster", attribute.Str())
}

func TestResourceAttributesResolverWithOnEC2WithASG(t *testing.T) {
Expand All @@ -110,7 +110,7 @@ func TestResourceAttributesResolverWithOnEC2WithASG(t *testing.T) {
platformAttr, ok := attributes.Get(common.AttributePlatformType)
assert.True(t, ok)
assert.Equal(t, "AWS::EC2", platformAttr.Str())
envAttr, ok := attributes.Get(common.MetricAttributeEnvironment)
envAttr, ok := attributes.Get(attr.AWSLocalEnvironment)
assert.True(t, ok)
assert.Equal(t, "ec2:my-asg", envAttr.Str())
}
Expand Down Expand Up @@ -162,7 +162,7 @@ func TestResourceAttributesResolverWithCustomEnvironment(t *testing.T) {
// insert custom env
resourceAttributes.PutStr(attr.AWSHostedInEnvironment, "env1")
resolver.Process(attributes, resourceAttributes)
envAttr, ok := attributes.Get(common.MetricAttributeEnvironment)
envAttr, ok := attributes.Get(attr.AWSLocalEnvironment)
assert.True(t, ok)
assert.Equal(t, "env1", envAttr.Str())

Expand All @@ -172,7 +172,7 @@ func TestResourceAttributesResolverWithCustomEnvironment(t *testing.T) {
resourceAttributes.PutStr(attr.AWSHostedInEnvironment, "error")
resourceAttributes.PutStr(semconv.AttributeDeploymentEnvironment, "env2")
resolver.Process(attributes, resourceAttributes)
envAttr, ok = attributes.Get(common.MetricAttributeEnvironment)
envAttr, ok = attributes.Get(attr.AWSLocalEnvironment)
assert.True(t, ok)
assert.Equal(t, "env2", envAttr.Str())

Expand All @@ -181,7 +181,7 @@ func TestResourceAttributesResolverWithCustomEnvironment(t *testing.T) {

resourceAttributes.PutStr(semconv.AttributeDeploymentEnvironment, "env3")
resolver.Process(attributes, resourceAttributes)
envAttr, ok = attributes.Get(common.MetricAttributeEnvironment)
envAttr, ok = attributes.Get(attr.AWSLocalEnvironment)
assert.True(t, ok)
assert.Equal(t, "env3", envAttr.Str())
})
Expand Down

0 comments on commit 60b8a4f

Please sign in to comment.