Skip to content

Commit 5f0800d

Browse files
authored
Track default vs explicit http client usage (#6849)
* Track default vs explicit http client usage Add metadata in the UA to track whether the HTTP client in use by the service client was picked up by default, or explicitly configured by the user. * Switch to discrete feature IDs * Switch to discrete feature IDs * Tests update * Revert user-agent.md changes * Fix test * Update changelog
1 parent 1112e37 commit 5f0800d

File tree

9 files changed

+603
-41
lines changed

9 files changed

+603
-41
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Add HTTP client configuration type metadata to the User-Agent header, tracking whether the HTTP client was auto-detected from the classpath, an explicit client instance or client builder configured by the customer."
6+
}

core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ private AwsExecutionContextBuilder() {
171171
AwsSignerExecutionAttribute.AWS_CREDENTIALS).orElse(null)));
172172

173173
putStreamingInputOutputTypesMetadata(executionAttributes, executionParams);
174+
putHttpClientConfigTypeMetadata(executionAttributes, clientConfig);
174175

175176
return ExecutionContext.builder()
176177
.interceptorChain(executionInterceptorChain)
@@ -183,53 +184,58 @@ private AwsExecutionContextBuilder() {
183184

184185
private static <InputT extends SdkRequest, OutputT extends SdkResponse> void putStreamingInputOutputTypesMetadata(
185186
ExecutionAttributes executionAttributes, ClientExecutionParams<InputT, OutputT> executionParams) {
186-
List<AdditionalMetadata> userAgentMetadata = new ArrayList<>();
187187

188188
if (executionParams.getRequestBody() != null) {
189-
userAgentMetadata.add(
190-
AdditionalMetadata
191-
.builder()
192-
.name("rb")
193-
.value(ContentStreamProvider.ProviderType.shortValueFromName(
194-
executionParams.getRequestBody().contentStreamProvider().name())
195-
)
196-
.build());
189+
addUserAgentMetadata(executionAttributes, "rb",
190+
ContentStreamProvider.ProviderType.shortValueFromName(
191+
executionParams.getRequestBody().contentStreamProvider().name()));
197192
}
198193

199194
if (executionParams.getAsyncRequestBody() != null) {
200-
userAgentMetadata.add(
201-
AdditionalMetadata
202-
.builder()
203-
.name("rb")
204-
.value(AsyncRequestBody.BodyType.shortValueFromName(
205-
executionParams.getAsyncRequestBody().body())
206-
)
207-
.build());
195+
addUserAgentMetadata(executionAttributes, "rb",
196+
AsyncRequestBody.BodyType.shortValueFromName(
197+
executionParams.getAsyncRequestBody().body()));
208198
}
209199

210200
if (executionParams.getResponseTransformer() != null) {
211-
userAgentMetadata.add(
212-
AdditionalMetadata
213-
.builder()
214-
.name("rt")
215-
.value(ResponseTransformer.TransformerType.shortValueFromName(
216-
executionParams.getResponseTransformer().name())
217-
)
218-
.build());
201+
addUserAgentMetadata(executionAttributes, "rt",
202+
ResponseTransformer.TransformerType.shortValueFromName(
203+
executionParams.getResponseTransformer().name()));
219204
}
220205

221206
if (executionParams.getAsyncResponseTransformer() != null) {
222-
userAgentMetadata.add(
223-
AdditionalMetadata
224-
.builder()
225-
.name("rt")
226-
.value(AsyncResponseTransformer.TransformerType.shortValueFromName(
227-
executionParams.getAsyncResponseTransformer().name())
228-
)
229-
.build());
207+
addUserAgentMetadata(executionAttributes, "rt",
208+
AsyncResponseTransformer.TransformerType.shortValueFromName(
209+
executionParams.getAsyncResponseTransformer().name()));
230210
}
211+
}
212+
213+
private static void putHttpClientConfigTypeMetadata(ExecutionAttributes executionAttributes,
214+
SdkClientConfiguration clientConfig) {
215+
BusinessMetricFeatureId httpClientConfigType = clientConfig.option(SdkClientOption.HTTP_CLIENT_CONFIG_TYPE);
216+
if (httpClientConfigType == null) {
217+
return;
218+
}
219+
BusinessMetricCollection businessMetrics = executionAttributes.getAttribute(
220+
SdkInternalExecutionAttribute.BUSINESS_METRICS);
221+
if (businessMetrics != null) {
222+
businessMetrics.addMetric(httpClientConfigType.value());
223+
}
224+
}
231225

232-
executionAttributes.putAttribute(SdkInternalExecutionAttribute.USER_AGENT_METADATA, userAgentMetadata);
226+
private static void addUserAgentMetadata(ExecutionAttributes executionAttributes, String name, String value) {
227+
List<AdditionalMetadata> metadata = executionAttributes.getAttribute(
228+
SdkInternalExecutionAttribute.USER_AGENT_METADATA);
229+
if (metadata == null) {
230+
metadata = new ArrayList<>();
231+
executionAttributes.putAttribute(SdkInternalExecutionAttribute.USER_AGENT_METADATA, metadata);
232+
}
233+
metadata.add(
234+
AdditionalMetadata
235+
.builder()
236+
.name(name)
237+
.value(value)
238+
.build());
233239
}
234240

235241
/**

core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilderTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@
6666
import software.amazon.awssdk.core.sync.RequestBody;
6767
import software.amazon.awssdk.core.sync.ResponseTransformer;
6868
import software.amazon.awssdk.core.useragent.AdditionalMetadata;
69+
import software.amazon.awssdk.core.useragent.BusinessMetricCollection;
70+
import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId;
6971
import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme;
7072
import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme;
7173
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
@@ -510,6 +512,51 @@ public void invokeInterceptorsAndCreateExecutionContext_withAsyncResponseTransfo
510512
);
511513
}
512514

515+
@Test
516+
public void invokeInterceptorsAndCreateExecutionContext_withDefaultHttpClient_addsAutoFeatureId() {
517+
SdkClientConfiguration clientConfig = testClientConfiguration()
518+
.option(SdkClientOption.HTTP_CLIENT_CONFIG_TYPE, BusinessMetricFeatureId.HTTP_CLIENT_AUTO)
519+
.build();
520+
521+
ExecutionContext executionContext =
522+
AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(clientExecutionParams(), clientConfig);
523+
524+
ExecutionAttributes executionAttributes = executionContext.executionAttributes();
525+
BusinessMetricCollection businessMetrics =
526+
executionAttributes.getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS);
527+
assertThat(businessMetrics.recordedMetrics()).contains("AJ");
528+
}
529+
530+
@Test
531+
public void invokeInterceptorsAndCreateExecutionContext_withExplicitHttpClientInstance_addsExplicitInstanceFeatureId() {
532+
SdkClientConfiguration clientConfig = testClientConfiguration()
533+
.option(SdkClientOption.HTTP_CLIENT_CONFIG_TYPE, BusinessMetricFeatureId.HTTP_CLIENT_EXPLICIT_INSTANCE)
534+
.build();
535+
536+
ExecutionContext executionContext =
537+
AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(clientExecutionParams(), clientConfig);
538+
539+
ExecutionAttributes executionAttributes = executionContext.executionAttributes();
540+
BusinessMetricCollection businessMetrics =
541+
executionAttributes.getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS);
542+
assertThat(businessMetrics.recordedMetrics()).contains("AK");
543+
}
544+
545+
@Test
546+
public void invokeInterceptorsAndCreateExecutionContext_withExplicitHttpClientFactory_addsExplicitFactoryFeatureId() {
547+
SdkClientConfiguration clientConfig = testClientConfiguration()
548+
.option(SdkClientOption.HTTP_CLIENT_CONFIG_TYPE, BusinessMetricFeatureId.HTTP_CLIENT_EXPLICIT_FACTORY)
549+
.build();
550+
551+
ExecutionContext executionContext =
552+
AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(clientExecutionParams(), clientConfig);
553+
554+
ExecutionAttributes executionAttributes = executionContext.executionAttributes();
555+
BusinessMetricCollection businessMetrics =
556+
executionAttributes.getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS);
557+
assertThat(businessMetrics.recordedMetrics()).contains("AL");
558+
}
559+
513560
private ClientExecutionParams<SdkRequest, SdkResponse> clientExecutionParams() {
514561
return clientExecutionParams(sdkRequest);
515562
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/client/builder/SdkDefaultClientBuilder.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import static software.amazon.awssdk.core.client.config.SdkClientOption.DEFAULT_RETRY_MODE;
4040
import static software.amazon.awssdk.core.client.config.SdkClientOption.EXECUTION_INTERCEPTORS;
4141
import static software.amazon.awssdk.core.client.config.SdkClientOption.HTTP_CLIENT_CONFIG;
42+
import static software.amazon.awssdk.core.client.config.SdkClientOption.HTTP_CLIENT_CONFIG_TYPE;
4243
import static software.amazon.awssdk.core.client.config.SdkClientOption.IDENTITY_PROVIDERS;
4344
import static software.amazon.awssdk.core.client.config.SdkClientOption.INTERNAL_USER_AGENT;
4445
import static software.amazon.awssdk.core.client.config.SdkClientOption.METRIC_PUBLISHERS;
@@ -96,6 +97,7 @@
9697
import software.amazon.awssdk.core.internal.useragent.SdkUserAgentBuilder;
9798
import software.amazon.awssdk.core.internal.useragent.UserAgentConstant;
9899
import software.amazon.awssdk.core.retry.RetryMode;
100+
import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId;
99101
import software.amazon.awssdk.core.util.SystemUserAgent;
100102
import software.amazon.awssdk.http.ExecutableHttpRequest;
101103
import software.amazon.awssdk.http.HttpExecuteRequest;
@@ -310,34 +312,57 @@ protected SdkClientConfiguration finalizeChildConfiguration(SdkClientConfigurati
310312
*/
311313
private SdkClientConfiguration finalizeSyncConfiguration(SdkClientConfiguration config) {
312314
return config.toBuilder()
315+
.option(HTTP_CLIENT_CONFIG_TYPE, resolveSyncHttpClientConfigType(config))
313316
.lazyOption(SdkClientOption.SYNC_HTTP_CLIENT, c -> resolveSyncHttpClient(c, config))
314317
.option(SdkClientOption.CLIENT_TYPE, SYNC)
315318
.build();
316319
}
317320

321+
private BusinessMetricFeatureId resolveSyncHttpClientConfigType(SdkClientConfiguration config) {
322+
if (config.option(CONFIGURED_SYNC_HTTP_CLIENT) != null) {
323+
return BusinessMetricFeatureId.HTTP_CLIENT_EXPLICIT_INSTANCE;
324+
}
325+
if (config.option(CONFIGURED_SYNC_HTTP_CLIENT_BUILDER) != null) {
326+
return BusinessMetricFeatureId.HTTP_CLIENT_EXPLICIT_FACTORY;
327+
}
328+
return BusinessMetricFeatureId.HTTP_CLIENT_AUTO;
329+
}
330+
318331
/**
319332
* Finalize async-specific configuration from the default-applied configuration.
320333
*/
321334
private SdkClientConfiguration finalizeAsyncConfiguration(SdkClientConfiguration config) {
322335
return config.toBuilder()
323336
.lazyOptionIfAbsent(FUTURE_COMPLETION_EXECUTOR, this::resolveAsyncFutureCompletionExecutor)
337+
.option(HTTP_CLIENT_CONFIG_TYPE, resolveAsyncHttpClientConfigType(config))
324338
.lazyOption(ASYNC_HTTP_CLIENT, c -> resolveAsyncHttpClient(c, config))
325339
.option(SdkClientOption.CLIENT_TYPE, ASYNC)
326340
.build();
327341
}
328342

343+
private BusinessMetricFeatureId resolveAsyncHttpClientConfigType(SdkClientConfiguration config) {
344+
if (config.option(CONFIGURED_ASYNC_HTTP_CLIENT) != null) {
345+
return BusinessMetricFeatureId.HTTP_CLIENT_EXPLICIT_INSTANCE;
346+
}
347+
if (config.option(CONFIGURED_ASYNC_HTTP_CLIENT_BUILDER) != null) {
348+
return BusinessMetricFeatureId.HTTP_CLIENT_EXPLICIT_FACTORY;
349+
}
350+
return BusinessMetricFeatureId.HTTP_CLIENT_AUTO;
351+
}
352+
329353
/**
330354
* Finalize global configuration from the default-applied configuration.
331355
*/
332356
private SdkClientConfiguration finalizeConfiguration(SdkClientConfiguration config) {
333-
return config.toBuilder()
357+
SdkClientConfiguration.Builder builder = config.toBuilder()
334358
.lazyOption(SCHEDULED_EXECUTOR_SERVICE, this::resolveScheduledExecutorService)
335359
.lazyOptionIfAbsent(RETRY_STRATEGY, this::resolveRetryStrategy)
336360
.option(EXECUTION_INTERCEPTORS, resolveExecutionInterceptors(config))
337361
.lazyOption(CLIENT_USER_AGENT, this::resolveClientUserAgent)
338362
.lazyOption(COMPRESSION_CONFIGURATION, this::resolveCompressionConfiguration)
339-
.lazyOptionIfAbsent(IDENTITY_PROVIDERS, c -> IdentityProviders.builder().build())
340-
.build();
363+
.lazyOptionIfAbsent(IDENTITY_PROVIDERS, c -> IdentityProviders.builder().build());
364+
builder.computeOptionIfAbsent(HTTP_CLIENT_CONFIG_TYPE, () -> BusinessMetricFeatureId.HTTP_CLIENT_AUTO);
365+
return builder.build();
341366
}
342367

343368
private CompressionConfiguration resolveCompressionConfiguration(LazyValueSource config) {

core/sdk-core/src/main/java/software/amazon/awssdk/core/client/config/SdkClientOption.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import software.amazon.awssdk.core.retry.RetryMode;
3737
import software.amazon.awssdk.core.retry.RetryPolicy;
3838
import software.amazon.awssdk.core.useragent.BusinessMetricCollection;
39+
import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId;
3940
import software.amazon.awssdk.endpoints.EndpointProvider;
4041
import software.amazon.awssdk.http.SdkHttpClient;
4142
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
@@ -196,6 +197,21 @@ public final class SdkClientOption<T> extends ClientOption<T> {
196197
public static final SdkClientOption<SdkHttpClient.Builder<?>> CONFIGURED_SYNC_HTTP_CLIENT_BUILDER =
197198
new SdkClientOption<>(new UnsafeValueType(SdkAsyncHttpClient.Builder.class));
198199

200+
/**
201+
* The HTTP client configuration type indicating how the HTTP client was selected.
202+
* <p>
203+
* Possible values:
204+
* <ul>
205+
* <li>{@link BusinessMetricFeatureId#HTTP_CLIENT_AUTO} - HTTP client was auto-detected from the classpath</li>
206+
* <li>{@link BusinessMetricFeatureId#HTTP_CLIENT_EXPLICIT_INSTANCE} - HTTP client was explicitly provided
207+
* via {@code httpClient()}</li>
208+
* <li>{@link BusinessMetricFeatureId#HTTP_CLIENT_EXPLICIT_FACTORY} - HTTP client factory was explicitly provided
209+
* via {@code httpClientBuilder()}</li>
210+
* </ul>
211+
*/
212+
public static final SdkClientOption<BusinessMetricFeatureId> HTTP_CLIENT_CONFIG_TYPE =
213+
new SdkClientOption<>(BusinessMetricFeatureId.class);
214+
199215
/**
200216
* Configuration that should be used to build the {@link #SYNC_HTTP_CLIENT} or {@link #ASYNC_HTTP_CLIENT}.
201217
*/

core/sdk-core/src/main/java/software/amazon/awssdk/core/useragent/BusinessMetricFeatureId.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ public enum BusinessMetricFeatureId {
5757
FLEXIBLE_CHECKSUMS_REQ_XXHASH3("AG"),
5858
FLEXIBLE_CHECKSUMS_REQ_XXHASH64("AH"),
5959
FLEXIBLE_CHECKSUMS_REQ_XXHASH128("AI"),
60+
HTTP_CLIENT_AUTO("AJ"),
61+
HTTP_CLIENT_EXPLICIT_INSTANCE("AK"),
62+
HTTP_CLIENT_EXPLICIT_FACTORY("AL"),
6063
DDB_MAPPER("d"),
6164
BEARER_SERVICE_ENV_VARS("3"),
6265
CREDENTIALS_CODE("e"),

0 commit comments

Comments
 (0)