Skip to content

Commit 7d54ff8

Browse files
dagnirdave-fn
andauthored
Add SERVICE_ENDPOINT metric (#4307)
* Implement SERVICE_ENDPOINT core metric * Create CollectionStages for SERVICE_ENDPOINT * Remove erviceEndpointMetricCollectionStages since they're unnecessary * Move collection of SERVICE_ENDPOINT metric Simplify by collecting in the API Call metric collection stages where other request metrics are already being collected. * Remove unused import * Update changelog entry * Fix test * Remove unused class --------- Co-authored-by: David Negrete <[email protected]>
1 parent be0fbc2 commit 7d54ff8

File tree

7 files changed

+74
-0
lines changed

7 files changed

+74
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "AWS SDK for Java v2",
3+
"contributor": "",
4+
"type": "bugfix",
5+
"description": "Add support for the `SERVICE_ENDPOINT` metric. This metric represents the endpoint (scheme and authority) that the request was sent to."
6+
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApiCallMetricCollectionStage.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import software.amazon.awssdk.core.internal.http.RequestExecutionContext;
2222
import software.amazon.awssdk.core.internal.http.pipeline.RequestPipeline;
2323
import software.amazon.awssdk.core.internal.http.pipeline.RequestToResponsePipeline;
24+
import software.amazon.awssdk.core.internal.util.MetricUtils;
2425
import software.amazon.awssdk.core.metrics.CoreMetric;
2526
import software.amazon.awssdk.http.SdkHttpFullRequest;
2627
import software.amazon.awssdk.metrics.MetricCollector;
@@ -39,6 +40,7 @@ public ApiCallMetricCollectionStage(RequestPipeline<SdkHttpFullRequest, Response
3940
@Override
4041
public Response<OutputT> execute(SdkHttpFullRequest input, RequestExecutionContext context) throws Exception {
4142
MetricCollector metricCollector = context.executionContext().metricCollector();
43+
MetricUtils.collectServiceEndpointMetrics(metricCollector, input);
4244

4345
// Note: at this point, any exception, even a service exception, will
4446
// be thrown from the wrapped pipeline so we can't use

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncApiCallMetricCollectionStage.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import software.amazon.awssdk.annotations.SdkInternalApi;
2121
import software.amazon.awssdk.core.internal.http.RequestExecutionContext;
2222
import software.amazon.awssdk.core.internal.http.pipeline.RequestPipeline;
23+
import software.amazon.awssdk.core.internal.util.MetricUtils;
2324
import software.amazon.awssdk.core.metrics.CoreMetric;
2425
import software.amazon.awssdk.http.SdkHttpFullRequest;
2526
import software.amazon.awssdk.metrics.MetricCollector;
@@ -40,6 +41,7 @@ public AsyncApiCallMetricCollectionStage(RequestPipeline<SdkHttpFullRequest, Com
4041
@Override
4142
public CompletableFuture<OutputT> execute(SdkHttpFullRequest input, RequestExecutionContext context) throws Exception {
4243
MetricCollector metricCollector = context.executionContext().metricCollector();
44+
MetricUtils.collectServiceEndpointMetrics(metricCollector, input);
4345

4446
CompletableFuture<OutputT> future = new CompletableFuture<>();
4547

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/util/MetricUtils.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@
1818
import static software.amazon.awssdk.core.http.HttpResponseHandler.X_AMZN_REQUEST_ID_HEADERS;
1919
import static software.amazon.awssdk.core.http.HttpResponseHandler.X_AMZ_ID_2_HEADER;
2020

21+
import java.net.URI;
22+
import java.net.URISyntaxException;
2123
import java.time.Duration;
2224
import java.util.concurrent.Callable;
2325
import java.util.function.Supplier;
2426
import software.amazon.awssdk.annotations.SdkInternalApi;
27+
import software.amazon.awssdk.core.exception.SdkClientException;
2528
import software.amazon.awssdk.core.internal.http.RequestExecutionContext;
2629
import software.amazon.awssdk.core.metrics.CoreMetric;
2730
import software.amazon.awssdk.http.HttpMetric;
31+
import software.amazon.awssdk.http.SdkHttpFullRequest;
2832
import software.amazon.awssdk.http.SdkHttpFullResponse;
2933
import software.amazon.awssdk.metrics.MetricCollector;
3034
import software.amazon.awssdk.metrics.NoOpMetricCollector;
@@ -65,6 +69,23 @@ public static <T> Pair<T, Duration> measureDurationUnsafe(Callable<T> c) throws
6569
return Pair.of(result, d);
6670
}
6771

72+
/**
73+
* Collect the SERVICE_ENDPOINT metric for this request.
74+
*/
75+
public static void collectServiceEndpointMetrics(MetricCollector metricCollector, SdkHttpFullRequest httpRequest) {
76+
if (metricCollector != null && !(metricCollector instanceof NoOpMetricCollector) && httpRequest != null) {
77+
// Only interested in the service endpoint so don't include any path, query, or fragment component
78+
URI requestUri = httpRequest.getUri();
79+
try {
80+
URI serviceEndpoint = new URI(requestUri.getScheme(), requestUri.getAuthority(), null, null, null);
81+
metricCollector.reportMetric(CoreMetric.SERVICE_ENDPOINT, serviceEndpoint);
82+
} catch (URISyntaxException e) {
83+
// This should not happen since getUri() should return a valid URI
84+
throw SdkClientException.create("Unable to collect SERVICE_ENDPOINT metric", e);
85+
}
86+
}
87+
}
88+
6889
public static void collectHttpMetrics(MetricCollector metricCollector, SdkHttpFullResponse httpResponse) {
6990
if (metricCollector != null && !(metricCollector instanceof NoOpMetricCollector) && httpResponse != null) {
7091
metricCollector.reportMetric(HttpMetric.HTTP_STATUS_CODE, httpResponse.statusCode());

core/sdk-core/src/main/java/software/amazon/awssdk/core/metrics/CoreMetric.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
package software.amazon.awssdk.core.metrics;
1717

18+
import java.net.URI;
1819
import java.time.Duration;
1920
import software.amazon.awssdk.annotations.SdkPublicApi;
2021
import software.amazon.awssdk.core.retry.RetryPolicy;
@@ -50,6 +51,12 @@ public final class CoreMetric {
5051
public static final SdkMetric<Integer> RETRY_COUNT =
5152
metric("RetryCount", Integer.class, MetricLevel.ERROR);
5253

54+
/**
55+
* The endpoint for the service.
56+
*/
57+
public static final SdkMetric<URI> SERVICE_ENDPOINT =
58+
metric("ServiceEndpoint", URI.class, MetricLevel.ERROR);
59+
5360
/**
5461
* The duration of the API call. This includes all call attempts made.
5562
*

test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/CoreMetricsTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424

2525
import java.io.ByteArrayInputStream;
2626
import java.io.IOException;
27+
import java.net.URI;
2728
import java.time.Duration;
2829
import java.util.List;
30+
import java.util.concurrent.CompletableFuture;
2931
import java.util.stream.Collectors;
3032
import org.junit.After;
3133
import org.junit.Before;
@@ -41,6 +43,7 @@
4143
import software.amazon.awssdk.core.exception.SdkException;
4244
import software.amazon.awssdk.core.metrics.CoreMetric;
4345
import software.amazon.awssdk.core.internal.metrics.SdkErrorType;
46+
import software.amazon.awssdk.endpoints.Endpoint;
4447
import software.amazon.awssdk.http.AbortableInputStream;
4548
import software.amazon.awssdk.http.ExecutableHttpRequest;
4649
import software.amazon.awssdk.http.HttpExecuteRequest;
@@ -52,6 +55,8 @@
5255
import software.amazon.awssdk.metrics.MetricPublisher;
5356
import software.amazon.awssdk.regions.Region;
5457
import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient;
58+
import software.amazon.awssdk.services.protocolrestjson.endpoints.ProtocolRestJsonEndpointParams;
59+
import software.amazon.awssdk.services.protocolrestjson.endpoints.ProtocolRestJsonEndpointProvider;
5560
import software.amazon.awssdk.services.protocolrestjson.model.EmptyModeledException;
5661
import software.amazon.awssdk.services.protocolrestjson.model.SimpleStruct;
5762
import software.amazon.awssdk.services.protocolrestjson.paginators.PaginatedOperationWithResultKeyIterable;
@@ -77,13 +82,17 @@ public class CoreMetricsTest {
7782
@Mock
7883
private MetricPublisher mockPublisher;
7984

85+
@Mock
86+
private ProtocolRestJsonEndpointProvider mockEndpointProvider;
87+
8088
@Before
8189
public void setup() throws IOException {
8290
client = ProtocolRestJsonClient.builder()
8391
.httpClient(mockHttpClient)
8492
.region(Region.US_WEST_2)
8593
.credentialsProvider(mockCredentialsProvider)
8694
.overrideConfiguration(c -> c.addMetricPublisher(mockPublisher).retryPolicy(b -> b.numRetries(MAX_RETRIES)))
95+
.endpointProvider(mockEndpointProvider)
8796
.build();
8897
AbortableInputStream content = contentStream("{}");
8998
SdkHttpFullResponse httpResponse = SdkHttpFullResponse.builder()
@@ -116,6 +125,11 @@ public void setup() throws IOException {
116125
}
117126
return AwsBasicCredentials.create("foo", "bar");
118127
});
128+
129+
when(mockEndpointProvider.resolveEndpoint(any(ProtocolRestJsonEndpointParams.class))).thenReturn(
130+
CompletableFuture.completedFuture(Endpoint.builder()
131+
.url(URI.create("https://protocolrestjson.amazonaws.com"))
132+
.build()));
119133
}
120134

121135
@After
@@ -183,6 +197,8 @@ public void testApiCall_operationSuccessful_addsMetrics() {
183197
assertThat(capturedCollection.metricValues(CoreMetric.MARSHALLING_DURATION).get(0))
184198
.isGreaterThanOrEqualTo(Duration.ZERO);
185199
assertThat(capturedCollection.metricValues(CoreMetric.RETRY_COUNT)).containsExactly(0);
200+
assertThat(capturedCollection.metricValues(CoreMetric.SERVICE_ENDPOINT).get(0)).isEqualTo(URI.create(
201+
"https://protocolrestjson.amazonaws.com"));
186202

187203
assertThat(capturedCollection.children()).hasSize(1);
188204
MetricCollection attemptCollection = capturedCollection.children().get(0);
@@ -280,6 +296,24 @@ public void testApiCall_httpClientThrowsNetworkError_errorTypeIncludedInMetrics(
280296
}
281297
}
282298

299+
@Test
300+
public void testApiCall_endpointProviderAddsPathQueryFragment_notReportedInServiceEndpointMetric() {
301+
when(mockEndpointProvider.resolveEndpoint(any(ProtocolRestJsonEndpointParams.class)))
302+
.thenReturn(CompletableFuture.completedFuture(Endpoint.builder()
303+
.url(URI.create("https://protocolrestjson.amazonaws.com:8080/foo?bar#baz"))
304+
.build()));
305+
306+
client.allTypes();
307+
308+
ArgumentCaptor<MetricCollection> collectionCaptor = ArgumentCaptor.forClass(MetricCollection.class);
309+
verify(mockPublisher).publish(collectionCaptor.capture());
310+
311+
MetricCollection capturedCollection = collectionCaptor.getValue();
312+
313+
URI expectedServiceEndpoint = URI.create("https://protocolrestjson.amazonaws.com:8080");
314+
assertThat(capturedCollection.metricValues(CoreMetric.SERVICE_ENDPOINT)).containsExactly(expectedServiceEndpoint);
315+
}
316+
283317

284318
private static HttpExecuteResponse mockExecuteResponse(SdkHttpFullResponse httpResponse) {
285319
HttpExecuteResponse mockResponse = mock(HttpExecuteResponse.class);

test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/metrics/async/BaseAsyncCoreMetricsTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ private void verifyApiCallCollection(MetricCollection capturedCollection) {
203203
.isGreaterThanOrEqualTo(Duration.ZERO);
204204
assertThat(capturedCollection.metricValues(CoreMetric.API_CALL_DURATION).get(0))
205205
.isGreaterThan(FIXED_DELAY);
206+
assertThat(capturedCollection.metricValues(CoreMetric.SERVICE_ENDPOINT).get(0)).toString()
207+
.startsWith("http://localhost");
206208
}
207209

208210
void stubSuccessfulResponse() {

0 commit comments

Comments
 (0)