fix(cloudwatch): store datums sent in PutMetricData EntityMetricData (#6070)

The CloudWatch Agent sends PutMetricData with an empty top-level
MetricData and every datum nested under EntityMetricData[].MetricData.
Both the JSON 1.0 handler (also reached by Smithy RPC v2 CBOR) and the
Query handler read only the top-level list, so the agent's metrics were
silently dropped.

Both handlers now append each EntityMetricData entry's datums to the
top-level ones and store them under the request Namespace, with the same
parsing as top-level datums. Entity and StrictEntityValidation are
accepted and ignored. The per-request INFO logs of the raw request and
parsed datums are lowered to a DEBUG count.

Fixes #6066

(cherry picked from commit 03f57303969561b049c3ebba38f1fd9d2b68df4c)
This commit is contained in:
Mohith
2026-10-20 17:49:42 +05:30
committed by Zakaria
parent 2e0a438d70
commit 21625c5a1b
6 changed files with 358 additions and 14 deletions
@@ -6,6 +6,7 @@ import software.amazon.awssdk.services.cloudwatch.model.*;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import static org.assertj.core.api.Assertions.*;
@@ -185,4 +186,51 @@ class CloudWatchTest {
);
assertThat(noMatch.datapoints()).isEmpty();
}
@Test
@DisplayName("PutMetricData stores datums nested under EntityMetricData")
void testPutMetricDataWithEntityMetricData() {
String namespace = "JavaTestEntityMetricData";
Instant now = Instant.now();
cloudWatchClient.putMetricData(request -> request
.namespace(namespace)
.metricData(datum -> datum
.metricName("TopLevel")
.value(1.0)
.timestamp(now))
.entityMetricData(entityMetricData -> entityMetricData
.entity(entity -> entity
.keyAttributes(Map.of(
"Type", "AWS::Resource",
"ResourceType", "AWS::EC2::Instance",
"Identifier", "i-0123456789abcdef0")))
.metricData(MetricDatum.builder()
.metricName("mem_used_percent")
.value(6.5)
.unit(StandardUnit.PERCENT)
.storageResolution(60)
.timestamp(now)
.dimensions(Dimension.builder().name("host").value("h1").build())
.build()))
.strictEntityValidation(false)
);
ListMetricsResponse listed = cloudWatchClient.listMetrics(request -> request.namespace(namespace));
assertThat(listed.metrics())
.extracting(Metric::metricName)
.containsExactlyInAnyOrder("TopLevel", "mem_used_percent");
GetMetricStatisticsResponse stats = cloudWatchClient.getMetricStatistics(request -> request
.namespace(namespace)
.metricName("mem_used_percent")
.dimensions(Dimension.builder().name("host").value("h1").build())
.startTime(now.minus(1, ChronoUnit.HOURS))
.endTime(now.plus(1, ChronoUnit.MINUTES))
.period(3600)
.statistics(Statistic.SUM)
);
assertThat(stats.datapoints()).hasSize(1);
assertThat(stats.datapoints().get(0).sum()).isEqualTo(6.5);
}
}