Skip to content

Commit c8d3576

Browse files
committed
Adds append_dimensions to collectd, supporting custom metric dimensions
1 parent e41b31c commit c8d3576

File tree

9 files changed

+283
-1
lines changed

9 files changed

+283
-1
lines changed

translator/cmdutil/translatorutil_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ func TestEthtoolConfig(t *testing.T) {
156156
checkIfSchemaValidateAsExpected(t, "../../translator/config/sampleSchema/validEthtoolConfig.json", true, map[string]int{})
157157
}
158158

159+
func TestCollectdConfig(t *testing.T) {
160+
checkIfSchemaValidateAsExpected(t, "../../translator/config/sampleSchema/validCollectdConfig.json", true, map[string]int{})
161+
}
162+
159163
func TestNvidiaGpuConfig(t *testing.T) {
160164
checkIfSchemaValidateAsExpected(t, "../../translator/config/sampleSchema/validNvidiaGpuConfig.json", true, map[string]int{})
161165
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"metrics": {
3+
"metrics_collected": {
4+
"collectd": {
5+
"service_address": "udp://127.0.0.1:25826",
6+
"name_prefix": "collectd_",
7+
"collectd_auth_file": "/etc/collectd/auth_file",
8+
"collectd_security_level": "encrypt",
9+
"metrics_aggregation_interval": 60,
10+
"append_dimensions": {
11+
"InstanceId": "${aws:InstanceId}",
12+
"Component": "MyService",
13+
"Environment": "Production"
14+
}
15+
}
16+
}
17+
}
18+
}

translator/config/schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,9 @@
286286
"type": "string",
287287
"minLength": 1,
288288
"maxLength": 4096
289+
},
290+
"append_dimensions": {
291+
"$ref": "#/definitions/generalAppendDimensionsDefinition"
289292
}
290293
},
291294
"additionalProperties": false
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
[agent]
2+
collection_jitter = "0s"
3+
debug = false
4+
flush_interval = "1s"
5+
flush_jitter = "0s"
6+
hostname = ""
7+
interval = "60s"
8+
logfile = "/opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log"
9+
logtarget = "lumberjack"
10+
metric_batch_size = 1000
11+
metric_buffer_limit = 10000
12+
omit_hostname = false
13+
precision = ""
14+
quiet = false
15+
round_interval = false
16+
17+
[inputs]
18+
19+
[[inputs.socket_listener]]
20+
collectd_auth_file = "/etc/collectd/auth_file"
21+
collectd_security_level = "encrypt"
22+
collectd_typesdb = ["/usr/share/collectd/types.db"]
23+
data_format = "collectd"
24+
name_prefix = "collectd_"
25+
service_address = "udp://127.0.0.1:25826"
26+
[inputs.socket_listener.tags]
27+
"aws:AggregationInterval" = "60s"
28+
d1 = "foo"
29+
d2 = "bar"
30+
31+
[outputs]
32+
33+
[[outputs.cloudwatch]]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"metrics": {
3+
"metrics_collected": {
4+
"collectd": {
5+
"service_address": "udp://127.0.0.1:25826",
6+
"name_prefix": "collectd_",
7+
"collectd_auth_file": "/etc/collectd/auth_file",
8+
"collectd_security_level": "encrypt",
9+
"collectd_typesdb": ["/usr/share/collectd/types.db"],
10+
"metrics_aggregation_interval": 60,
11+
"append_dimensions": {
12+
"d1": "foo",
13+
"d2": "bar"
14+
}
15+
}
16+
}
17+
}
18+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
exporters:
2+
awscloudwatch:
3+
force_flush_interval: 1m0s
4+
max_datums_per_call: 1000
5+
max_values_per_datum: 150
6+
middleware: agenthealth/metrics
7+
namespace: CWAgent
8+
region: us-west-2
9+
resource_to_telemetry_conversion:
10+
enabled: true
11+
extensions:
12+
agenthealth/metrics:
13+
is_usage_data_enabled: true
14+
stats:
15+
operations:
16+
- PutMetricData
17+
usage_flags:
18+
mode: EC2
19+
region_type: ACJ
20+
agenthealth/statuscode:
21+
is_status_code_enabled: true
22+
is_usage_data_enabled: true
23+
stats:
24+
usage_flags:
25+
mode: EC2
26+
region_type: ACJ
27+
entitystore:
28+
mode: ec2
29+
region: us-west-2
30+
processors:
31+
awsentity/service/telegraf:
32+
entity_type: Service
33+
platform: ec2
34+
scrape_datapoint_attribute: true
35+
receivers:
36+
telegraf_socket_listener:
37+
collection_interval: 1m0s
38+
initial_delay: 1s
39+
timeout: 0s
40+
service:
41+
extensions:
42+
- agenthealth/metrics
43+
- agenthealth/statuscode
44+
- entitystore
45+
pipelines:
46+
metrics/hostCustomMetrics:
47+
exporters:
48+
- awscloudwatch
49+
processors:
50+
- awsentity/service/telegraf
51+
receivers:
52+
- telegraf_socket_listener
53+
telemetry:
54+
logs:
55+
encoding: console
56+
level: info
57+
output_paths:
58+
- /opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log
59+
sampling:
60+
enabled: true
61+
initial: 2
62+
thereafter: 500
63+
tick: 10s
64+
metrics:
65+
level: None
66+
traces:
67+
level: None

translator/tocwconfig/tocwconfig_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,15 @@ func TestCollectDConfig(t *testing.T) {
415415
checkTranslation(t, "collectd_config_linux", "darwin", nil, "")
416416
}
417417

418+
// CollectD with append_dimensions
419+
func TestCollectDAppendDimensionsConfig(t *testing.T) {
420+
resetContext(t)
421+
context.CurrentContext().SetMode(config.ModeEC2)
422+
expectedEnvVars := map[string]string{}
423+
checkTranslation(t, "collectd_append_dimensions_linux", "linux", expectedEnvVars, "")
424+
checkTranslation(t, "collectd_append_dimensions_linux", "darwin", nil, "")
425+
}
426+
418427
// diskio
419428
func TestDiskIOTelegrafConfig(t *testing.T) {
420429
resetContext(t)

translator/translate/metrics/metrics_collect/collectd/collectd.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package collected
66
import (
77
"github.com/aws/amazon-cloudwatch-agent/translator"
88
parent "github.com/aws/amazon-cloudwatch-agent/translator/translate/metrics/metrics_collect"
9+
"github.com/aws/amazon-cloudwatch-agent/translator/translate/metrics/util"
910
)
1011

1112
//
@@ -50,7 +51,7 @@ func (obj *CollectD) ApplyRule(input interface{}) (returnKey string, returnVal i
5051
returnVal = ""
5152
} else {
5253
//If exists, process it
53-
//Check if there are some config entry with rules applied
54+
util.ProcessAppendDimensions(m[SectionKey].(map[string]interface{}), SectionKey, result)
5455
result = translator.ProcessRuleToMergeAndApply(m[SectionKey], ChildRule, result)
5556
resArray = append(resArray, result)
5657
returnKey = SectionMappedKey

translator/translate/metrics/metrics_collect/collectd/collectd_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"testing"
99

1010
"github.com/stretchr/testify/assert"
11+
12+
"github.com/aws/amazon-cloudwatch-agent/translator/translate/util"
1113
)
1214

1315
func TestCollectD_HappyCase(t *testing.T) {
@@ -62,3 +64,130 @@ func TestCollectD_MinimumConfig(t *testing.T) {
6264

6365
assert.Equal(t, expect, actual)
6466
}
67+
68+
func TestCollectD_WithAppendDimensions(t *testing.T) {
69+
// Mock EC2 metadata to avoid 6s IMDS timeout
70+
originalProvider := util.Ec2MetadataInfoProvider
71+
util.Ec2MetadataInfoProvider = func() *util.Metadata {
72+
return &util.Metadata{InstanceID: "i-1234567890abcdef0", InstanceType: "t3.medium"}
73+
}
74+
defer func() { util.Ec2MetadataInfoProvider = originalProvider }()
75+
76+
obj := new(CollectD)
77+
var input interface{}
78+
err := json.Unmarshal([]byte(`{"collectd": {
79+
"service_address": "udp://127.0.0.1:123",
80+
"append_dimensions": {
81+
"InstanceId": "${aws:InstanceId}",
82+
"CustomDimension": "CustomValue"
83+
}
84+
}}`), &input)
85+
assert.NoError(t, err)
86+
87+
_, actual := obj.ApplyRule(input)
88+
89+
actualMap := actual.([]interface{})[0].(map[string]interface{})
90+
tags := actualMap["tags"].(map[string]interface{})
91+
92+
assert.Equal(t, "60s", tags["aws:AggregationInterval"])
93+
assert.Equal(t, "CustomValue", tags["CustomDimension"])
94+
assert.Equal(t, "i-1234567890abcdef0", tags["InstanceId"])
95+
}
96+
97+
func TestCollectD_WithAppendDimensionsAndAggregationInterval(t *testing.T) {
98+
obj := new(CollectD)
99+
var input interface{}
100+
err := json.Unmarshal([]byte(`{"collectd": {
101+
"metrics_aggregation_interval": 30,
102+
"append_dimensions": {
103+
"Environment": "Production",
104+
"Team": "Infrastructure"
105+
}
106+
}}`), &input)
107+
assert.NoError(t, err)
108+
109+
_, actual := obj.ApplyRule(input)
110+
111+
expect := []interface{}{
112+
map[string]interface{}{
113+
"data_format": "collectd",
114+
"service_address": "udp://127.0.0.1:25826",
115+
"name_prefix": "collectd_",
116+
"collectd_auth_file": "/etc/collectd/auth_file",
117+
"collectd_security_level": "encrypt",
118+
"collectd_typesdb": []interface{}{"/usr/share/collectd/types.db"},
119+
"tags": map[string]interface{}{
120+
"aws:AggregationInterval": "30s",
121+
"Environment": "Production",
122+
"Team": "Infrastructure",
123+
},
124+
},
125+
}
126+
127+
assert.Equal(t, expect, actual)
128+
}
129+
130+
func TestCollectD_WithFullConfigAndAppendDimensions(t *testing.T) {
131+
// Mock EC2 metadata to avoid IMDS timeout
132+
originalProvider := util.Ec2MetadataInfoProvider
133+
util.Ec2MetadataInfoProvider = func() *util.Metadata {
134+
return &util.Metadata{InstanceID: "i-1234567890abcdef0", InstanceType: "t3.large", ImageID: "ami-12345678"}
135+
}
136+
defer func() { util.Ec2MetadataInfoProvider = originalProvider }()
137+
138+
obj := new(CollectD)
139+
var input interface{}
140+
err := json.Unmarshal([]byte(`{"collectd": {
141+
"service_address": "udp://127.0.0.1:123",
142+
"name_prefix": "collectd_prefix_",
143+
"collectd_auth_file": "/etc/collectd/_auth_file",
144+
"collectd_security_level": "none",
145+
"collectd_typesdb": ["/usr/share/collectd/types.db", "/custom_location/types.db"],
146+
"metrics_aggregation_interval": 30,
147+
"append_dimensions": {
148+
"InstanceId": "${aws:InstanceId}",
149+
"InstanceType": "${aws:InstanceType}",
150+
"ImageId": "${aws:ImageId}",
151+
"CustomTag": "MyValue"
152+
}
153+
}}`), &input)
154+
assert.NoError(t, err)
155+
156+
_, actual := obj.ApplyRule(input)
157+
158+
actualMap := actual.([]interface{})[0].(map[string]interface{})
159+
tags := actualMap["tags"].(map[string]interface{})
160+
161+
assert.Equal(t, "30s", tags["aws:AggregationInterval"])
162+
assert.Equal(t, "MyValue", tags["CustomTag"])
163+
assert.Equal(t, "i-1234567890abcdef0", tags["InstanceId"])
164+
assert.Equal(t, "t3.large", tags["InstanceType"])
165+
assert.Equal(t, "ami-12345678", tags["ImageId"])
166+
}
167+
168+
func TestCollectD_EmptyAppendDimensions(t *testing.T) {
169+
obj := new(CollectD)
170+
var input interface{}
171+
err := json.Unmarshal([]byte(`{"collectd": {
172+
"append_dimensions": {}
173+
}}`), &input)
174+
assert.NoError(t, err)
175+
176+
_, actual := obj.ApplyRule(input)
177+
178+
expect := []interface{}{
179+
map[string]interface{}{
180+
"data_format": "collectd",
181+
"service_address": "udp://127.0.0.1:25826",
182+
"name_prefix": "collectd_",
183+
"collectd_auth_file": "/etc/collectd/auth_file",
184+
"collectd_security_level": "encrypt",
185+
"collectd_typesdb": []interface{}{"/usr/share/collectd/types.db"},
186+
"tags": map[string]interface{}{
187+
"aws:AggregationInterval": "60s",
188+
},
189+
},
190+
}
191+
192+
assert.Equal(t, expect, actual)
193+
}

0 commit comments

Comments
 (0)