Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions x-pack/metricbeat/module/aws/cloudwatch/metadata/ec2/ec2.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

"github.com/elastic/beats/v7/metricbeat/mb"
"github.com/elastic/beats/v7/x-pack/metricbeat/module/aws"
"github.com/elastic/beats/v7/x-pack/metricbeat/module/aws/cloudwatch/metadata"
"github.com/elastic/elastic-agent-libs/logp"
)

Expand All @@ -35,26 +36,32 @@ func AddMetadata(logger *logp.Logger, regionName string, awsConfig awssdk.Config
}

for eventIdentifier := range events {
eventIdentifierComponents := strings.Split(eventIdentifier, "-")
potentialInstanceID := strings.Join(eventIdentifierComponents[0:len(eventIdentifierComponents)-1], "-")
// Get instance ID from dimension value
var instanceIDForMatching string
if dimInstanceID, err := events[eventIdentifier].RootFields.GetValue("aws.dimensions.InstanceId"); err == nil {
if idStr, ok := dimInstanceID.(string); ok {
instanceIDForMatching = idStr
_, _ = events[eventIdentifier].RootFields.Put("cloud.instance.id", dimInstanceID)
}
}
if instanceIDForMatching == "" {
// Fallback: parse eventIdentifier, stripping account ID prefix if present
// Format: {accountId}-{resourceId}-{index} or {resourceId}-{index}
instanceIDForMatching = metadata.ExtractResourceID(eventIdentifier)
}

// add host cpu/network/disk fields and host.id and rate metrics for all instances from both the monitoring
// account and linked source accounts if include_linked_accounts is set to true
addHostFields(events[eventIdentifier], potentialInstanceID)
addHostFields(events[eventIdentifier], instanceIDForMatching)
period, err := events[eventIdentifier].RootFields.GetValue(aws.CloudWatchPeriodName)
if err != nil {
logger.Warnf("can't get period information for instance %s, skipping rate calculation", eventIdentifier)
} else {
calculateRate(events[eventIdentifier], period.(int))
}

// add instance ID from dimension value
if dimInstanceID, err := events[eventIdentifier].RootFields.GetValue("aws.dimensions.InstanceId"); err == nil {
_, _ = events[eventIdentifier].RootFields.Put("cloud.instance.id", dimInstanceID)
} else if periodInt, ok := period.(int); ok {
calculateRate(events[eventIdentifier], periodInt)
}

for instanceID, output := range instancesOutputs {
if instanceID != potentialInstanceID {
if instanceID != instanceIDForMatching {
continue
}
for _, tag := range output.Tags {
Expand Down Expand Up @@ -186,8 +193,10 @@ func calculateRate(event mb.Event, periodInSeconds int) {
for _, metricName := range metricList {
metricValue, err := event.RootFields.GetValue(metricName)
if err == nil && metricValue != nil {
rateValue := metricValue.(float64) / float64(periodInSeconds)
_, _ = event.RootFields.Put(strings.Replace(metricName, ".sum", ".rate", -1), rateValue)
if floatVal, ok := metricValue.(float64); ok {
rateValue := floatVal / float64(periodInSeconds)
_, _ = event.RootFields.Put(strings.ReplaceAll(metricName, ".sum", ".rate"), rateValue)
}
}
}
}
42 changes: 42 additions & 0 deletions x-pack/metricbeat/module/aws/cloudwatch/metadata/metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package metadata

import (
"strings"
"unicode"
)

// ExtractResourceID extracts the resource identifier from an event identifier.
// Event identifier format: {accountId}-{resourceId}-{index}
// Account ID is always 12 digits, so we detect and strip it.
func ExtractResourceID(eventIdentifier string) string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have one concern, we create identifier as below,

identifierValue := labels[aws.LabelConst.AccountIdIdx] + "-" + labels[aws.LabelConst.IdentifierValueIdx] + fmt.Sprint("-", valI)

if labels[aws.LabelConst.IdentifierValueIdx] contain dashes, then this logic can fail 🤔

parts := strings.Split(eventIdentifier, "-")
if len(parts) < 2 {
return eventIdentifier
}

startIdx := 0
// Check if first part is a 12-digit account ID
if len(parts[0]) == 12 && isAllDigits(parts[0]) {
startIdx = 1
}

// Remove the last part (index) and join the rest
if startIdx < len(parts)-1 {
return strings.Join(parts[startIdx:len(parts)-1], "-")
}
return eventIdentifier
}

// isAllDigits returns true if the string contains only digits
func isAllDigits(s string) bool {
for _, c := range s {
if !unicode.IsDigit(c) {
return false
}
}
return true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package metadata

import (
"testing"

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

func TestExtractResourceID(t *testing.T) {
tests := []struct {
name string
eventIdentifier string
expected string
}{
{
name: "EC2 instance ID",
eventIdentifier: "123456789012-i-0abcd1234efgh5678-0",
expected: "i-0abcd1234efgh5678",
},
{
name: "RDS with multiple dashes",
eventIdentifier: "123456789012-my-database-instance-0",
expected: "my-database-instance",
},
{
name: "11-digit prefix is not stripped",
eventIdentifier: "12345678901-resource-0",
expected: "12345678901-resource",
},
{
name: "Single part returns as is",
eventIdentifier: "singlevalue",
expected: "singlevalue",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ExtractResourceID(tt.eventIdentifier)
assert.Equal(t, tt.expected, result)
})
}
}
6 changes: 3 additions & 3 deletions x-pack/metricbeat/module/aws/cloudwatch/metadata/rds/rds.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ package rds
import (
"context"
"fmt"
"strings"

"github.com/aws/aws-sdk-go-v2/service/rds/types"

awssdk "github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/rds"

"github.com/elastic/beats/v7/metricbeat/mb"
"github.com/elastic/beats/v7/x-pack/metricbeat/module/aws/cloudwatch/metadata"
)

const metadataPrefix = "aws.rds.db_instance."
Expand Down Expand Up @@ -47,8 +47,8 @@ func AddMetadata(regionName string, awsConfig awssdk.Config, fips_enabled bool,

for dbInstanceIdentifier, output := range dbDetailsMap {
for eventIdentifier := range events {
eventIdentifierComponents := strings.Split(eventIdentifier, "-")
potentialDBInstanceIdentifier := strings.Join(eventIdentifierComponents[0:len(eventIdentifierComponents)-1], "-")
// Extract DB instance identifier, stripping account ID prefix if present
potentialDBInstanceIdentifier := metadata.ExtractResourceID(eventIdentifier)
if dbInstanceIdentifier != potentialDBInstanceIdentifier {
continue
}
Expand Down
5 changes: 3 additions & 2 deletions x-pack/metricbeat/module/aws/cloudwatch/metadata/sqs/sqs.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/aws/aws-sdk-go-v2/service/sqs"

"github.com/elastic/beats/v7/metricbeat/mb"
"github.com/elastic/beats/v7/x-pack/metricbeat/module/aws/cloudwatch/metadata"
)

const metadataPrefix = "aws.sqs.queue"
Expand All @@ -39,8 +40,8 @@ func AddMetadata(regionName string, awsConfig awssdk.Config, fips_enabled bool,
queueURLParsed := strings.Split(queueURL, "/")
queueName := queueURLParsed[len(queueURLParsed)-1]
for eventIdentifier := range events {
eventIdentifierComponents := strings.Split(eventIdentifier, "-")
potentialQueueName := strings.Join(eventIdentifierComponents[0:len(eventIdentifierComponents)-1], "-")
// Extract queue name, stripping account ID prefix if present
potentialQueueName := metadata.ExtractResourceID(eventIdentifier)
if queueName != potentialQueueName {
continue
}
Expand Down