diff --git a/.changes/next-release/bugfix-AWSSDKforJavav2-0a552ee.json b/.changes/next-release/bugfix-AWSSDKforJavav2-0a552ee.json new file mode 100644 index 000000000000..7ff6d8584379 --- /dev/null +++ b/.changes/next-release/bugfix-AWSSDKforJavav2-0a552ee.json @@ -0,0 +1,6 @@ +{ + "category": "AWS SDK for Java v2", + "contributor": "", + "type": "bugfix", + "description": "Implement SERVICE_ENDPOINT core metric" +} diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonAsyncHttpClient.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonAsyncHttpClient.java index 7d0ebce693c5..af50ecf18115 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonAsyncHttpClient.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/AmazonAsyncHttpClient.java @@ -36,6 +36,7 @@ import software.amazon.awssdk.core.internal.http.pipeline.stages.AsyncBeforeTransmissionExecutionInterceptorsStage; import software.amazon.awssdk.core.internal.http.pipeline.stages.AsyncExecutionFailureExceptionReportingStage; import software.amazon.awssdk.core.internal.http.pipeline.stages.AsyncRetryableStage; +import software.amazon.awssdk.core.internal.http.pipeline.stages.AsyncServiceEndpointMetricCollectionStage; import software.amazon.awssdk.core.internal.http.pipeline.stages.AsyncSigningStage; import software.amazon.awssdk.core.internal.http.pipeline.stages.MakeAsyncHttpRequestStage; import software.amazon.awssdk.core.internal.http.pipeline.stages.MakeRequestImmutableStage; @@ -180,7 +181,8 @@ public CompletableFuture execute( .then(async(() -> new AfterExecutionInterceptorsStage<>())) .wrappedWith(AsyncExecutionFailureExceptionReportingStage::new) .wrappedWith(AsyncApiCallTimeoutTrackingStage::new) - .wrappedWith(AsyncApiCallMetricCollectionStage::new)::build)::build) + .wrappedWith(AsyncApiCallMetricCollectionStage::new) + .wrappedWith(AsyncServiceEndpointMetricCollectionStage::new)::build)::build) .build(httpClientDependencies) .execute(request, createRequestExecutionDependencies()); } catch (RuntimeException e) { 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 91e24c798f31..af50ffa0b4eb 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 @@ -43,6 +43,8 @@ import software.amazon.awssdk.core.internal.http.pipeline.stages.MergeCustomHeadersStage; import software.amazon.awssdk.core.internal.http.pipeline.stages.MergeCustomQueryParamsStage; import software.amazon.awssdk.core.internal.http.pipeline.stages.RetryableStage; +import software.amazon.awssdk.core.internal.http.pipeline.stages.ServiceEndpointAttemptMetricCollectionStage; +import software.amazon.awssdk.core.internal.http.pipeline.stages.ServiceEndpointMetricCollectionStage; import software.amazon.awssdk.core.internal.http.pipeline.stages.SigningStage; import software.amazon.awssdk.core.internal.http.pipeline.stages.TimeoutExceptionHandlingStage; import software.amazon.awssdk.core.internal.http.pipeline.stages.UnwrapResponseContainer; @@ -182,10 +184,13 @@ public OutputT execute(HttpResponseHandler> response .wrappedWith(ApiCallAttemptTimeoutTrackingStage::new) .wrappedWith(TimeoutExceptionHandlingStage::new) .wrappedWith((deps, wrapped) -> new ApiCallAttemptMetricCollectionStage<>(wrapped)) + .wrappedWith((deps, wrapped) -> + new ServiceEndpointAttemptMetricCollectionStage<>(wrapped)) .wrappedWith(RetryableStage::new)::build) .wrappedWith(StreamManagingStage::new) .wrappedWith(ApiCallTimeoutTrackingStage::new)::build) .wrappedWith((deps, wrapped) -> new ApiCallMetricCollectionStage<>(wrapped)) + .wrappedWith((deps, wrapped) -> new ServiceEndpointMetricCollectionStage<>(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/pipeline/stages/AsyncServiceEndpointMetricCollectionStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncServiceEndpointMetricCollectionStage.java new file mode 100644 index 000000000000..168186cb86db --- /dev/null +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncServiceEndpointMetricCollectionStage.java @@ -0,0 +1,59 @@ +/* + * 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.util.concurrent.CompletableFuture; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.core.internal.http.RequestExecutionContext; +import software.amazon.awssdk.core.internal.http.pipeline.RequestPipeline; +import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.utils.CompletableFutureUtils; + +/** + * Wrapper pipeline that tracks the {@link CoreMetric#SERVICE_ENDPOINT} metric. + */ +@SdkInternalApi +public final class AsyncServiceEndpointMetricCollectionStage implements RequestPipeline> { + private final RequestPipeline> wrapped; + + public AsyncServiceEndpointMetricCollectionStage(RequestPipeline> wrapped) { + this.wrapped = wrapped; + } + + @Override + public CompletableFuture execute(SdkHttpFullRequest input, RequestExecutionContext context) throws Exception { + MetricCollector metricCollector = context.executionContext().metricCollector(); + + CompletableFuture future = new CompletableFuture<>(); + + CompletableFuture executeFuture = wrapped.execute(input, context); + + executeFuture.whenComplete((r, t) -> { + metricCollector.reportMetric(CoreMetric.SERVICE_ENDPOINT, input.getUri()); + + if (t != null) { + future.completeExceptionally(t); + } else { + future.complete(r); + } + }); + + return CompletableFutureUtils.forwardExceptionTo(future, executeFuture); + } +} diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ServiceEndpointAttemptMetricCollectionStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ServiceEndpointAttemptMetricCollectionStage.java new file mode 100644 index 000000000000..8e6fcaff5131 --- /dev/null +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ServiceEndpointAttemptMetricCollectionStage.java @@ -0,0 +1,53 @@ +/* + * 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 static software.amazon.awssdk.core.internal.util.MetricUtils.collectServiceEndpointMetrics; +import static software.amazon.awssdk.core.internal.util.MetricUtils.createAttemptMetricsCollector; + +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.http.SdkHttpFullRequest; +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 ServiceEndpointAttemptMetricCollectionStage implements RequestToResponsePipeline { + private final RequestPipeline> wrapped; + + public ServiceEndpointAttemptMetricCollectionStage(RequestPipeline> wrapped) { + this.wrapped = wrapped; + } + + @Override + public Response execute(SdkHttpFullRequest input, RequestExecutionContext context) throws Exception { + MetricCollector apiCallAttemptMetrics = createAttemptMetricsCollector(context); + context.attemptMetricCollector(apiCallAttemptMetrics); + + Response response = wrapped.execute(input, context); + + collectServiceEndpointMetrics(apiCallAttemptMetrics, input); + + return response; + } + +} diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ServiceEndpointMetricCollectionStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ServiceEndpointMetricCollectionStage.java new file mode 100644 index 000000000000..cb5f0db7eb4e --- /dev/null +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ServiceEndpointMetricCollectionStage.java @@ -0,0 +1,45 @@ +/* + * 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.metrics.MetricCollector; + +/** + * Wrapper pipeline that tracks the {@link CoreMetric#SERVICE_ENDPOINT} metric. + */ +@SdkInternalApi +public class ServiceEndpointMetricCollectionStage implements RequestToResponsePipeline { + private final RequestPipeline> wrapped; + + public ServiceEndpointMetricCollectionStage(RequestPipeline> wrapped) { + this.wrapped = wrapped; + } + + @Override + public Response execute(SdkHttpFullRequest input, RequestExecutionContext context) throws Exception { + MetricCollector metricCollector = context.executionContext().metricCollector(); + metricCollector.reportMetric(CoreMetric.SERVICE_ENDPOINT, input.getUri()); + + return wrapped.execute(input, context); + } +} 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 index 8805653dd636..1b1e918c2749 100644 --- 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 @@ -25,6 +25,7 @@ import software.amazon.awssdk.core.internal.http.RequestExecutionContext; import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.http.HttpMetric; +import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpFullResponse; import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.metrics.NoOpMetricCollector; @@ -65,6 +66,12 @@ public static Pair measureDurationUnsafe(Callable c) throws return Pair.of(result, d); } + public static void collectServiceEndpointMetrics(MetricCollector metricCollector, SdkHttpFullRequest httpRequest) { + if (metricCollector != null && !(metricCollector instanceof NoOpMetricCollector) && httpRequest != null) { + metricCollector.reportMetric(CoreMetric.SERVICE_ENDPOINT, httpRequest.getUri()); + } + } + public static void collectHttpMetrics(MetricCollector metricCollector, SdkHttpFullResponse httpResponse) { if (metricCollector != null && !(metricCollector instanceof NoOpMetricCollector) && httpResponse != null) { metricCollector.reportMetric(HttpMetric.HTTP_STATUS_CODE, httpResponse.statusCode()); 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 index fda6bbc67113..62240e55a91a 100644 --- 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 @@ -15,6 +15,7 @@ package software.amazon.awssdk.core.metrics; +import java.net.URI; import java.time.Duration; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.core.retry.RetryPolicy; @@ -50,6 +51,12 @@ public final class CoreMetric { public static final SdkMetric RETRY_COUNT = metric("RetryCount", Integer.class, MetricLevel.ERROR); + /** + * The endpoint for the service. + */ + public static final SdkMetric SERVICE_ENDPOINT = + metric("ServiceEndpoint", URI.class, MetricLevel.ERROR); + /** * The duration of the API call. This includes all call attempts made. * diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/CoreMetricsTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/CoreMetricsTest.java index 0594f635d7ad..daac421a86e7 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/CoreMetricsTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/CoreMetricsTest.java @@ -24,6 +24,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +import java.net.URI; import java.time.Duration; import java.util.List; import java.util.stream.Collectors; @@ -181,6 +182,8 @@ public void testApiCall_operationSuccessful_addsMetrics() { assertThat(capturedCollection.metricValues(CoreMetric.MARSHALLING_DURATION).get(0)) .isGreaterThanOrEqualTo(Duration.ZERO); assertThat(capturedCollection.metricValues(CoreMetric.RETRY_COUNT)).containsExactly(0); + assertThat(capturedCollection.metricValues(CoreMetric.SERVICE_ENDPOINT).get(0)).isEqualTo(URI.create( + "https://customresponsemetadata.us-west-2.amazonaws.com")); assertThat(capturedCollection.children()).hasSize(1); MetricCollection attemptCollection = capturedCollection.children().get(0); diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/BaseAsyncCoreMetricsTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/BaseAsyncCoreMetricsTest.java index 54fbf8d5f38e..6639438fbc6a 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/BaseAsyncCoreMetricsTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/BaseAsyncCoreMetricsTest.java @@ -28,7 +28,6 @@ import java.time.Duration; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -201,6 +200,8 @@ private void verifyApiCallCollection(MetricCollection capturedCollection) { .isGreaterThanOrEqualTo(Duration.ZERO); assertThat(capturedCollection.metricValues(CoreMetric.API_CALL_DURATION).get(0)) .isGreaterThan(FIXED_DELAY); + assertThat(capturedCollection.metricValues(CoreMetric.SERVICE_ENDPOINT).get(0)).toString() + .startsWith("http://localhost"); } void stubSuccessfulResponse() {