diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/SyncClientClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/SyncClientClass.java index 3be04d2362dc..8be3bfc480fa 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/SyncClientClass.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/SyncClientClass.java @@ -28,11 +28,11 @@ import java.net.URI; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import javax.lang.model.element.Modifier; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.awscore.client.config.AwsClientOption; -import software.amazon.awssdk.awscore.metrics.AwsCoreMetric; import software.amazon.awssdk.codegen.docs.SimpleMethodOverload; import software.amazon.awssdk.codegen.emitters.GeneratorTaskParams; import software.amazon.awssdk.codegen.model.config.customization.UtilitiesMethod; @@ -53,6 +53,8 @@ import software.amazon.awssdk.core.client.handler.SyncClientHandler; import software.amazon.awssdk.core.endpointdiscovery.EndpointDiscoveryRefreshCache; import software.amazon.awssdk.core.endpointdiscovery.EndpointDiscoveryRequest; +import software.amazon.awssdk.core.internal.util.MetricUtils; +import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.metrics.MetricPublisher; @@ -194,9 +196,9 @@ private List operationMethodSpecs(OperationModel opModel) { method.addStatement("$1T $2N = $1T.create($3S)", MetricCollector.class, metricCollectorName, "ApiCall"); - method.addStatement("$N.reportMetric($T.$L, $S)", metricCollectorName, AwsCoreMetric.class, "SERVICE_ID", + method.addStatement("$N.reportMetric($T.$L, $S)", metricCollectorName, CoreMetric.class, "SERVICE_ID", model.getMetadata().getServiceId()); - method.addStatement("$N.reportMetric($T.$L, $S)", metricCollectorName, AwsCoreMetric.class, "OPERATION_NAME", + method.addStatement("$N.reportMetric($T.$L, $S)", metricCollectorName, CoreMetric.class, "OPERATION_NAME", opModel.getOperationName()); String publisherName = "metricPublisher"; @@ -205,11 +207,13 @@ private List operationMethodSpecs(OperationModel opModel) { .addCode(protocolSpec.executionHandler(opModel)) .endControlFlow() .beginControlFlow("finally") - .addStatement("$T $N = clientConfiguration.option($T.$L)", - MetricPublisher.class, publisherName, SdkClientOption.class, "METRIC_PUBLISHER") - .beginControlFlow("if ($N != null)", publisherName) - .addStatement("$N.publish($N.collect())", publisherName, metricCollectorName) - .endControlFlow() + .addStatement("$T<$T> $N = $T.resolvePublisher(clientConfiguration, $N)", + Optional.class, + MetricPublisher.class, + publisherName, + MetricUtils.class, + opModel.getInput().getVariableName()) + .addStatement("$N.ifPresent(p -> p.publish($N.collect()))", publisherName, metricCollectorName) .endControlFlow(); methods.add(method.build()); diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-endpoint-discovery-sync.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-endpoint-discovery-sync.java index cc9fe0dfe5ce..2a8761096264 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-endpoint-discovery-sync.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-endpoint-discovery-sync.java @@ -1,12 +1,12 @@ package software.amazon.awssdk.services.endpointdiscoverytest; import java.net.URI; +import java.util.Optional; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.awscore.client.config.AwsClientOption; import software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler; import software.amazon.awssdk.awscore.exception.AwsServiceException; -import software.amazon.awssdk.awscore.metrics.AwsCoreMetric; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.client.handler.ClientExecutionParams; @@ -15,6 +15,8 @@ import software.amazon.awssdk.core.endpointdiscovery.EndpointDiscoveryRequest; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.http.HttpResponseHandler; +import software.amazon.awssdk.core.internal.util.MetricUtils; +import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.protocols.json.AwsJsonProtocol; @@ -92,8 +94,8 @@ public DescribeEndpointsResponse describeEndpoints(DescribeEndpointsRequest desc HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); MetricCollector apiCallMetricCollector = MetricCollector.create("ApiCall"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.SERVICE_ID, "AwsEndpointDiscoveryTest"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.OPERATION_NAME, "DescribeEndpoints"); + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "AwsEndpointDiscoveryTest"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "DescribeEndpoints"); try { return clientHandler.execute(new ClientExecutionParams() @@ -102,10 +104,9 @@ public DescribeEndpointsResponse describeEndpoints(DescribeEndpointsRequest desc .withMetricCollector(apiCallMetricCollector) .withMarshaller(new DescribeEndpointsRequestMarshaller(protocolFactory))); } finally { - MetricPublisher metricPublisher = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHER); - if (metricPublisher != null) { - metricPublisher.publish(apiCallMetricCollector.collect()); - } + Optional metricPublisher = MetricUtils.resolvePublisher(clientConfiguration, + describeEndpointsRequest); + metricPublisher.ifPresent(p -> p.publish(apiCallMetricCollector.collect())); } } @@ -144,8 +145,8 @@ public TestDiscoveryIdentifiersRequiredResponse testDiscoveryIdentifiersRequired cachedEndpoint = endpointDiscoveryCache.get(key, endpointDiscoveryRequest); } MetricCollector apiCallMetricCollector = MetricCollector.create("ApiCall"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.SERVICE_ID, "AwsEndpointDiscoveryTest"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.OPERATION_NAME, "TestDiscoveryIdentifiersRequired"); + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "AwsEndpointDiscoveryTest"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "TestDiscoveryIdentifiersRequired"); try { return clientHandler @@ -155,10 +156,9 @@ public TestDiscoveryIdentifiersRequiredResponse testDiscoveryIdentifiersRequired .withInput(testDiscoveryIdentifiersRequiredRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new TestDiscoveryIdentifiersRequiredRequestMarshaller(protocolFactory))); } finally { - MetricPublisher metricPublisher = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHER); - if (metricPublisher != null) { - metricPublisher.publish(apiCallMetricCollector.collect()); - } + Optional metricPublisher = MetricUtils.resolvePublisher(clientConfiguration, + testDiscoveryIdentifiersRequiredRequest); + metricPublisher.ifPresent(p -> p.publish(apiCallMetricCollector.collect())); } } @@ -196,8 +196,8 @@ public TestDiscoveryOptionalResponse testDiscoveryOptional(TestDiscoveryOptional cachedEndpoint = endpointDiscoveryCache.get(key, endpointDiscoveryRequest); } MetricCollector apiCallMetricCollector = MetricCollector.create("ApiCall"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.SERVICE_ID, "AwsEndpointDiscoveryTest"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.OPERATION_NAME, "TestDiscoveryOptional"); + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "AwsEndpointDiscoveryTest"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "TestDiscoveryOptional"); try { return clientHandler.execute(new ClientExecutionParams() @@ -206,10 +206,9 @@ public TestDiscoveryOptionalResponse testDiscoveryOptional(TestDiscoveryOptional .withInput(testDiscoveryOptionalRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new TestDiscoveryOptionalRequestMarshaller(protocolFactory))); } finally { - MetricPublisher metricPublisher = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHER); - if (metricPublisher != null) { - metricPublisher.publish(apiCallMetricCollector.collect()); - } + Optional metricPublisher = MetricUtils.resolvePublisher(clientConfiguration, + testDiscoveryOptionalRequest); + metricPublisher.ifPresent(p -> p.publish(apiCallMetricCollector.collect())); } } @@ -247,8 +246,8 @@ public TestDiscoveryRequiredResponse testDiscoveryRequired(TestDiscoveryRequired cachedEndpoint = endpointDiscoveryCache.get(key, endpointDiscoveryRequest); } MetricCollector apiCallMetricCollector = MetricCollector.create("ApiCall"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.SERVICE_ID, "AwsEndpointDiscoveryTest"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.OPERATION_NAME, "TestDiscoveryRequired"); + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "AwsEndpointDiscoveryTest"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "TestDiscoveryRequired"); try { return clientHandler.execute(new ClientExecutionParams() @@ -257,10 +256,9 @@ public TestDiscoveryRequiredResponse testDiscoveryRequired(TestDiscoveryRequired .withInput(testDiscoveryRequiredRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new TestDiscoveryRequiredRequestMarshaller(protocolFactory))); } finally { - MetricPublisher metricPublisher = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHER); - if (metricPublisher != null) { - metricPublisher.publish(apiCallMetricCollector.collect()); - } + Optional metricPublisher = MetricUtils.resolvePublisher(clientConfiguration, + testDiscoveryRequiredRequest); + metricPublisher.ifPresent(p -> p.publish(apiCallMetricCollector.collect())); } } @@ -280,3 +278,4 @@ public void close() { clientHandler.close(); } } + diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-client-class.java index 67ac43ee3963..226eba0f0d41 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-client-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-json-client-class.java @@ -1,5 +1,6 @@ package software.amazon.awssdk.services.json; +import java.util.Optional; import java.util.function.Consumer; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; @@ -7,14 +8,14 @@ import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; import software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler; import software.amazon.awssdk.awscore.exception.AwsServiceException; -import software.amazon.awssdk.awscore.metrics.AwsCoreMetric; import software.amazon.awssdk.core.ApiName; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; -import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.client.handler.ClientExecutionParams; import software.amazon.awssdk.core.client.handler.SyncClientHandler; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.http.HttpResponseHandler; +import software.amazon.awssdk.core.internal.util.MetricUtils; +import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.core.runtime.transform.StreamingRequestMarshaller; import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.core.sync.RequestBody; @@ -118,8 +119,8 @@ public APostOperationResponse aPostOperation(APostOperationRequest aPostOperatio HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); MetricCollector apiCallMetricCollector = MetricCollector.create("ApiCall"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.SERVICE_ID, "Json Service"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.OPERATION_NAME, "APostOperation"); + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperation"); try { return clientHandler.execute(new ClientExecutionParams() @@ -128,10 +129,8 @@ public APostOperationResponse aPostOperation(APostOperationRequest aPostOperatio .withInput(aPostOperationRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new APostOperationRequestMarshaller(protocolFactory))); } finally { - MetricPublisher metricPublisher = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHER); - if (metricPublisher != null) { - metricPublisher.publish(apiCallMetricCollector.collect()); - } + Optional metricPublisher = MetricUtils.resolvePublisher(clientConfiguration, aPostOperationRequest); + metricPublisher.ifPresent(p -> p.publish(apiCallMetricCollector.collect())); } } @@ -168,8 +167,8 @@ public APostOperationWithOutputResponse aPostOperationWithOutput( HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); MetricCollector apiCallMetricCollector = MetricCollector.create("ApiCall"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.SERVICE_ID, "Json Service"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.OPERATION_NAME, "APostOperationWithOutput"); + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperationWithOutput"); try { return clientHandler @@ -179,10 +178,9 @@ public APostOperationWithOutputResponse aPostOperationWithOutput( .withMetricCollector(apiCallMetricCollector) .withMarshaller(new APostOperationWithOutputRequestMarshaller(protocolFactory))); } finally { - MetricPublisher metricPublisher = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHER); - if (metricPublisher != null) { - metricPublisher.publish(apiCallMetricCollector.collect()); - } + Optional metricPublisher = MetricUtils.resolvePublisher(clientConfiguration, + aPostOperationWithOutputRequest); + metricPublisher.ifPresent(p -> p.publish(apiCallMetricCollector.collect())); } } @@ -219,8 +217,8 @@ public GetWithoutRequiredMembersResponse getWithoutRequiredMembers( HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); MetricCollector apiCallMetricCollector = MetricCollector.create("ApiCall"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.SERVICE_ID, "Json Service"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.OPERATION_NAME, "GetWithoutRequiredMembers"); + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "GetWithoutRequiredMembers"); try { return clientHandler @@ -230,10 +228,9 @@ public GetWithoutRequiredMembersResponse getWithoutRequiredMembers( .withMetricCollector(apiCallMetricCollector) .withMarshaller(new GetWithoutRequiredMembersRequestMarshaller(protocolFactory))); } finally { - MetricPublisher metricPublisher = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHER); - if (metricPublisher != null) { - metricPublisher.publish(apiCallMetricCollector.collect()); - } + Optional metricPublisher = MetricUtils.resolvePublisher(clientConfiguration, + getWithoutRequiredMembersRequest); + metricPublisher.ifPresent(p -> p.publish(apiCallMetricCollector.collect())); } } @@ -266,8 +263,8 @@ public PaginatedOperationWithResultKeyResponse paginatedOperationWithResultKey( HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); MetricCollector apiCallMetricCollector = MetricCollector.create("ApiCall"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.SERVICE_ID, "Json Service"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.OPERATION_NAME, "PaginatedOperationWithResultKey"); + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PaginatedOperationWithResultKey"); try { return clientHandler @@ -277,10 +274,9 @@ public PaginatedOperationWithResultKeyResponse paginatedOperationWithResultKey( .withMetricCollector(apiCallMetricCollector) .withMarshaller(new PaginatedOperationWithResultKeyRequestMarshaller(protocolFactory))); } finally { - MetricPublisher metricPublisher = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHER); - if (metricPublisher != null) { - metricPublisher.publish(apiCallMetricCollector.collect()); - } + Optional metricPublisher = MetricUtils.resolvePublisher(clientConfiguration, + paginatedOperationWithResultKeyRequest); + metricPublisher.ifPresent(p -> p.publish(apiCallMetricCollector.collect())); } } @@ -391,8 +387,8 @@ public PaginatedOperationWithoutResultKeyResponse paginatedOperationWithoutResul HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); MetricCollector apiCallMetricCollector = MetricCollector.create("ApiCall"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.SERVICE_ID, "Json Service"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.OPERATION_NAME, "PaginatedOperationWithoutResultKey"); + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "PaginatedOperationWithoutResultKey"); try { return clientHandler @@ -402,10 +398,9 @@ public PaginatedOperationWithoutResultKeyResponse paginatedOperationWithoutResul .withMetricCollector(apiCallMetricCollector) .withMarshaller(new PaginatedOperationWithoutResultKeyRequestMarshaller(protocolFactory))); } finally { - MetricPublisher metricPublisher = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHER); - if (metricPublisher != null) { - metricPublisher.publish(apiCallMetricCollector.collect()); - } + Optional metricPublisher = MetricUtils.resolvePublisher(clientConfiguration, + paginatedOperationWithoutResultKeyRequest); + metricPublisher.ifPresent(p -> p.publish(apiCallMetricCollector.collect())); } } @@ -527,8 +522,8 @@ public StreamingInputOperationResponse streamingInputOperation(StreamingInputOpe HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); MetricCollector apiCallMetricCollector = MetricCollector.create("ApiCall"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.SERVICE_ID, "Json Service"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.OPERATION_NAME, "StreamingInputOperation"); + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingInputOperation"); try { return clientHandler @@ -544,10 +539,9 @@ public StreamingInputOperationResponse streamingInputOperation(StreamingInputOpe .delegateMarshaller(new StreamingInputOperationRequestMarshaller(protocolFactory)) .requestBody(requestBody).build())); } finally { - MetricPublisher metricPublisher = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHER); - if (metricPublisher != null) { - metricPublisher.publish(apiCallMetricCollector.collect()); - } + Optional metricPublisher = MetricUtils.resolvePublisher(clientConfiguration, + streamingInputOperationRequest); + metricPublisher.ifPresent(p -> p.publish(apiCallMetricCollector.collect())); } } @@ -601,8 +595,8 @@ public ReturnT streamingInputOutputOperation( HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); MetricCollector apiCallMetricCollector = MetricCollector.create("ApiCall"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.SERVICE_ID, "Json Service"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.OPERATION_NAME, "StreamingInputOutputOperation"); + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingInputOutputOperation"); try { return clientHandler.execute( @@ -620,10 +614,9 @@ public ReturnT streamingInputOutputOperation( new StreamingInputOutputOperationRequestMarshaller(protocolFactory)) .requestBody(requestBody).transferEncoding(true).build()), responseTransformer); } finally { - MetricPublisher metricPublisher = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHER); - if (metricPublisher != null) { - metricPublisher.publish(apiCallMetricCollector.collect()); - } + Optional metricPublisher = MetricUtils.resolvePublisher(clientConfiguration, + streamingInputOutputOperationRequest); + metricPublisher.ifPresent(p -> p.publish(apiCallMetricCollector.collect())); } } @@ -663,8 +656,8 @@ public ReturnT streamingOutputOperation(StreamingOutputOperationReques HttpResponseHandler errorResponseHandler = createErrorResponseHandler(protocolFactory, operationMetadata); MetricCollector apiCallMetricCollector = MetricCollector.create("ApiCall"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.SERVICE_ID, "Json Service"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.OPERATION_NAME, "StreamingOutputOperation"); + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Json Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingOutputOperation"); try { return clientHandler.execute( @@ -674,10 +667,9 @@ public ReturnT streamingOutputOperation(StreamingOutputOperationReques .withMetricCollector(apiCallMetricCollector) .withMarshaller(new StreamingOutputOperationRequestMarshaller(protocolFactory)), responseTransformer); } finally { - MetricPublisher metricPublisher = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHER); - if (metricPublisher != null) { - metricPublisher.publish(apiCallMetricCollector.collect()); - } + Optional metricPublisher = MetricUtils.resolvePublisher(clientConfiguration, + streamingOutputOperationRequest); + metricPublisher.ifPresent(p -> p.publish(apiCallMetricCollector.collect())); } } @@ -727,3 +719,4 @@ public JsonUtilities utilities() { return JsonUtilities.create(param1, param2, param3); } } + diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-query-client-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-query-client-class.java index c4c82282dd3c..0509a9c35eed 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-query-client-class.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-query-client-class.java @@ -1,16 +1,17 @@ package software.amazon.awssdk.services.query; +import java.util.Optional; import software.amazon.awssdk.annotations.Generated; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler; import software.amazon.awssdk.awscore.exception.AwsServiceException; -import software.amazon.awssdk.awscore.metrics.AwsCoreMetric; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; -import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.client.handler.ClientExecutionParams; import software.amazon.awssdk.core.client.handler.SyncClientHandler; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.http.HttpResponseHandler; +import software.amazon.awssdk.core.internal.util.MetricUtils; +import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.core.runtime.transform.StreamingRequestMarshaller; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.core.sync.ResponseTransformer; @@ -89,8 +90,8 @@ public APostOperationResponse aPostOperation(APostOperationRequest aPostOperatio HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); MetricCollector apiCallMetricCollector = MetricCollector.create("ApiCall"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.SERVICE_ID, "Query Service"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.OPERATION_NAME, "APostOperation"); + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperation"); try { return clientHandler.execute(new ClientExecutionParams() @@ -99,10 +100,8 @@ public APostOperationResponse aPostOperation(APostOperationRequest aPostOperatio .withInput(aPostOperationRequest).withMetricCollector(apiCallMetricCollector) .withMarshaller(new APostOperationRequestMarshaller(protocolFactory))); } finally { - MetricPublisher metricPublisher = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHER); - if (metricPublisher != null) { - metricPublisher.publish(apiCallMetricCollector.collect()); - } + Optional metricPublisher = MetricUtils.resolvePublisher(clientConfiguration, aPostOperationRequest); + metricPublisher.ifPresent(p -> p.publish(apiCallMetricCollector.collect())); } } @@ -136,8 +135,8 @@ public APostOperationWithOutputResponse aPostOperationWithOutput( HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); MetricCollector apiCallMetricCollector = MetricCollector.create("ApiCall"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.SERVICE_ID, "Query Service"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.OPERATION_NAME, "APostOperationWithOutput"); + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "APostOperationWithOutput"); try { return clientHandler @@ -147,10 +146,9 @@ public APostOperationWithOutputResponse aPostOperationWithOutput( .withMetricCollector(apiCallMetricCollector) .withMarshaller(new APostOperationWithOutputRequestMarshaller(protocolFactory))); } finally { - MetricPublisher metricPublisher = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHER); - if (metricPublisher != null) { - metricPublisher.publish(apiCallMetricCollector.collect()); - } + Optional metricPublisher = MetricUtils.resolvePublisher(clientConfiguration, + aPostOperationWithOutputRequest); + metricPublisher.ifPresent(p -> p.publish(apiCallMetricCollector.collect())); } } @@ -190,8 +188,8 @@ public StreamingInputOperationResponse streamingInputOperation(StreamingInputOpe HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); MetricCollector apiCallMetricCollector = MetricCollector.create("ApiCall"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.SERVICE_ID, "Query Service"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.OPERATION_NAME, "StreamingInputOperation"); + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingInputOperation"); try { return clientHandler @@ -207,10 +205,9 @@ public StreamingInputOperationResponse streamingInputOperation(StreamingInputOpe .delegateMarshaller(new StreamingInputOperationRequestMarshaller(protocolFactory)) .requestBody(requestBody).build())); } finally { - MetricPublisher metricPublisher = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHER); - if (metricPublisher != null) { - metricPublisher.publish(apiCallMetricCollector.collect()); - } + Optional metricPublisher = MetricUtils.resolvePublisher(clientConfiguration, + streamingInputOperationRequest); + metricPublisher.ifPresent(p -> p.publish(apiCallMetricCollector.collect())); } } @@ -247,8 +244,8 @@ public ReturnT streamingOutputOperation(StreamingOutputOperationReques HttpResponseHandler errorResponseHandler = protocolFactory.createErrorResponseHandler(); MetricCollector apiCallMetricCollector = MetricCollector.create("ApiCall"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.SERVICE_ID, "Query Service"); - apiCallMetricCollector.reportMetric(AwsCoreMetric.OPERATION_NAME, "StreamingOutputOperation"); + apiCallMetricCollector.reportMetric(CoreMetric.SERVICE_ID, "Query Service"); + apiCallMetricCollector.reportMetric(CoreMetric.OPERATION_NAME, "StreamingOutputOperation"); try { return clientHandler.execute( @@ -258,10 +255,9 @@ public ReturnT streamingOutputOperation(StreamingOutputOperationReques .withMetricCollector(apiCallMetricCollector) .withMarshaller(new StreamingOutputOperationRequestMarshaller(protocolFactory)), responseTransformer); } finally { - MetricPublisher metricPublisher = clientConfiguration.option(SdkClientOption.METRIC_PUBLISHER); - if (metricPublisher != null) { - metricPublisher.publish(apiCallMetricCollector.collect()); - } + Optional metricPublisher = MetricUtils.resolvePublisher(clientConfiguration, + streamingOutputOperationRequest); + metricPublisher.ifPresent(p -> p.publish(apiCallMetricCollector.collect())); } } @@ -279,3 +275,4 @@ public void close() { clientHandler.close(); } } + diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/handler/AwsClientHandlerUtils.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/handler/AwsClientHandlerUtils.java index eaaac5e52503..142a0e062873 100644 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/handler/AwsClientHandlerUtils.java +++ b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/client/handler/AwsClientHandlerUtils.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.nio.ByteBuffer; +import java.time.Duration; import java.util.Map; import java.util.stream.Collectors; import software.amazon.awssdk.annotations.SdkProtectedApi; @@ -42,9 +43,13 @@ import software.amazon.awssdk.core.interceptor.InterceptorContext; import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.core.internal.util.MetricUtils; +import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.utils.IoUtils; +import software.amazon.awssdk.utils.Pair; import software.amazon.awssdk.utils.Validate; import software.amazon.eventstream.HeaderValue; import software.amazon.eventstream.Message; @@ -69,7 +74,11 @@ static ExecutionContext .flatMap(AwsRequestOverrideConfiguration::credentialsProvider) .orElse(clientCredentials); - AwsCredentials credentials = credentialsProvider.resolveCredentials(); + Pair measuredCredentialsFetch = MetricUtils.measureDuration( + credentialsProvider::resolveCredentials); + AwsCredentials credentials = measuredCredentialsFetch.left(); + MetricCollector metricCollector = resolveMetricCollector(executionParams); + metricCollector.reportMetric(CoreMetric.CREDENTIALS_FETCH_DURATION, measuredCredentialsFetch.right()); Validate.validState(credentials != null, "Credential providers must never return null."); @@ -99,6 +108,7 @@ static ExecutionContext .build()) .executionAttributes(executionAttributes) .signer(computeSigner(originalRequest, clientConfig)) + .metricCollector(metricCollector) .build(); } @@ -133,4 +143,12 @@ private static Signer computeSigner(SdkRequest originalRequest, .flatMap(RequestOverrideConfiguration::signer) .orElse(clientConfiguration.option(AwsAdvancedClientOption.SIGNER)); } + + private static MetricCollector resolveMetricCollector(ClientExecutionParams params) { + MetricCollector metricCollector = params.getMetricCollector(); + if (metricCollector == null) { + metricCollector = MetricCollector.create("ApiCall"); + } + return metricCollector; + } } diff --git a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/metrics/AwsCoreMetric.java b/core/aws-core/src/main/java/software/amazon/awssdk/awscore/metrics/AwsCoreMetric.java deleted file mode 100644 index 5e387fdead5b..000000000000 --- a/core/aws-core/src/main/java/software/amazon/awssdk/awscore/metrics/AwsCoreMetric.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package software.amazon.awssdk.awscore.metrics; - -import software.amazon.awssdk.annotations.SdkPublicApi; -import software.amazon.awssdk.metrics.MetricCategory; -import software.amazon.awssdk.metrics.SdkMetric; - -/** - * The set of metrics collected for all SDK clients. - */ -@SdkPublicApi -public final class AwsCoreMetric { - - /** - * The unique ID for the service. This is present for all API call metrics. - */ - public static final SdkMetric SERVICE_ID = metric("ServiceName", String.class); - - /** - * The name of the service operation being invoked. This is present for all - * API call metrics. - */ - public static final SdkMetric OPERATION_NAME = metric("OperationName", String.class); - - private AwsCoreMetric() { - } - - private static SdkMetric metric(String name, Class clzz) { - return SdkMetric.create(name, clzz, MetricCategory.DEFAULT); - } -} diff --git a/core/metrics-spi/src/main/java/software/amazon/awssdk/metrics/MetricCollection.java b/core/metrics-spi/src/main/java/software/amazon/awssdk/metrics/MetricCollection.java index 581985d70029..8e19816aee2e 100644 --- a/core/metrics-spi/src/main/java/software/amazon/awssdk/metrics/MetricCollection.java +++ b/core/metrics-spi/src/main/java/software/amazon/awssdk/metrics/MetricCollection.java @@ -15,7 +15,6 @@ package software.amazon.awssdk.metrics; -import java.util.Collection; import java.util.List; import software.amazon.awssdk.annotations.SdkPublicApi; @@ -41,5 +40,5 @@ public interface MetricCollection extends Iterable> { /** * @return The child metric collections. */ - Collection children(); + List children(); } diff --git a/core/metrics-spi/src/main/java/software/amazon/awssdk/metrics/internal/DefaultMetricCollection.java b/core/metrics-spi/src/main/java/software/amazon/awssdk/metrics/internal/DefaultMetricCollection.java index e5cf6f9c953d..05bc6bbc8ed1 100644 --- a/core/metrics-spi/src/main/java/software/amazon/awssdk/metrics/internal/DefaultMetricCollection.java +++ b/core/metrics-spi/src/main/java/software/amazon/awssdk/metrics/internal/DefaultMetricCollection.java @@ -15,7 +15,6 @@ package software.amazon.awssdk.metrics.internal; -import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -30,15 +29,15 @@ public final class DefaultMetricCollection implements MetricCollection { private final String name; private final Map, List>> metrics; - private final Collection children; + private final List children; public DefaultMetricCollection(String name, Map, List>> metrics, - Collection children) { + List children) { this.name = name; this.metrics = metrics; - this.children = children != null ? Collections.unmodifiableCollection(children) : Collections.emptyList(); + this.children = children != null ? Collections.unmodifiableList(children) : Collections.emptyList(); } @Override @@ -60,7 +59,7 @@ public List metricValues(SdkMetric metric) { } @Override - public Collection children() { + public List children() { return children; } diff --git a/core/metrics-spi/src/main/java/software/amazon/awssdk/metrics/internal/util/MetricUtil.java b/core/metrics-spi/src/main/java/software/amazon/awssdk/metrics/internal/util/MetricUtil.java deleted file mode 100644 index b97e4b38a8ff..000000000000 --- a/core/metrics-spi/src/main/java/software/amazon/awssdk/metrics/internal/util/MetricUtil.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package software.amazon.awssdk.metrics.internal.util; - -import java.time.Duration; -import java.util.concurrent.Callable; -import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.utils.Pair; - -@SdkInternalApi -public final class MetricUtil { - - private MetricUtil() { - } - - public static Pair measureDuration(Callable c) { - long start = System.nanoTime(); - - T result; - - try { - result = c.call(); - } catch (Exception e) { - throw new RuntimeException(e); - } - - Duration d = Duration.ofNanos(System.nanoTime() - start); - - return Pair.of(result, d); - } -} diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java index 22867dc1d43e..e60763b964e2 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java @@ -28,6 +28,7 @@ import software.amazon.awssdk.annotations.Immutable; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.Validate; @@ -44,6 +45,7 @@ public abstract class RequestOverrideConfiguration { private final Duration apiCallTimeout; private final Duration apiCallAttemptTimeout; private final Signer signer; + private final MetricPublisher metricPublisher; protected RequestOverrideConfiguration(Builder builder) { this.headers = CollectionUtils.deepUnmodifiableMap(builder.headers(), () -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)); @@ -52,6 +54,7 @@ protected RequestOverrideConfiguration(Builder builder) { this.apiCallTimeout = Validate.isPositiveOrNull(builder.apiCallTimeout(), "apiCallTimeout"); this.apiCallAttemptTimeout = Validate.isPositiveOrNull(builder.apiCallAttemptTimeout(), "apiCallAttemptTimeout"); this.signer = builder.signer(); + this.metricPublisher = builder.metricPublisher(); } /** @@ -127,6 +130,14 @@ public Optional signer() { return Optional.ofNullable(signer); } + /** + * Return the metric publisher for publishing the metrics collected for this request. This publisher supersedes the + * metric publisher set on the client. + */ + public Optional metricPublisher() { + return Optional.ofNullable(metricPublisher); + } + @Override public boolean equals(Object o) { if (this == o) { @@ -141,7 +152,8 @@ public boolean equals(Object o) { Objects.equals(apiNames, that.apiNames) && Objects.equals(apiCallTimeout, that.apiCallTimeout) && Objects.equals(apiCallAttemptTimeout, that.apiCallAttemptTimeout) && - Objects.equals(signer, that.signer); + Objects.equals(signer, that.signer) && + Objects.equals(metricPublisher, that.metricPublisher); } @Override @@ -153,6 +165,7 @@ public int hashCode() { hashCode = 31 * hashCode + Objects.hashCode(apiCallTimeout); hashCode = 31 * hashCode + Objects.hashCode(apiCallAttemptTimeout); hashCode = 31 * hashCode + Objects.hashCode(signer); + hashCode = 31 * hashCode + Objects.hashCode(metricPublisher); return hashCode; } @@ -339,6 +352,17 @@ default B putRawQueryParameter(String name, String value) { Signer signer(); + /** + * Sets the metric publisher for publishing the metrics collected for this request. This publisher supersedes + * the metric publisher set on the client. + * + * @param metricPublisher The metric publisher for this request. + * @return This object for method chaining. + */ + B metricPublisher(MetricPublisher metricPublisher); + + MetricPublisher metricPublisher(); + /** * Create a new {@code SdkRequestOverrideConfiguration} with the properties set on this builder. * @@ -354,6 +378,7 @@ protected abstract static class BuilderImpl implements Builde private Duration apiCallTimeout; private Duration apiCallAttemptTimeout; private Signer signer; + private MetricPublisher metricPublisher; protected BuilderImpl() { } @@ -470,5 +495,20 @@ public void setSigner(Signer signer) { public Signer signer() { return signer; } + + @Override + public B metricPublisher(MetricPublisher metricPublisher) { + this.metricPublisher = metricPublisher; + return (B) this; + } + + public void setMetricPublisher(MetricPublisher metricPublisher) { + metricPublisher(metricPublisher); + } + + @Override + public MetricPublisher metricPublisher() { + return metricPublisher; + } } } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java index c4194399f976..4dde45af566c 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java @@ -366,6 +366,7 @@ public final B overrideConfiguration(ClientOverrideConfiguration overrideConfig) overrideConfig.advancedOption(DISABLE_HOST_PREFIX_INJECTION).orElse(null)); clientConfiguration.option(PROFILE_FILE, overrideConfig.defaultProfileFile().orElse(null)); clientConfiguration.option(PROFILE_NAME, overrideConfig.defaultProfileName().orElse(null)); + clientConfiguration.option(METRIC_PUBLISHER, overrideConfig.metricPublisher().orElse(null)); return thisBuilder(); } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/ClientOverrideConfiguration.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/ClientOverrideConfiguration.java index 40d6a59ad8e5..ec70e54e1892 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/ClientOverrideConfiguration.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/ClientOverrideConfiguration.java @@ -29,6 +29,7 @@ import software.amazon.awssdk.core.retry.RetryMode; import software.amazon.awssdk.core.retry.RetryPolicy; import software.amazon.awssdk.core.sync.ResponseTransformer; +import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.profiles.ProfileFileSystemSetting; import software.amazon.awssdk.utils.AttributeMap; @@ -55,6 +56,7 @@ public final class ClientOverrideConfiguration private final Duration apiCallTimeout; private final ProfileFile defaultProfileFile; private final String defaultProfileName; + private final MetricPublisher metricPublisher; /** * Initialize this configuration. Private to require use of {@link #builder()}. @@ -68,6 +70,7 @@ private ClientOverrideConfiguration(Builder builder) { this.apiCallAttemptTimeout = Validate.isPositiveOrNull(builder.apiCallAttemptTimeout(), "apiCallAttemptTimeout"); this.defaultProfileFile = builder.defaultProfileFile(); this.defaultProfileName = builder.defaultProfileName(); + this.metricPublisher = builder.metricPublisher(); } @Override @@ -184,6 +187,15 @@ public Optional defaultProfileName() { return Optional.ofNullable(defaultProfileName); } + /** + * The metric publisher to use to publisher metrics collected for this client. + * + * @return The metric publisher. + */ + public Optional metricPublisher() { + return Optional.ofNullable(metricPublisher); + } + @Override public String toString() { return ToString.builder("ClientOverrideConfiguration") @@ -408,6 +420,16 @@ default Builder retryPolicy(RetryMode retryMode) { Builder defaultProfileName(String defaultProfileName); String defaultProfileName(); + + /** + * Set the metric publisher to use for publishing metrics collected fo this client. + * + * @param metricPublisher The metric publisher to use. + * @return This object for method chaining. + */ + Builder metricPublisher(MetricPublisher metricPublisher); + + MetricPublisher metricPublisher(); } /** @@ -422,6 +444,7 @@ private static final class DefaultClientOverrideConfigurationBuilder implements private Duration apiCallAttemptTimeout; private ProfileFile defaultProfileFile; private String defaultProfileName; + private MetricPublisher metricPublisher; @Override public Builder headers(Map> headers) { @@ -563,6 +586,17 @@ public Builder defaultProfileName(String defaultProfileName) { return this; } + @Override + public Builder metricPublisher(MetricPublisher metricPublisher) { + this.metricPublisher = metricPublisher; + return this; + } + + @Override + public MetricPublisher metricPublisher() { + return metricPublisher; + } + @Override public ClientOverrideConfiguration build() { return new ClientOverrideConfiguration(this); diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/http/ExecutionContext.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/http/ExecutionContext.java index e9375a75e14d..37fb230b5c9c 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/http/ExecutionContext.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/http/ExecutionContext.java @@ -21,6 +21,7 @@ import software.amazon.awssdk.core.interceptor.ExecutionInterceptorChain; import software.amazon.awssdk.core.interceptor.InterceptorContext; import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.utils.builder.CopyableBuilder; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; @@ -34,12 +35,14 @@ public final class ExecutionContext implements ToCopyableBuilder InterceptorContext finalizeSdkHttpFu SdkClientConfiguration clientConfiguration) { runBeforeMarshallingInterceptors(executionContext); - SdkHttpFullRequest request = executionParams.getMarshaller().marshall(inputT); + + Pair measuredMarshall = MetricUtils.measureDuration(() -> + executionParams.getMarshaller().marshall(inputT)); + + executionContext.metricCollector().reportMetric(CoreMetric.MARSHALLING_DURATION, measuredMarshall.right()); + + SdkHttpFullRequest request = measuredMarshall.left(); + request = modifyEndpointHostIfNeeded(request, clientConfiguration, executionParams); addHttpRequest(executionContext, request); @@ -177,6 +189,8 @@ protected ExecutionCont ExecutionInterceptorChain interceptorChain = new ExecutionInterceptorChain(clientConfiguration.option(SdkClientOption.EXECUTION_INTERCEPTORS)); + MetricCollector metricCollector = resolveMetricCollector(params); + return ExecutionContext.builder() .interceptorChain(interceptorChain) .interceptorContext(InterceptorContext.builder() @@ -184,6 +198,7 @@ protected ExecutionCont .build()) .executionAttributes(executionAttributes) .signer(clientConfiguration.option(SdkAdvancedClientOption.SIGNER)) + .metricCollector(metricCollector) .build(); } @@ -271,4 +286,12 @@ private static BiFunction composeResponseFunctions(BiFunction function2) { return (x, y) -> function2.apply(function1.apply(x, y), y); } + + private MetricCollector resolveMetricCollector(ClientExecutionParams params) { + MetricCollector metricCollector = params.getMetricCollector(); + if (metricCollector == null) { + metricCollector = MetricCollector.create("ApiCall"); + } + return metricCollector; + } } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonSyncHttpClient.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonSyncHttpClient.java index 38feba879847..91e24c798f31 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonSyncHttpClient.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonSyncHttpClient.java @@ -27,7 +27,9 @@ import software.amazon.awssdk.core.internal.http.pipeline.RequestPipelineBuilder; import software.amazon.awssdk.core.internal.http.pipeline.stages.AfterExecutionInterceptorsStage; import software.amazon.awssdk.core.internal.http.pipeline.stages.AfterTransmissionExecutionInterceptorsStage; +import software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallAttemptMetricCollectionStage; import software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallAttemptTimeoutTrackingStage; +import software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallMetricCollectionStage; import software.amazon.awssdk.core.internal.http.pipeline.stages.ApiCallTimeoutTrackingStage; import software.amazon.awssdk.core.internal.http.pipeline.stages.ApplyTransactionIdStage; import software.amazon.awssdk.core.internal.http.pipeline.stages.ApplyUserAgentStage; @@ -179,9 +181,11 @@ public OutputT execute(HttpResponseHandler> response .then(() -> new HandleResponseStage<>(responseHandler)) .wrappedWith(ApiCallAttemptTimeoutTrackingStage::new) .wrappedWith(TimeoutExceptionHandlingStage::new) + .wrappedWith((deps, wrapped) -> new ApiCallAttemptMetricCollectionStage<>(wrapped)) .wrappedWith(RetryableStage::new)::build) .wrappedWith(StreamManagingStage::new) .wrappedWith(ApiCallTimeoutTrackingStage::new)::build) + .wrappedWith((deps, wrapped) -> new ApiCallMetricCollectionStage<>(wrapped)) .then(() -> new UnwrapResponseContainer<>()) .then(() -> new AfterExecutionInterceptorsStage<>()) .wrappedWith(ExecutionFailureExceptionReportingStage::new) diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/RequestExecutionContext.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/RequestExecutionContext.java index b84afb9512dc..d49f0cb0754f 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/RequestExecutionContext.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/RequestExecutionContext.java @@ -26,6 +26,7 @@ import software.amazon.awssdk.core.internal.http.pipeline.RequestPipeline; import software.amazon.awssdk.core.internal.http.timers.TimeoutTracker; import software.amazon.awssdk.core.signer.Signer; +import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.utils.Validate; /** @@ -41,6 +42,7 @@ public final class RequestExecutionContext { private final ExecutionContext executionContext; private TimeoutTracker apiCallTimeoutTracker; private TimeoutTracker apiCallAttemptTimeoutTracker; + private MetricCollector metricCollector; private RequestExecutionContext(Builder builder) { this.requestProvider = builder.requestProvider; @@ -115,6 +117,14 @@ public void apiCallAttemptTimeoutTracker(TimeoutTracker timeoutTracker) { this.apiCallAttemptTimeoutTracker = timeoutTracker; } + public MetricCollector metricCollector() { + return metricCollector; + } + + public void metricCollector(MetricCollector metricCollector) { + this.metricCollector = metricCollector; + } + /** * Sets the request body provider. * Used for transforming the original body provider to sign events for diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApiCallAttemptMetricCollectionStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApiCallAttemptMetricCollectionStage.java new file mode 100644 index 000000000000..32c87e552b08 --- /dev/null +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApiCallAttemptMetricCollectionStage.java @@ -0,0 +1,74 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.core.internal.http.pipeline.stages; + +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.core.Response; +import software.amazon.awssdk.core.internal.http.RequestExecutionContext; +import software.amazon.awssdk.core.internal.http.pipeline.RequestPipeline; +import software.amazon.awssdk.core.internal.http.pipeline.RequestToResponsePipeline; +import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.SdkHttpFullResponse; +import software.amazon.awssdk.metrics.MetricCollector; + +/** + * Wrapper pipeline that initializes and tracks the API call attempt metric collection. This wrapper and any wrapped + * stages will track API call attempt metrics. + */ +@SdkInternalApi +public final class ApiCallAttemptMetricCollectionStage implements RequestToResponsePipeline { + private final RequestPipeline> wrapped; + + public ApiCallAttemptMetricCollectionStage(RequestPipeline> wrapped) { + this.wrapped = wrapped; + } + + @Override + public Response execute(SdkHttpFullRequest input, RequestExecutionContext context) throws Exception { + MetricCollector apiCallAttemptMetrics = createAttemptMetricsCollector(context); + context.metricCollector(apiCallAttemptMetrics); + + try { + Response response = wrapped.execute(input, context); + + collectHttpMetrics(apiCallAttemptMetrics, response.httpResponse()); + + if (!response.isSuccess() && response.exception() != null) { + apiCallAttemptMetrics.reportMetric(CoreMetric.EXCEPTION, response.exception()); + } + + return response; + } catch (Throwable t) { + apiCallAttemptMetrics.reportMetric(CoreMetric.EXCEPTION, t); + throw t; + } + } + + private MetricCollector createAttemptMetricsCollector(RequestExecutionContext context) { + return context.executionContext() + .metricCollector() + .createChild("ApiCallAttemptMetrics"); + } + + private void collectHttpMetrics(MetricCollector metricCollector, SdkHttpFullResponse httpResponse) { + metricCollector.reportMetric(CoreMetric.HTTP_STATUS_CODE, httpResponse.statusCode()); + httpResponse.firstMatchingHeader("x-amz-request-id") + .ifPresent(v -> metricCollector.reportMetric(CoreMetric.AWS_REQUEST_ID, v)); + httpResponse.firstMatchingHeader("x-amz-id-2") + .ifPresent(v -> metricCollector.reportMetric(CoreMetric.AWS_EXTENDED_REQUEST_ID, v)); + } +} diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApiCallMetricCollectionStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApiCallMetricCollectionStage.java new file mode 100644 index 000000000000..a54fd9678376 --- /dev/null +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApiCallMetricCollectionStage.java @@ -0,0 +1,54 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.core.internal.http.pipeline.stages; + +import java.time.Duration; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.core.Response; +import software.amazon.awssdk.core.internal.http.RequestExecutionContext; +import software.amazon.awssdk.core.internal.http.pipeline.RequestPipeline; +import software.amazon.awssdk.core.internal.http.pipeline.RequestToResponsePipeline; +import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.metrics.MetricCollector; + +/** + * Wrapper pipeline that tracks the {@link CoreMetric#API_CALL_DURATION} metric. + */ +@SdkInternalApi +public class ApiCallMetricCollectionStage implements RequestToResponsePipeline { + private final RequestPipeline> wrapped; + + public ApiCallMetricCollectionStage(RequestPipeline> wrapped) { + this.wrapped = wrapped; + } + + @Override + public Response execute(SdkHttpFullRequest input, RequestExecutionContext context) throws Exception { + MetricCollector metricCollector = context.executionContext().metricCollector(); + + // Note: at this point, any exception, even a service exception, will + // be thrown from the wrapped pipeline so we can't use + // MetricUtil.measureDuration() + long callStart = System.nanoTime(); + try { + return wrapped.execute(input, context); + } finally { + long d = System.nanoTime() - callStart; + metricCollector.reportMetric(CoreMetric.API_CALL_DURATION, Duration.ofNanos(d)); + } + } +} diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeHttpRequestStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeHttpRequestStage.java index 05dbf2026d66..0ed4e1ab9db2 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeHttpRequestStage.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeHttpRequestStage.java @@ -15,18 +15,22 @@ package software.amazon.awssdk.core.internal.http.pipeline.stages; +import java.time.Duration; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.core.client.config.SdkClientOption; import software.amazon.awssdk.core.internal.http.HttpClientDependencies; import software.amazon.awssdk.core.internal.http.InterruptMonitor; import software.amazon.awssdk.core.internal.http.RequestExecutionContext; import software.amazon.awssdk.core.internal.http.pipeline.RequestPipeline; +import software.amazon.awssdk.core.internal.util.MetricUtils; +import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.http.ExecutableHttpRequest; import software.amazon.awssdk.http.HttpExecuteRequest; import software.amazon.awssdk.http.HttpExecuteResponse; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpFullResponse; +import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.utils.Pair; /** @@ -63,6 +67,14 @@ private HttpExecuteResponse executeHttpRequest(SdkHttpFullRequest request, Reque context.apiCallTimeoutTracker().abortable(requestCallable); context.apiCallAttemptTimeoutTracker().abortable(requestCallable); - return requestCallable.call(); + + MetricCollector metricCollector = context.metricCollector(); + + Pair measuredExecute = MetricUtils.measureDurationUnsafe(requestCallable); + + metricCollector.reportMetric(CoreMetric.HTTP_REQUEST_ROUND_TRIP_TIME, measuredExecute.right()); + + return measuredExecute.left(); } + } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStage.java index f436cbb91139..9ac1789b0830 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStage.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/SigningStage.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.core.internal.http.pipeline.stages; +import java.time.Duration; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.http.ExecutionContext; @@ -24,9 +25,13 @@ import software.amazon.awssdk.core.internal.http.InterruptMonitor; import software.amazon.awssdk.core.internal.http.RequestExecutionContext; import software.amazon.awssdk.core.internal.http.pipeline.RequestToRequestPipeline; +import software.amazon.awssdk.core.internal.util.MetricUtils; +import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.core.signer.AsyncRequestBodySigner; import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.utils.Pair; /** * Sign the marshalled request (if applicable). @@ -52,15 +57,21 @@ public SdkHttpFullRequest execute(SdkHttpFullRequest request, RequestExecutionCo /** * Sign the request if the signer if provided and credentials are present. */ - private SdkHttpFullRequest signRequest(SdkHttpFullRequest request, RequestExecutionContext context) { + private SdkHttpFullRequest signRequest(SdkHttpFullRequest request, RequestExecutionContext context) throws Exception { updateInterceptorContext(request, context.executionContext()); Signer signer = context.signer(); + MetricCollector metricCollector = context.executionContext().metricCollector(); if (shouldSign(signer)) { adjustForClockSkew(context.executionAttributes()); - SdkHttpFullRequest signedRequest = signer.sign(request, context.executionAttributes()); + Pair measuredSign = MetricUtils.measureDuration(() -> + signer.sign(request, context.executionAttributes())); + + metricCollector.reportMetric(CoreMetric.SIGNING_DURATION, measuredSign.right()); + + SdkHttpFullRequest signedRequest = measuredSign.left(); if (signer instanceof AsyncRequestBodySigner) { //Transform request body provider with signing operator diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/util/MetricUtils.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/util/MetricUtils.java new file mode 100644 index 000000000000..dcd0554cdde5 --- /dev/null +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/util/MetricUtils.java @@ -0,0 +1,82 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.core.internal.util; + +import static software.amazon.awssdk.core.client.config.SdkClientOption.METRIC_PUBLISHER; + +import java.time.Duration; +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.function.Supplier; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.core.RequestOverrideConfiguration; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.metrics.MetricPublisher; +import software.amazon.awssdk.utils.Pair; + +/** + * Utility methods for working with metrics. + */ +@SdkInternalApi +public final class MetricUtils { + + private MetricUtils() { + } + + /** + * Resolve the correct metric publisher to use. The publisher set on the request always takes precedence. + * + * @param clientConfig The client configuration. + * @param requestConfig The request override configuration. + * @return The metric publisher to use. + */ + public static Optional resolvePublisher(SdkClientConfiguration clientConfig, + SdkRequest requestConfig) { + Optional requestOverride = requestConfig.overrideConfiguration() + .flatMap(RequestOverrideConfiguration::metricPublisher); + if (requestOverride.isPresent()) { + return requestOverride; + } + return Optional.ofNullable(clientConfig.option(METRIC_PUBLISHER)); + } + + /** + * Measure the duration of the given callable. + * + * @param c The callable to measure. + * @return A {@code Pair} containing the result of {@code c} and the duration. + */ + public static Pair measureDuration(Supplier c) { + long start = System.nanoTime(); + T result = c.get(); + Duration d = Duration.ofNanos(System.nanoTime() - start); + return Pair.of(result, d); + } + + /** + * Measure the duration of the given callable. + * + * @param c The callable to measure. + * @return A {@code Pair} containing the result of {@code c} and the duration. + */ + public static Pair measureDurationUnsafe(Callable c) throws Exception { + long start = System.nanoTime(); + T result = c.call(); + Duration d = Duration.ofNanos(System.nanoTime() - start); + return Pair.of(result, d); + } +} diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/metrics/CoreMetric.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/metrics/CoreMetric.java new file mode 100644 index 000000000000..857a607b6de1 --- /dev/null +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/metrics/CoreMetric.java @@ -0,0 +1,89 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.core.metrics; + +import java.time.Duration; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.metrics.MetricCategory; +import software.amazon.awssdk.metrics.SdkMetric; + +@SdkPublicApi +public final class CoreMetric { + /** + * The unique ID for the service. This is present for all API call metrics. + */ + public static final SdkMetric SERVICE_ID = metric("ServiceName", String.class); + + /** + * The name of the service operation being invoked. This is present for all + * API call metrics. + */ + public static final SdkMetric OPERATION_NAME = metric("OperationName", String.class); + + /** + * The duration of the API call. This includes all call attempts made. + */ + public static final SdkMetric API_CALL_DURATION = metric("ApiCallDuration", Duration.class); + + /** + * The duration of time taken to marshall the SDK request to an HTTP + * request. + */ + public static final SdkMetric MARSHALLING_DURATION = metric("MarshallingDuration", Duration.class); + + /** + * The duration of time taken to fetch signing credentials for the request. + */ + public static final SdkMetric CREDENTIALS_FETCH_DURATION = metric("CredentialsFetchDuration", Duration.class); + + /** + * The duration fo time taken to sign the HTTP request. + */ + public static final SdkMetric SIGNING_DURATION = metric("SigningDuration", Duration.class); + + /** + * The total time take to send a HTTP request and receive the response. + */ + public static final SdkMetric HTTP_REQUEST_ROUND_TRIP_TIME = metric("HttpRequestRoundTripTime", Duration.class); + + /** + * The status code of the HTTP response. + */ + public static final SdkMetric HTTP_STATUS_CODE = metric("HttpStatusCode", Integer.class); + + /** + * The request ID of the service request. + */ + public static final SdkMetric AWS_REQUEST_ID = metric("AwsRequestId", String.class); + + /** + * The extended request ID of the service request. + */ + public static final SdkMetric AWS_EXTENDED_REQUEST_ID = metric("AwsExtendedRequestId", String.class); + + /** + * The exception thrown during request execution. Note this may be a service + * error that has been unmarshalled, or a clientside exception. + */ + public static final SdkMetric EXCEPTION = metric("Exception", Throwable.class); + + private CoreMetric() { + } + + private static SdkMetric metric(String name, Class clzz) { + return SdkMetric.create(name, clzz, MetricCategory.DEFAULT); + } +} diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/client/AsyncClientHandlerInterceptorExceptionTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/client/AsyncClientHandlerInterceptorExceptionTest.java index f4404ea1699c..2bbdce0a4c4f 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/client/AsyncClientHandlerInterceptorExceptionTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/client/AsyncClientHandlerInterceptorExceptionTest.java @@ -56,6 +56,7 @@ import software.amazon.awssdk.http.async.AsyncExecuteRequest; import software.amazon.awssdk.http.async.SdkAsyncHttpClient; import software.amazon.awssdk.http.async.SdkAsyncHttpResponseHandler; +import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.utils.CompletableFutureUtils; import utils.HttpTestUtils; import utils.ValidSdkObjects; diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/AsyncHttpClientApiCallTimeoutTests.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/AsyncHttpClientApiCallTimeoutTests.java index 5528486f3ed7..b4d5ad181976 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/AsyncHttpClientApiCallTimeoutTests.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/AsyncHttpClientApiCallTimeoutTests.java @@ -53,6 +53,7 @@ import software.amazon.awssdk.core.retry.RetryPolicy; import software.amazon.awssdk.core.signer.NoOpSigner; import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.metrics.MetricCollector; import utils.ValidSdkObjects; public class AsyncHttpClientApiCallTimeoutTests { @@ -181,6 +182,7 @@ private ExecutionContext withInterceptors(ExecutionInterceptor... requestHandler .interceptorChain(interceptors) .executionAttributes(new ExecutionAttributes()) .interceptorContext(incerceptorContext) + .metricCollector(MetricCollector.create("ApiCall")) .build(); } } diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/ClientExecutionAndRequestTimerTestUtils.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/ClientExecutionAndRequestTimerTestUtils.java index 5b439a711b03..b4923e2b363e 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/ClientExecutionAndRequestTimerTestUtils.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/ClientExecutionAndRequestTimerTestUtils.java @@ -34,6 +34,7 @@ import software.amazon.awssdk.core.signer.NoOpSigner; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.metrics.MetricCollector; /** * Useful asserts and utilities for verifying behavior or the client execution timeout and request @@ -116,6 +117,7 @@ public static ExecutionContext executionContext(SdkHttpFullRequest request) { .interceptorChain(new ExecutionInterceptorChain(Collections.emptyList())) .executionAttributes(new ExecutionAttributes()) .interceptorContext(incerceptorContext) + .metricCollector(MetricCollector.create("ApiCall")) .build(); } diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/HttpClientApiCallAttemptTimeoutTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/HttpClientApiCallAttemptTimeoutTest.java index b1c6c36642b5..b0f0ee487e66 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/HttpClientApiCallAttemptTimeoutTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/HttpClientApiCallAttemptTimeoutTest.java @@ -48,6 +48,7 @@ import software.amazon.awssdk.core.retry.RetryPolicy; import software.amazon.awssdk.core.signer.NoOpSigner; import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.metrics.MetricCollector; import utils.ValidSdkObjects; @@ -143,6 +144,7 @@ private ExecutionContext withInterceptors(ExecutionInterceptor... requestHandler .interceptorChain(interceptors) .executionAttributes(new ExecutionAttributes()) .interceptorContext(incerceptorContext) + .metricCollector(MetricCollector.create("ApiCall")) .build(); } } diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/HttpClientApiCallTimeoutTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/HttpClientApiCallTimeoutTest.java index 2b58e2a55706..937d3bf851cb 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/HttpClientApiCallTimeoutTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/timers/HttpClientApiCallTimeoutTest.java @@ -48,6 +48,7 @@ import software.amazon.awssdk.core.retry.RetryPolicy; import software.amazon.awssdk.core.signer.NoOpSigner; import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.metrics.MetricCollector; import utils.ValidSdkObjects; @@ -143,6 +144,7 @@ private ExecutionContext withInterceptors(ExecutionInterceptor... requestHandler .interceptorChain(interceptors) .executionAttributes(new ExecutionAttributes()) .interceptorContext(incerceptorContext) + .metricCollector(MetricCollector.create("ApiCall")) .build(); } } diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/protocolrestjson/CoreMetricsTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/protocolrestjson/CoreMetricsTest.java new file mode 100644 index 000000000000..4192ac63aa48 --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/protocolrestjson/CoreMetricsTest.java @@ -0,0 +1,258 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.services.protocolrestjson; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.time.Duration; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.http.AbortableInputStream; +import software.amazon.awssdk.http.ExecutableHttpRequest; +import software.amazon.awssdk.http.HttpExecuteRequest; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpFullResponse; +import software.amazon.awssdk.metrics.MetricCollection; +import software.amazon.awssdk.metrics.MetricPublisher; +import software.amazon.awssdk.services.protocolrestjson.model.EmptyModeledException; + +@RunWith(MockitoJUnitRunner.class) +public class CoreMetricsTest { + private static final String SERVICE_ID = "AmazonProtocolRestJson"; + private static final String REQUEST_ID = "req-id"; + private static final String EXTENDED_REQUEST_ID = "extended-id"; + private static final int MAX_RETRIES = 2; + + private static ProtocolRestJsonClient client; + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Mock + private SdkHttpClient mockHttpClient; + + @Mock + private AwsCredentialsProvider mockCredentialsProvider; + + @Mock + private MetricPublisher mockPublisher; + + @Before + public void setup() throws IOException { + client = ProtocolRestJsonClient.builder() + .httpClient(mockHttpClient) + .credentialsProvider(mockCredentialsProvider) + .overrideConfiguration(c -> c.metricPublisher(mockPublisher).retryPolicy(b -> b.numRetries(MAX_RETRIES))) + .build(); + AbortableInputStream content = contentStream("{}"); + SdkHttpFullResponse httpResponse = SdkHttpFullResponse.builder() + .statusCode(200) + .putHeader("x-amz-request-id", REQUEST_ID) + .putHeader("x-amz-id-2", EXTENDED_REQUEST_ID) + .content(content) + .build(); + + HttpExecuteResponse mockResponse = mockExecuteResponse(httpResponse); + + ExecutableHttpRequest mockExecuteRequest = mock(ExecutableHttpRequest.class); + when(mockExecuteRequest.call()).thenAnswer(invocation -> { + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + return mockResponse; + }); + + when(mockHttpClient.prepareRequest(any(HttpExecuteRequest.class))) + .thenReturn(mockExecuteRequest); + + when(mockCredentialsProvider.resolveCredentials()).thenAnswer(invocation -> { + try { + Thread.sleep(100); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + return AwsBasicCredentials.create("foo", "bar"); + }); + } + + @After + public void teardown() { + if (client != null) { + client.close(); + } + client = null; + } + + @Test + public void testApiCall_noConfiguredPublisher_succeeds() { + ProtocolRestJsonClient noPublisher = ProtocolRestJsonClient.builder() + .httpClient(mockHttpClient) + .build(); + + noPublisher.allTypes(); + } + + @Test + public void testApiCall_publisherOverriddenOnRequest_requestPublisherTakesPrecedence() { + MetricPublisher requestMetricPublisher = mock(MetricPublisher.class); + + client.allTypes(r -> r.overrideConfiguration(o -> o.metricPublisher(requestMetricPublisher))); + + verify(requestMetricPublisher).publish(any(MetricCollection.class)); + verifyZeroInteractions(mockPublisher); + } + + @Test + public void testApiCall_operationSuccessful_addsMetrics() { + client.allTypes(); + + ArgumentCaptor collectionCaptor = ArgumentCaptor.forClass(MetricCollection.class); + verify(mockPublisher).publish(collectionCaptor.capture()); + + MetricCollection capturedCollection = collectionCaptor.getValue(); + + assertThat(capturedCollection.name()).isEqualTo("ApiCall"); + assertThat(capturedCollection.metricValues(CoreMetric.SERVICE_ID)) + .containsExactly(SERVICE_ID); + assertThat(capturedCollection.metricValues(CoreMetric.OPERATION_NAME)) + .containsExactly("AllTypes"); + assertThat(capturedCollection.metricValues(CoreMetric.CREDENTIALS_FETCH_DURATION).get(0)) + .isGreaterThanOrEqualTo(Duration.ZERO); + assertThat(capturedCollection.metricValues(CoreMetric.SIGNING_DURATION).get(0)) + .isGreaterThanOrEqualTo(Duration.ZERO); + assertThat(capturedCollection.metricValues(CoreMetric.MARSHALLING_DURATION).get(0)) + .isGreaterThanOrEqualTo(Duration.ZERO); + assertThat(capturedCollection.metricValues(CoreMetric.API_CALL_DURATION).get(0)) + .isGreaterThan(Duration.ZERO); + + assertThat(capturedCollection.children()).hasSize(1); + MetricCollection attemptCollection = capturedCollection.children().get(0); + + assertThat(attemptCollection.name()).isEqualTo("ApiCallAttemptMetrics"); + assertThat(attemptCollection.children()).isEmpty(); + assertThat(attemptCollection.metricValues(CoreMetric.HTTP_STATUS_CODE)) + .containsExactly(200); + assertThat(attemptCollection.metricValues(CoreMetric.AWS_REQUEST_ID)) + .containsExactly(REQUEST_ID); + assertThat(attemptCollection.metricValues(CoreMetric.AWS_EXTENDED_REQUEST_ID)) + .containsExactly(EXTENDED_REQUEST_ID); + + assertThat(attemptCollection.metricValues(CoreMetric.HTTP_REQUEST_ROUND_TRIP_TIME).get(0)) + .isGreaterThanOrEqualTo(Duration.ofMillis(100)); + } + + @Test + public void testApiCall_serviceReturnsError_errorInfoIncludedInMetrics() throws IOException { + AbortableInputStream content = contentStream("{}"); + + SdkHttpFullResponse httpResponse = SdkHttpFullResponse.builder() + .statusCode(500) + .putHeader("x-amz-request-id", REQUEST_ID) + .putHeader("x-amz-id-2", EXTENDED_REQUEST_ID) + .putHeader("X-Amzn-Errortype", "EmptyModeledException") + .content(content) + .build(); + + HttpExecuteResponse response = mockExecuteResponse(httpResponse); + + ExecutableHttpRequest mockExecuteRequest = mock(ExecutableHttpRequest.class); + when(mockExecuteRequest.call()).thenReturn(response); + + when(mockHttpClient.prepareRequest(any(HttpExecuteRequest.class))) + .thenReturn(mockExecuteRequest); + + thrown.expect(EmptyModeledException.class); + try { + client.allTypes(); + } finally { + ArgumentCaptor collectionCaptor = ArgumentCaptor.forClass(MetricCollection.class); + verify(mockPublisher).publish(collectionCaptor.capture()); + + MetricCollection capturedCollection = collectionCaptor.getValue(); + + assertThat(capturedCollection.children()).hasSize(MAX_RETRIES + 1); + + for (MetricCollection requestMetrics : capturedCollection.children()) { + assertThat(requestMetrics.metricValues(CoreMetric.EXCEPTION).get(0)) + // Note: for some reason we don't throw the same exact + // instance as the one unmarshalled by the response + // handler (seems we make a copy upstream) so an + // isSamAs assertion fails here + .isInstanceOf(EmptyModeledException.class); + + // A service exception is still a successful HTTP execution so + // we should still have HTTP metrics as well. + assertThat(requestMetrics.metricValues(CoreMetric.HTTP_STATUS_CODE)) + .containsExactly(500); + assertThat(requestMetrics.metricValues(CoreMetric.AWS_REQUEST_ID)) + .containsExactly(REQUEST_ID); + assertThat(requestMetrics.metricValues(CoreMetric.AWS_EXTENDED_REQUEST_ID)) + .containsExactly(EXTENDED_REQUEST_ID); + assertThat(requestMetrics.metricValues(CoreMetric.HTTP_REQUEST_ROUND_TRIP_TIME).get(0)) + .isGreaterThanOrEqualTo(Duration.ZERO); + } + } + } + + @Test + public void testApiCall_clientSideExceptionThrown_includedInMetrics() { + when(mockHttpClient.prepareRequest(any(HttpExecuteRequest.class))).thenThrow(new RuntimeException("oops")); + + thrown.expect(RuntimeException.class); + + client.allTypes(); + + ArgumentCaptor collectionCaptor = ArgumentCaptor.forClass(MetricCollection.class); + verify(mockPublisher).publish(collectionCaptor.capture()); + + MetricCollection capturedCollection = collectionCaptor.getValue(); + + MetricCollection requestMetrics = capturedCollection.children().get(0); + + assertThat(requestMetrics.metricValues(CoreMetric.EXCEPTION).get(0)).isExactlyInstanceOf(RuntimeException.class); + } + + private static HttpExecuteResponse mockExecuteResponse(SdkHttpFullResponse httpResponse) { + HttpExecuteResponse mockResponse = mock(HttpExecuteResponse.class); + when(mockResponse.httpResponse()).thenReturn(httpResponse); + when(mockResponse.responseBody()).thenReturn(httpResponse.content()); + return mockResponse; + } + + private static AbortableInputStream contentStream(String content) { + ByteArrayInputStream baos = new ByteArrayInputStream(content.getBytes()); + return AbortableInputStream.create(baos); + } +}