Skip to content

Commit 7f72a11

Browse files
committed
Collect HTTP/1 metrics in NettyNioHttpClient.
1 parent 4c8dd25 commit 7f72a11

29 files changed

+600
-119
lines changed
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,20 @@
1313
* permissions and limitations under the License.
1414
*/
1515

16-
package software.amazon.awssdk.http.apache.internal;
16+
package software.amazon.awssdk.metrics;
1717

18-
import software.amazon.awssdk.annotations.SdkInternalApi;
19-
import software.amazon.awssdk.metrics.MetricCollection;
20-
import software.amazon.awssdk.metrics.MetricCollector;
21-
import software.amazon.awssdk.metrics.SdkMetric;
18+
import software.amazon.awssdk.annotations.SdkPublicApi;
2219

2320
/**
2421
* A metric collector that doesn't do anything.
2522
*/
26-
@SdkInternalApi
23+
@SdkPublicApi
2724
public final class NoOpMetricCollector implements MetricCollector {
2825
private static final NoOpMetricCollector INSTANCE = new NoOpMetricCollector();
2926

27+
private NoOpMetricCollector() {
28+
}
29+
3030
@Override
3131
public String name() {
3232
return "NoOp";

core/metrics-spi/src/main/java/software/amazon/awssdk/metrics/internal/DefaultMetricCollector.java

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,15 @@
2727
import software.amazon.awssdk.metrics.SdkMetric;
2828
import software.amazon.awssdk.utils.Validate;
2929

30+
/**
31+
* TODO: Before launch, we should iterate on the performance of this collector, because it's currently very naive.
32+
*/
3033
@SdkInternalApi
3134
public final class DefaultMetricCollector implements MetricCollector {
3235
private final String name;
33-
private Map<SdkMetric<?>, List<MetricRecord<?>>> metrics = new LinkedHashMap<>();
36+
private final Map<SdkMetric<?>, List<MetricRecord<?>>> metrics = new LinkedHashMap<>();
3437
private final List<MetricCollector> children = new ArrayList<>();
3538

36-
private MetricCollection collection;
37-
3839
public DefaultMetricCollector(String name) {
3940
this.name = name;
4041
}
@@ -45,40 +46,29 @@ public String name() {
4546
}
4647

4748
@Override
48-
public <T> void reportMetric(SdkMetric<T> metric, T data) {
49-
if (collected()) {
50-
throw new IllegalStateException("This collector has already been closed");
51-
}
49+
public synchronized <T> void reportMetric(SdkMetric<T> metric, T data) {
5250
metrics.computeIfAbsent(metric, (m) -> new ArrayList<>())
53-
.add(new DefaultMetricRecord<>(metric, data));
51+
.add(new DefaultMetricRecord<>(metric, data));
5452
}
5553

5654
@Override
57-
public MetricCollector createChild(String name) {
55+
public synchronized MetricCollector createChild(String name) {
5856
MetricCollector child = new DefaultMetricCollector(name);
5957
children.add(child);
6058
return child;
6159
}
6260

6361
@Override
64-
public MetricCollection collect() {
65-
if (!collected()) {
66-
List<MetricCollection> collectedChildren = children.stream()
67-
.map(MetricCollector::collect)
68-
.collect(Collectors.toList());
69-
70-
collection = new DefaultMetricCollection(name, metrics, collectedChildren);
71-
}
62+
public synchronized MetricCollection collect() {
63+
List<MetricCollection> collectedChildren = children.stream()
64+
.map(MetricCollector::collect)
65+
.collect(Collectors.toList());
7266

73-
return collection;
67+
return new DefaultMetricCollection(name, metrics, collectedChildren);
7468
}
7569

7670
public static MetricCollector create(String name) {
7771
Validate.notEmpty(name, "name");
7872
return new DefaultMetricCollector(name);
7973
}
80-
81-
private boolean collected() {
82-
return collection != null;
83-
}
8474
}

core/metrics-spi/src/test/java/software/amazon/awssdk/metrics/internal/DefaultMetricCollectorTest.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,4 @@ public void testCollect_returnedCollectionContainsAllChildren() {
6868
MetricCollection collected = parent.collect();
6969
assertThat(collected.children().stream().map(MetricCollection::name)).containsExactly(childNames);
7070
}
71-
72-
@Test
73-
public void testReportMetric_collected_throws() {
74-
thrown.expect(IllegalStateException.class);
75-
thrown.expectMessage("This collector has already been closed");
76-
77-
MetricCollector collector = MetricCollector.create("collector");
78-
collector.collect();
79-
collector.reportMetric(M1, 42);
80-
}
8171
}

http-client-spi/src/main/java/software/amazon/awssdk/http/HttpExecuteRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public interface Builder {
8383
* report metrics collected for this request.
8484
*
8585
* @param metricCollector The metric collector.
86-
* @return This bilder for method chaining.
86+
* @return This builder for method chaining.
8787
*/
8888
Builder metricCollector(MetricCollector metricCollector);
8989

http-client-spi/src/main/java/software/amazon/awssdk/http/async/AsyncExecuteRequest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515

1616
package software.amazon.awssdk.http.async;
1717

18+
import java.util.Optional;
1819
import software.amazon.awssdk.annotations.SdkPublicApi;
1920
import software.amazon.awssdk.http.SdkHttpRequest;
21+
import software.amazon.awssdk.metrics.MetricCollector;
2022

2123
/**
2224
* Request object containing the parameters necessary to make an asynchronous HTTP request.
@@ -28,12 +30,14 @@ public final class AsyncExecuteRequest {
2830
private final SdkHttpRequest request;
2931
private final SdkHttpContentPublisher requestContentPublisher;
3032
private final SdkAsyncHttpResponseHandler responseHandler;
33+
private final MetricCollector metricCollector;
3134
private final boolean isFullDuplex;
3235

3336
private AsyncExecuteRequest(BuilderImpl builder) {
3437
this.request = builder.request;
3538
this.requestContentPublisher = builder.requestContentPublisher;
3639
this.responseHandler = builder.responseHandler;
40+
this.metricCollector = builder.metricCollector;
3741
this.isFullDuplex = builder.isFullDuplex;
3842
}
3943

@@ -58,6 +62,13 @@ public SdkAsyncHttpResponseHandler responseHandler() {
5862
return responseHandler;
5963
}
6064

65+
/**
66+
* @return The {@link MetricCollector}.
67+
*/
68+
public Optional<MetricCollector> metricCollector() {
69+
return Optional.ofNullable(metricCollector);
70+
}
71+
6172
/**
6273
* @return True if the operation this request belongs to is full duplex. Otherwise false.
6374
*/
@@ -94,6 +105,15 @@ public interface Builder {
94105
*/
95106
Builder responseHandler(SdkAsyncHttpResponseHandler responseHandler);
96107

108+
/**
109+
* Set the {@link MetricCollector} to be used by the HTTP client to
110+
* report metrics collected for this request.
111+
*
112+
* @param metricCollector The metric collector.
113+
* @return This builder for method chaining.
114+
*/
115+
Builder metricCollector(MetricCollector metricCollector);
116+
97117
/**
98118
* Option to indicate if the request is for a full duplex operation ie., request and response are sent/received at
99119
* the same time.
@@ -112,6 +132,7 @@ private static class BuilderImpl implements Builder {
112132
private SdkHttpRequest request;
113133
private SdkHttpContentPublisher requestContentPublisher;
114134
private SdkAsyncHttpResponseHandler responseHandler;
135+
private MetricCollector metricCollector;
115136
private boolean isFullDuplex;
116137

117138
@Override
@@ -132,6 +153,12 @@ public Builder responseHandler(SdkAsyncHttpResponseHandler responseHandler) {
132153
return this;
133154
}
134155

156+
@Override
157+
public Builder metricCollector(MetricCollector metricCollector) {
158+
this.metricCollector = metricCollector;
159+
return this;
160+
}
161+
135162
@Override
136163
public Builder fullDuplex(boolean fullDuplex) {
137164
isFullDuplex = fullDuplex;

http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/ApacheHttpClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@
7878
import software.amazon.awssdk.http.TlsTrustManagersProvider;
7979
import software.amazon.awssdk.http.apache.internal.ApacheHttpRequestConfig;
8080
import software.amazon.awssdk.http.apache.internal.DefaultConfiguration;
81-
import software.amazon.awssdk.http.apache.internal.NoOpMetricCollector;
8281
import software.amazon.awssdk.http.apache.internal.SdkProxyRoutePlanner;
8382
import software.amazon.awssdk.http.apache.internal.conn.ClientConnectionManagerFactory;
8483
import software.amazon.awssdk.http.apache.internal.conn.IdleConnectionReaper;
@@ -89,6 +88,7 @@
8988
import software.amazon.awssdk.http.apache.internal.impl.ConnectionManagerAwareHttpClient;
9089
import software.amazon.awssdk.http.apache.internal.utils.ApacheUtils;
9190
import software.amazon.awssdk.metrics.MetricCollector;
91+
import software.amazon.awssdk.metrics.NoOpMetricCollector;
9292
import software.amazon.awssdk.utils.AttributeMap;
9393
import software.amazon.awssdk.utils.Logger;
9494
import software.amazon.awssdk.utils.Validate;

http-clients/netty-nio-client/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@
4646
<artifactId>utils</artifactId>
4747
<version>${awsjavasdk.version}</version>
4848
</dependency>
49+
<dependency>
50+
<groupId>software.amazon.awssdk</groupId>
51+
<artifactId>metrics-spi</artifactId>
52+
<version>${awsjavasdk.version}</version>
53+
</dependency>
4954

5055
<!--Netty dependencies-->
5156
<dependency>

http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClient.java

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

1616
package software.amazon.awssdk.http.nio.netty;
1717

18+
import static software.amazon.awssdk.http.HttpMetric.HTTP_CLIENT_NAME;
1819
import static software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration.EVENTLOOP_SHUTDOWN_FUTURE_TIMEOUT_SECONDS;
1920
import static software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration.EVENTLOOP_SHUTDOWN_QUIET_PERIOD_SECONDS;
2021
import static software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration.EVENTLOOP_SHUTDOWN_TIMEOUT_SECONDS;
@@ -23,7 +24,6 @@
2324

2425
import io.netty.channel.ChannelOption;
2526
import io.netty.channel.EventLoopGroup;
26-
import io.netty.channel.pool.ChannelPool;
2727
import io.netty.handler.ssl.SslContext;
2828
import io.netty.handler.ssl.SslProvider;
2929
import java.net.URI;
@@ -51,6 +51,7 @@
5151
import software.amazon.awssdk.http.nio.netty.internal.NonManagedEventLoopGroup;
5252
import software.amazon.awssdk.http.nio.netty.internal.RequestContext;
5353
import software.amazon.awssdk.http.nio.netty.internal.SdkChannelOptions;
54+
import software.amazon.awssdk.http.nio.netty.internal.SdkChannelPool;
5455
import software.amazon.awssdk.http.nio.netty.internal.SdkChannelPoolMap;
5556
import software.amazon.awssdk.http.nio.netty.internal.SharedSdkEventLoopGroup;
5657
import software.amazon.awssdk.utils.AttributeMap;
@@ -79,7 +80,7 @@ public final class NettyNioAsyncHttpClient implements SdkAsyncHttpClient {
7980
.build();
8081

8182
private final SdkEventLoopGroup sdkEventLoopGroup;
82-
private final SdkChannelPoolMap<URI, ? extends ChannelPool> pools;
83+
private final SdkChannelPoolMap<URI, ? extends SdkChannelPool> pools;
8384
private final NettyConfiguration configuration;
8485

8586
private NettyNioAsyncHttpClient(DefaultBuilder builder, AttributeMap serviceDefaultsMap) {
@@ -107,7 +108,7 @@ private NettyNioAsyncHttpClient(DefaultBuilder builder, AttributeMap serviceDefa
107108

108109
@SdkTestInternalApi
109110
NettyNioAsyncHttpClient(SdkEventLoopGroup sdkEventLoopGroup,
110-
SdkChannelPoolMap<URI, ? extends ChannelPool> pools,
111+
SdkChannelPoolMap<URI, ? extends SdkChannelPool> pools,
111112
NettyConfiguration configuration) {
112113
this.sdkEventLoopGroup = sdkEventLoopGroup;
113114
this.pools = pools;
@@ -117,6 +118,7 @@ private NettyNioAsyncHttpClient(DefaultBuilder builder, AttributeMap serviceDefa
117118
@Override
118119
public CompletableFuture<Void> execute(AsyncExecuteRequest request) {
119120
RequestContext ctx = createRequestContext(request);
121+
ctx.metricCollector().reportMetric(HTTP_CLIENT_NAME, clientName()); // TODO: Can't this be done in core?
120122
return new NettyRequestExecutor(ctx).execute();
121123
}
122124

@@ -125,7 +127,7 @@ public static Builder builder() {
125127
}
126128

127129
private RequestContext createRequestContext(AsyncExecuteRequest request) {
128-
ChannelPool pool = pools.get(poolKey(request.request()));
130+
SdkChannelPool pool = pools.get(poolKey(request.request()));
129131
return new RequestContext(pool, sdkEventLoopGroup.eventLoopGroup(), request, configuration);
130132
}
131133

http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ protected SimpleChannelPoolAwareChannelPool newPool(URI key) {
150150
baseChannelPool = tcpChannelPool;
151151
}
152152

153-
ChannelPool wrappedPool = wrapBaseChannelPool(bootstrap, baseChannelPool);
153+
SdkChannelPool wrappedPool = wrapBaseChannelPool(bootstrap, baseChannelPool);
154154

155155
channelPoolRef.set(wrappedPool);
156156
return new SimpleChannelPoolAwareChannelPool(wrappedPool, tcpChannelPool);
@@ -231,33 +231,32 @@ private URI proxyAddress(URI remoteHost) {
231231
}
232232
}
233233

234-
private ChannelPool wrapBaseChannelPool(Bootstrap bootstrap, ChannelPool channelPool) {
234+
private SdkChannelPool wrapBaseChannelPool(Bootstrap bootstrap, ChannelPool channelPool) {
235235

236236
// Wrap the channel pool such that the ChannelAttributeKey.CLOSE_ON_RELEASE flag is honored.
237237
channelPool = new HonorCloseOnReleaseChannelPool(channelPool);
238238

239239
// Wrap the channel pool such that HTTP 2 channels won't be released to the underlying pool while they're still in use.
240-
channelPool = new HttpOrHttp2ChannelPool(channelPool,
241-
bootstrap.config().group(),
242-
configuration.maxConnections(),
243-
configuration);
244-
240+
SdkChannelPool sdkChannelPool = new HttpOrHttp2ChannelPool(channelPool,
241+
bootstrap.config().group(),
242+
configuration.maxConnections(),
243+
configuration);
245244

246245
// Wrap the channel pool such that we remove request-specific handlers with each request.
247-
channelPool = new HandlerRemovingChannelPool(channelPool);
246+
sdkChannelPool = new HandlerRemovingChannelPool(sdkChannelPool);
248247

249248
// Wrap the channel pool such that an individual channel can only be released to the underlying pool once.
250-
channelPool = new ReleaseOnceChannelPool(channelPool);
249+
sdkChannelPool = new ReleaseOnceChannelPool(sdkChannelPool);
251250

252251
// Wrap the channel pool to guarantee all channels checked out are healthy, and all unhealthy channels checked in are
253252
// closed.
254-
channelPool = new HealthCheckedChannelPool(bootstrap.config().group(), configuration, channelPool);
253+
sdkChannelPool = new HealthCheckedChannelPool(bootstrap.config().group(), configuration, sdkChannelPool);
255254

256255
// Wrap the channel pool such that if the Promise given to acquire(Promise) is done when the channel is acquired
257256
// from the underlying pool, the channel is closed and released.
258-
channelPool = new CancellableAcquireChannelPool(bootstrap.config().group().next(), channelPool);
257+
sdkChannelPool = new CancellableAcquireChannelPool(bootstrap.config().group().next(), sdkChannelPool);
259258

260-
return channelPool;
259+
return sdkChannelPool;
261260
}
262261

263262
private SslContext sslContext(URI targetAddress) {

http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/CancellableAcquireChannelPool.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import io.netty.util.concurrent.EventExecutor;
2121
import io.netty.util.concurrent.Future;
2222
import io.netty.util.concurrent.Promise;
23+
import java.util.concurrent.CompletableFuture;
2324
import software.amazon.awssdk.annotations.SdkInternalApi;
25+
import software.amazon.awssdk.metrics.MetricCollector;
2426

2527
/**
2628
* Simple decorator {@link ChannelPool} that attempts to complete the promise
@@ -29,11 +31,11 @@
2931
* is closed then released back to the delegate.
3032
*/
3133
@SdkInternalApi
32-
public final class CancellableAcquireChannelPool implements ChannelPool {
34+
public final class CancellableAcquireChannelPool implements SdkChannelPool {
3335
private final EventExecutor executor;
34-
private final ChannelPool delegatePool;
36+
private final SdkChannelPool delegatePool;
3537

36-
public CancellableAcquireChannelPool(EventExecutor executor, ChannelPool delegatePool) {
38+
public CancellableAcquireChannelPool(EventExecutor executor, SdkChannelPool delegatePool) {
3739
this.executor = executor;
3840
this.delegatePool = delegatePool;
3941
}
@@ -73,4 +75,9 @@ public Future<Void> release(Channel channel, Promise<Void> promise) {
7375
public void close() {
7476
delegatePool.close();
7577
}
78+
79+
@Override
80+
public CompletableFuture<Void> collectChannelPoolMetrics(MetricCollector metrics) {
81+
return delegatePool.collectChannelPoolMetrics(metrics);
82+
}
7683
}

0 commit comments

Comments
 (0)