Skip to content

Commit da4a7f3

Browse files
authored
metrics refactor to publish in bulk (#311)
1 parent 81b08b6 commit da4a7f3

5 files changed

Lines changed: 84 additions & 108 deletions

File tree

src/main/java/software/amazon/cloudformation/LambdaWrapper.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import java.time.Instant;
3030
import java.util.Arrays;
3131
import java.util.Date;
32-
import java.util.EnumSet;
3332
import java.util.HashMap;
3433
import java.util.List;
3534
import java.util.Map;
@@ -242,11 +241,8 @@ public void handleRequest(final InputStream inputStream, final OutputStream outp
242241
// A response will be output on all paths, though CloudFormation will
243242
// not block on invoking the handlers, but rather listen for callbacks
244243

245-
if (handlerResponse != null) {
246-
publishExceptionCodeAndCountMetric(request == null ? null : request.getAction(), handlerResponse.getErrorCode(),
247-
handlerResponse.getStatus() == OperationStatus.FAILED);
248-
}
249244
writeResponse(outputStream, handlerResponse);
245+
publishExceptionCodeAndCountMetrics(request == null ? null : request.getAction(), handlerResponse.getErrorCode());
250246
}
251247
}
252248

@@ -493,12 +489,9 @@ private void publishExceptionMetric(final Action action, final Throwable ex, fin
493489
/*
494490
* null-safe exception metrics delivery
495491
*/
496-
private void
497-
publishExceptionCodeAndCountMetric(final Action action, final HandlerErrorCode handlerErrorCode, final boolean thrown) {
492+
private void publishExceptionCodeAndCountMetrics(final Action action, final HandlerErrorCode handlerErrorCode) {
498493
if (this.metricsPublisherProxy != null) {
499-
EnumSet.allOf(HandlerErrorCode.class).forEach(errorCode -> this.metricsPublisherProxy
500-
.publishExceptionByErrorCodeMetric(Instant.now(), action, errorCode, thrown && errorCode == handlerErrorCode));
501-
this.metricsPublisherProxy.publishExceptionCountMetric(Instant.now(), action, thrown);
494+
this.metricsPublisherProxy.publishExceptionByErrorCodeAndCountBulkMetrics(Instant.now(), action, handlerErrorCode);
502495
}
503496
}
504497

src/main/java/software/amazon/cloudformation/metrics/MetricsPublisher.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,9 @@ public void publishExceptionMetric(final Instant timestamp,
4242
final HandlerErrorCode handlerErrorCode) {
4343
}
4444

45-
public void publishExceptionByErrorCodeMetric(final Instant timestamp,
46-
final Action action,
47-
final HandlerErrorCode handlerErrorCode,
48-
final boolean thrown) {
49-
}
50-
51-
public void publishExceptionCountMetric(final Instant timestamp, final Action action, final boolean thrown) {
45+
public void publishExceptionByErrorCodeAndCountBulkMetrics(final Instant timestamp,
46+
final Action action,
47+
final HandlerErrorCode handlerErrorCode) {
5248
}
5349

5450
public void publishInvocationMetric(final Instant timestamp, final Action action) {

src/main/java/software/amazon/cloudformation/metrics/MetricsPublisherImpl.java

Lines changed: 61 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
*/
1515
package software.amazon.cloudformation.metrics;
1616

17+
import com.google.common.collect.Sets;
1718
import java.time.Instant;
18-
import java.util.ArrayList;
19-
import java.util.HashMap;
20-
import java.util.List;
21-
import java.util.Map;
19+
import java.util.EnumSet;
20+
import java.util.HashSet;
21+
import java.util.Set;
2222
import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
2323
import software.amazon.awssdk.services.cloudwatch.model.Dimension;
2424
import software.amazon.awssdk.services.cloudwatch.model.MetricDatum;
@@ -57,86 +57,83 @@ public void publishExceptionMetric(final Instant timestamp,
5757
final Action action,
5858
final Throwable e,
5959
final HandlerErrorCode handlerErrorCode) {
60-
Map<String, String> dimensions = new HashMap<>();
61-
dimensions.put(Metric.DIMENSION_KEY_ACTION_TYPE, action == null ? "NO_ACTION" : action.name());
62-
dimensions.put(Metric.DIMENSION_KEY_EXCEPTION_TYPE, e.getClass().toString());
63-
dimensions.put(Metric.DIMENSION_KEY_RESOURCE_TYPE, this.getResourceTypeName());
64-
dimensions.put(Metric.DIMENSION_KEY_HANDLER_ERROR_CODE, handlerErrorCode.name());
65-
66-
publishMetric(Metric.METRIC_NAME_HANDLER_EXCEPTION, dimensions, StandardUnit.COUNT, 1.0, timestamp);
60+
publishBulkMetrics(MetricDatum.builder().timestamp(timestamp).metricName(Metric.METRIC_NAME_HANDLER_EXCEPTION)
61+
.unit(StandardUnit.COUNT).value(1.0)
62+
.dimensions(Sets.newHashSet(
63+
Dimension.builder().name(Metric.DIMENSION_KEY_ACTION_TYPE).value(action == null ? "NO_ACTION" : action.name())
64+
.build(),
65+
Dimension.builder().name(Metric.DIMENSION_KEY_EXCEPTION_TYPE).value(e.getClass().toString()).build(),
66+
Dimension.builder().name(Metric.DIMENSION_KEY_RESOURCE_TYPE).value(this.getResourceTypeName()).build(),
67+
Dimension.builder().name(Metric.DIMENSION_KEY_HANDLER_ERROR_CODE).value(handlerErrorCode.name()).build()))
68+
.build());
6769
}
6870

6971
@Override
70-
public void publishExceptionByErrorCodeMetric(final Instant timestamp,
71-
final Action action,
72-
final HandlerErrorCode handlerErrorCode,
73-
final boolean thrown) {
74-
Map<String, String> dimensions = new HashMap<>();
75-
dimensions.put(Metric.DIMENSION_KEY_ACTION_TYPE, action == null ? "NO_ACTION" : action.name());
76-
dimensions.put(Metric.DIMENSION_KEY_HANDLER_ERROR_CODE, handlerErrorCode.name());
77-
78-
publishMetric(Metric.METRIC_NAME_HANDLER_EXCEPTION_BY_ERROR_CODE, dimensions, StandardUnit.COUNT, thrown ? 1.0 : 0.0,
79-
timestamp);
80-
}
81-
82-
public void publishExceptionCountMetric(final Instant timestamp, final Action action, final boolean thrown) {
83-
Map<String, String> dimensions = new HashMap<>();
84-
dimensions.put(Metric.DIMENSION_KEY_ACTION_TYPE, action == null ? "NO_ACTION" : action.name());
85-
86-
publishMetric(Metric.METRIC_NAME_HANDLER_EXCEPTION_BY_EXCEPTION_COUNT, dimensions, StandardUnit.COUNT, thrown ? 1.0 : 0.0,
87-
timestamp);
72+
public void publishExceptionByErrorCodeAndCountBulkMetrics(final Instant timestamp,
73+
final Action action,
74+
final HandlerErrorCode handlerErrorCode) {
75+
Set<MetricDatum> bulkData = new HashSet<>();
76+
77+
// By Error Code dimensions
78+
79+
EnumSet.allOf(HandlerErrorCode.class).forEach(
80+
errorCode -> bulkData.add(MetricDatum.builder().metricName(Metric.METRIC_NAME_HANDLER_EXCEPTION_BY_ERROR_CODE)
81+
.unit(StandardUnit.COUNT).value(errorCode == handlerErrorCode ? 1.0 : 0.0)
82+
.dimensions(Sets.newHashSet(
83+
Dimension.builder().name(Metric.DIMENSION_KEY_ACTION_TYPE).value(action == null ? "NO_ACTION" : action.name())
84+
.build(),
85+
Dimension.builder().name(Metric.DIMENSION_KEY_HANDLER_ERROR_CODE).value(errorCode.name()).build()))
86+
.timestamp(timestamp).build()));
87+
88+
// By Count dimensions
89+
bulkData.add(MetricDatum.builder().metricName(Metric.METRIC_NAME_HANDLER_EXCEPTION_BY_EXCEPTION_COUNT)
90+
.unit(StandardUnit.COUNT).value(handlerErrorCode == null ? 0.0 : 1.0).dimensions(Dimension.builder()
91+
.name(Metric.DIMENSION_KEY_ACTION_TYPE).value(action == null ? "NO_ACTION" : action.name()).build())
92+
.timestamp(timestamp).build());
93+
94+
publishBulkMetrics(bulkData.toArray(new MetricDatum[bulkData.size()]));
8895
}
8996

9097
@Override
9198
public void publishProviderLogDeliveryExceptionMetric(final Instant timestamp, final Throwable e) {
92-
Map<String, String> dimensions = new HashMap<>();
93-
dimensions.put(Metric.DIMENSION_KEY_ACTION_TYPE, "ProviderLogDelivery");
94-
dimensions.put(Metric.DIMENSION_KEY_EXCEPTION_TYPE, e.getClass().toString());
95-
dimensions.put(Metric.DIMENSION_KEY_RESOURCE_TYPE, this.getResourceTypeName());
96-
97-
publishMetric(Metric.METRIC_NAME_HANDLER_EXCEPTION, dimensions, StandardUnit.COUNT, 1.0, timestamp);
99+
publishBulkMetrics(
100+
MetricDatum.builder().metricName(Metric.METRIC_NAME_HANDLER_EXCEPTION).unit(StandardUnit.COUNT).value(1.0)
101+
.dimensions(Sets.newHashSet(
102+
Dimension.builder().name(Metric.DIMENSION_KEY_ACTION_TYPE).value("ProviderLogDelivery").build(),
103+
Dimension.builder().name(Metric.DIMENSION_KEY_EXCEPTION_TYPE).value(e.getClass().toString()).build(),
104+
Dimension.builder().name(Metric.DIMENSION_KEY_RESOURCE_TYPE).value(this.getResourceTypeName()).build()))
105+
.timestamp(timestamp).build());
98106
}
99107

100108
@Override
101109
public void publishInvocationMetric(final Instant timestamp, final Action action) {
102-
Map<String, String> dimensions = new HashMap<>();
103-
dimensions.put(Metric.DIMENSION_KEY_ACTION_TYPE, action == null ? "NO_ACTION" : action.name());
104-
dimensions.put(Metric.DIMENSION_KEY_RESOURCE_TYPE, this.getResourceTypeName());
105-
106-
publishMetric(Metric.METRIC_NAME_HANDLER_INVOCATION_COUNT, dimensions, StandardUnit.COUNT, 1.0, timestamp);
110+
publishBulkMetrics(
111+
MetricDatum.builder().metricName(Metric.METRIC_NAME_HANDLER_INVOCATION_COUNT).unit(StandardUnit.COUNT).value(1.0)
112+
.dimensions(Sets.newHashSet(
113+
Dimension.builder().name(Metric.DIMENSION_KEY_ACTION_TYPE).value(action == null ? "NO_ACTION" : action.name())
114+
.build(),
115+
Dimension.builder().name(Metric.DIMENSION_KEY_RESOURCE_TYPE).value(this.getResourceTypeName()).build()))
116+
.timestamp(timestamp).build());
107117
}
108118

109119
@Override
110120
public void publishDurationMetric(final Instant timestamp, final Action action, final long milliseconds) {
111-
Map<String, String> dimensions = new HashMap<>();
112-
dimensions.put(Metric.DIMENSION_KEY_ACTION_TYPE, action == null ? "NO_ACTION" : action.name());
113-
dimensions.put(Metric.DIMENSION_KEY_RESOURCE_TYPE, this.getResourceTypeName());
114-
115-
publishMetric(Metric.METRIC_NAME_HANDLER_DURATION, dimensions, StandardUnit.MILLISECONDS, (double) milliseconds,
116-
timestamp);
121+
publishBulkMetrics(MetricDatum.builder().metricName(Metric.METRIC_NAME_HANDLER_DURATION).unit(StandardUnit.MILLISECONDS)
122+
.value((double) milliseconds)
123+
.dimensions(Sets.newHashSet(
124+
Dimension.builder().name(Metric.DIMENSION_KEY_ACTION_TYPE).value(action == null ? "NO_ACTION" : action.name())
125+
.build(),
126+
Dimension.builder().name(Metric.DIMENSION_KEY_RESOURCE_TYPE).value(this.getResourceTypeName()).build()))
127+
.timestamp(timestamp).build());
117128
}
118129

119-
private void publishMetric(final String metricName,
120-
final Map<String, String> dimensionData,
121-
final StandardUnit unit,
122-
final Double value,
123-
final Instant timestamp) {
130+
private void publishBulkMetrics(final MetricDatum... metricData) {
124131
assert cloudWatchClient != null : "CloudWatchEventsClient was not initialised. You must call refreshClient() first.";
125132

126-
List<Dimension> dimensions = new ArrayList<>();
127-
for (Map.Entry<String, String> kvp : dimensionData.entrySet()) {
128-
Dimension dimension = Dimension.builder().name(kvp.getKey()).value(kvp.getValue()).build();
129-
dimensions.add(dimension);
130-
}
131-
132-
MetricDatum metricDatum = MetricDatum.builder().metricName(metricName).unit(unit).value(value).dimensions(dimensions)
133-
.timestamp(timestamp).build();
134-
135-
PutMetricDataRequest putMetricDataRequest = PutMetricDataRequest.builder()
136-
.namespace(String.format("%s/%s", Metric.METRIC_NAMESPACE_ROOT, resourceNamespace)).metricData(metricDatum).build();
137-
138133
try {
139-
this.cloudWatchClient.putMetricData(putMetricDataRequest);
134+
this.cloudWatchClient.putMetricData(
135+
PutMetricDataRequest.builder().namespace(String.format("%s/%s", Metric.METRIC_NAMESPACE_ROOT, resourceNamespace))
136+
.metricData(metricData).build());
140137
} catch (final Exception e) {
141138
log(String.format("An error occurred while publishing metrics: %s", e.getMessage()));
142139
}

src/main/java/software/amazon/cloudformation/proxy/MetricsPublisherProxy.java

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,11 @@ public void publishExceptionMetric(final Instant timestamp,
3535
.forEach(metricsPublisher -> metricsPublisher.publishExceptionMetric(timestamp, action, e, handlerErrorCode));
3636
}
3737

38-
public void publishExceptionByErrorCodeMetric(final Instant timestamp,
39-
final Action action,
40-
final HandlerErrorCode handlerErrorCode,
41-
final boolean thrown) {
42-
metricsPublishers.stream().forEach(
43-
metricsPublisher -> metricsPublisher.publishExceptionByErrorCodeMetric(timestamp, action, handlerErrorCode, thrown));
44-
}
45-
46-
public void publishExceptionCountMetric(final Instant timestamp, final Action action, final boolean thrown) {
47-
metricsPublishers.stream()
48-
.forEach(metricsPublisher -> metricsPublisher.publishExceptionCountMetric(timestamp, action, thrown));
38+
public void publishExceptionByErrorCodeAndCountBulkMetrics(final Instant timestamp,
39+
final Action action,
40+
final HandlerErrorCode handlerErrorCode) {
41+
metricsPublishers.stream().forEach(metricsPublisher -> metricsPublisher
42+
.publishExceptionByErrorCodeAndCountBulkMetrics(timestamp, action, handlerErrorCode));
4943
}
5044

5145
public void publishInvocationMetric(final Instant timestamp, final Action action) {

src/test/java/software/amazon/cloudformation/LambdaWrapperTest.java

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,8 @@ public void invokeHandler_nullResponse_returnsFailure(final String requestDataPa
161161
// validation failure metric should be published for final error handling
162162
verify(providerMetricsPublisher).publishExceptionMetric(any(Instant.class), any(), any(TerminalException.class),
163163
any(HandlerErrorCode.class));
164-
verify(providerMetricsPublisher).publishExceptionByErrorCodeMetric(any(Instant.class), any(),
165-
any(HandlerErrorCode.class), eq(Boolean.TRUE));
166-
verify(providerMetricsPublisher).publishExceptionCountMetric(any(Instant.class), any(), any(Boolean.class));
164+
verify(providerMetricsPublisher).publishExceptionByErrorCodeAndCountBulkMetrics(any(Instant.class), any(),
165+
any(HandlerErrorCode.class));
167166

168167
// all metrics should be published even on terminal failure
169168
verify(providerMetricsPublisher).publishInvocationMetric(any(Instant.class), eq(action));
@@ -215,8 +214,10 @@ providerEventsLogger, providerMetricsPublisher, new Validator() {
215214
verifyInitialiseRuntime();
216215

217216
// verify output response
218-
verifyHandlerResponse(out, ProgressEvent.<TestModel, TestContext>builder().errorCode(HandlerErrorCode.InvalidRequest)
219-
.status(OperationStatus.FAILED).message("Resource properties validation failed with invalid configuration").build());
217+
verifyHandlerResponse(out,
218+
ProgressEvent.<TestModel, TestContext>builder().errorCode(HandlerErrorCode.InvalidRequest)
219+
.status(OperationStatus.FAILED).message("Resource properties validation failed with invalid configuration")
220+
.build());
220221
}
221222
}
222223

@@ -443,9 +444,8 @@ public void invokeHandler_InProgress_returnsInProgress(final String requestDataP
443444

444445
}
445446

446-
verify(providerMetricsPublisher, atLeastOnce()).publishExceptionByErrorCodeMetric(any(Instant.class), eq(action),
447-
any(), any(Boolean.class));
448-
verify(providerMetricsPublisher).publishExceptionCountMetric(any(Instant.class), any(), any(Boolean.class));
447+
verify(providerMetricsPublisher).publishExceptionByErrorCodeAndCountBulkMetrics(any(Instant.class), eq(action),
448+
any());
449449

450450
// validation failure metric should not be published
451451
verifyNoMoreInteractions(providerMetricsPublisher);
@@ -488,9 +488,8 @@ public void reInvokeHandler_InProgress_returnsInProgress(final String requestDat
488488
// all metrics should be published, once for a single invocation
489489
verify(providerMetricsPublisher).publishInvocationMetric(any(Instant.class), eq(action));
490490
verify(providerMetricsPublisher).publishDurationMetric(any(Instant.class), eq(action), anyLong());
491-
verify(providerMetricsPublisher, atLeastOnce()).publishExceptionByErrorCodeMetric(any(Instant.class), eq(action),
492-
any(), eq(Boolean.FALSE));
493-
verify(providerMetricsPublisher).publishExceptionCountMetric(any(Instant.class), eq(action), eq(Boolean.FALSE));
491+
verify(providerMetricsPublisher).publishExceptionByErrorCodeAndCountBulkMetrics(any(Instant.class), eq(action),
492+
any());
494493

495494
// validation failure metric should not be published
496495
verifyNoMoreInteractions(providerMetricsPublisher);
@@ -875,11 +874,8 @@ public void invokeHandler_metricPublisherThrowable_returnsFailureResponse() thro
875874
// verify initialiseRuntime was called and initialised dependencies
876875
verifyInitialiseRuntime();
877876

878-
verify(providerMetricsPublisher, atLeastOnce()).publishExceptionByErrorCodeMetric(any(Instant.class),
879-
any(Action.class), any(HandlerErrorCode.class), any(Boolean.class));
880-
881-
verify(providerMetricsPublisher).publishExceptionCountMetric(any(Instant.class), any(Action.class),
882-
any(Boolean.class));
877+
verify(providerMetricsPublisher).publishExceptionByErrorCodeAndCountBulkMetrics(any(Instant.class), any(Action.class),
878+
any(HandlerErrorCode.class));
883879

884880
// no further calls to metrics publisher should occur
885881
verifyNoMoreInteractions(providerMetricsPublisher);

0 commit comments

Comments
 (0)