Skip to content

Commit 9032023

Browse files
committed
Added support for HTTP/2 metrics to Netty.
Updated existing HTTP metrics to specify that they are "concurrency", not "connections". For HTTP/2, we can't calculate the maximum number of connections, and concurrency is more useful to customers anyway. Fixed the AVAILABLE_CONCURRENCY in Netty to actually be the number of established but idle concurrency, not the difference between max concurrency and leased concurrency.
1 parent c882aaa commit 9032023

File tree

20 files changed

+1124
-90
lines changed

20 files changed

+1124
-90
lines changed

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@
1515

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

18+
import static java.util.stream.Collectors.toList;
19+
1820
import java.util.Collections;
1921
import java.util.Iterator;
2022
import java.util.List;
2123
import java.util.Map;
22-
import java.util.stream.Collectors;
2324
import software.amazon.awssdk.annotations.SdkInternalApi;
2425
import software.amazon.awssdk.metrics.MetricCollection;
2526
import software.amazon.awssdk.metrics.MetricRecord;
2627
import software.amazon.awssdk.metrics.SdkMetric;
28+
import software.amazon.awssdk.utils.ToString;
2729

2830
@SdkInternalApi
2931
public final class DefaultMetricCollection implements MetricCollection {
@@ -52,7 +54,7 @@ public <T> List<T> metricValues(SdkMetric<T> metric) {
5254
List<MetricRecord<?>> metricRecords = metrics.get(metric);
5355
List<?> values = metricRecords.stream()
5456
.map(MetricRecord::value)
55-
.collect(Collectors.toList());
57+
.collect(toList());
5658
return (List<T>) Collections.unmodifiableList(values);
5759
}
5860
return Collections.emptyList();
@@ -69,4 +71,13 @@ public Iterator<MetricRecord<?>> iterator() {
6971
.flatMap(List::stream)
7072
.iterator();
7173
}
74+
75+
@Override
76+
public String toString() {
77+
return ToString.builder("MetricCollection")
78+
.add("name", name)
79+
.add("metrics", metrics.values().stream().flatMap(List::stream).collect(toList()))
80+
.add("children", children)
81+
.build();
82+
}
7283
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import software.amazon.awssdk.annotations.SdkInternalApi;
1919
import software.amazon.awssdk.metrics.MetricRecord;
2020
import software.amazon.awssdk.metrics.SdkMetric;
21+
import software.amazon.awssdk.utils.ToString;
2122

2223
@SdkInternalApi
2324
public final class DefaultMetricRecord<T> implements MetricRecord<T> {
@@ -38,4 +39,12 @@ public SdkMetric<T> metric() {
3839
public T value() {
3940
return value;
4041
}
42+
43+
@Override
44+
public String toString() {
45+
return ToString.builder("MetricRecord")
46+
.add("metric", metric.name())
47+
.add("value", value)
48+
.build();
49+
}
4150
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http;
17+
18+
import software.amazon.awssdk.annotations.SdkPublicApi;
19+
import software.amazon.awssdk.metrics.MetricCategory;
20+
import software.amazon.awssdk.metrics.SdkMetric;
21+
22+
/**
23+
* Metrics collected by HTTP clients for HTTP/2 operations. See {@link HttpMetric} for metrics that are available on both HTTP/1
24+
* and HTTP/2 operations.
25+
*/
26+
@SdkPublicApi
27+
public final class Http2Metric {
28+
/**
29+
* The local HTTP/2 window size in bytes for the stream that this request was executed on.
30+
*
31+
* <p>See https://http2.github.io/http2-spec/#FlowControl for more information on HTTP/2 window sizes.
32+
*/
33+
public static final SdkMetric<Integer> LOCAL_STREAM_WINDOW_SIZE_IN_BYTES = metric("LocalStreamWindowSize", Integer.class);
34+
35+
/**
36+
* The remote HTTP/2 window size in bytes for the stream that this request was executed on.
37+
*
38+
* <p>See https://http2.github.io/http2-spec/#FlowControl for more information on HTTP/2 window sizes.
39+
*/
40+
public static final SdkMetric<Integer> REMOTE_STREAM_WINDOW_SIZE_IN_BYTES = metric("RemoteStreamWindowSize", Integer.class);
41+
42+
private Http2Metric() {
43+
}
44+
45+
private static <T> SdkMetric<T> metric(String name, Class<T> clzz) {
46+
return SdkMetric.create(name, clzz, MetricCategory.DEFAULT, MetricCategory.HTTP_CLIENT);
47+
}
48+
}

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

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
import software.amazon.awssdk.metrics.SdkMetric;
2121

2222
/**
23-
* Metrics collected by HTTP clients.
23+
* Metrics collected by HTTP clients for HTTP/1 and HTTP/2 operations. See {@link Http2Metric} for metrics that are only available
24+
* on HTTP/2 operations.
2425
*/
2526
@SdkPublicApi
2627
public final class HttpMetric {
@@ -30,24 +31,64 @@ public final class HttpMetric {
3031
public static final SdkMetric<String> HTTP_CLIENT_NAME = metric("HttpClientName", String.class);
3132

3233
/**
33-
* The maximum number of connections that will be pooled by the HTTP client.
34+
* The maximum number of concurrent requests that is supported by the HTTP client.
35+
*
36+
* <p>For HTTP/1 operations, this is equal to the maximum number of TCP connections that can be be pooled by the HTTP client.
37+
* For HTTP/2 operations, this is equal to the maximum number of streams that can be pooled by the HTTP client.
38+
*
39+
* <p>Note: Depending on the HTTP client, this is either a value for all endpoints served by the HTTP client, or a value
40+
* that applies only to the specific endpoint/host used in the request. For 'apache-http-client', this value is
41+
* for the entire HTTP client. For 'netty-nio-client', this value is per-endpoint. In all cases, this value is scoped to an
42+
* individual HTTP client instance, and does not include concurrency that may be available in other HTTP clients running
43+
* within the same JVM.
3444
*/
35-
public static final SdkMetric<Integer> MAX_CONNECTIONS = metric("MaxConnections", Integer.class);
45+
public static final SdkMetric<Integer> MAX_CONCURRENCY = metric("MaxConcurrency", Integer.class);
3646

3747
/**
38-
* The number of idle connections in the connection pool that are ready to serve a request.
48+
* The number of additional concurrent requests that can be supported by the HTTP client without needing to establish
49+
* additional connections to the target server.
50+
*
51+
* <p>For HTTP/1 operations, this is equal to the number of TCP connections that have been established with the service,
52+
* but are currently idle/unused. For HTTP/2 operations, this is equal to the number of streams that are currently
53+
* idle/unused.
54+
*
55+
* <p>Note: Depending on the HTTP client, this is either a value for all endpoints served by the HTTP client, or a value
56+
* that applies only to the specific endpoint/host used in the request. For 'apache-http-client', this value is
57+
* for the entire HTTP client. For 'netty-nio-client', this value is per-endpoint. In all cases, this value is scoped to an
58+
* individual HTTP client instance, and does not include concurrency that may be available in other HTTP clients running
59+
* within the same JVM.
3960
*/
40-
public static final SdkMetric<Integer> AVAILABLE_CONNECTIONS = metric("AvailableConnections", Integer.class);
61+
public static final SdkMetric<Integer> AVAILABLE_CONCURRENCY = metric("AvailableConcurrency", Integer.class);
4162

4263
/**
43-
* The number of connections from the connection pool that are busy serving requests.
64+
* The number of requests that are currently being executed by the HTTP client.
65+
*
66+
* <p>For HTTP/1 operations, this is equal to the number of TCP connections currently in active communication with the service
67+
* (excluding idle connections). For HTTP/2 operations, this is equal to the number of HTTP streams currently in active
68+
* communication with the service (excluding idle stream capacity).
69+
*
70+
* <p>Note: Depending on the HTTP client, this is either a value for all endpoints served by the HTTP client, or a value
71+
* that applies only to the specific endpoint/host used in the request. For 'apache-http-client', this value is
72+
* for the entire HTTP client. For 'netty-nio-client', this value is per-endpoint. In all cases, this value is scoped to an
73+
* individual HTTP client instance, and does not include concurrency that may be available in other HTTP clients running
74+
* within the same JVM.
4475
*/
45-
public static final SdkMetric<Integer> LEASED_CONNECTIONS = metric("LeasedConnections", Integer.class);
76+
public static final SdkMetric<Integer> LEASED_CONCURRENCY = metric("LeasedConcurrency", Integer.class);
4677

4778
/**
48-
* The number of requests awaiting a free connection from the pool.
79+
* The number of requests that are awaiting concurrency to be made available from the HTTP client.
80+
*
81+
* <p>For HTTP/1 operations, this is equal to the number of requests currently blocked, waiting for a TCP connection to be
82+
* established or returned from the connection pool. For HTTP/2 operations, this is equal to the number of requests currently
83+
* blocked, waiting for a new stream (and possibly a new HTTP/2 connection) from the connection pool.
84+
*
85+
* <p>Note: Depending on the HTTP client, this is either a value for all endpoints served by the HTTP client, or a value
86+
* that applies only to the specific endpoint/host used in the request. For 'apache-http-client', this value is
87+
* for the entire HTTP client. For 'netty-nio-client', this value is per-endpoint. In all cases, this value is scoped to an
88+
* individual HTTP client instance, and does not include concurrency that may be available in other HTTP clients running
89+
* within the same JVM.
4990
*/
50-
public static final SdkMetric<Integer> PENDING_CONNECTION_ACQUIRES = metric("PendingConnectionAcquires", Integer.class);
91+
public static final SdkMetric<Integer> PENDING_CONCURRENCY_ACQUIRES = metric("PendingConcurrencyAcquires", Integer.class);
5192

5293
private HttpMetric() {
5394
}

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
import static java.util.stream.Collectors.groupingBy;
1919
import static java.util.stream.Collectors.mapping;
2020
import static java.util.stream.Collectors.toList;
21-
import static software.amazon.awssdk.http.HttpMetric.AVAILABLE_CONNECTIONS;
21+
import static software.amazon.awssdk.http.HttpMetric.AVAILABLE_CONCURRENCY;
2222
import static software.amazon.awssdk.http.HttpMetric.HTTP_CLIENT_NAME;
23-
import static software.amazon.awssdk.http.HttpMetric.LEASED_CONNECTIONS;
24-
import static software.amazon.awssdk.http.HttpMetric.MAX_CONNECTIONS;
25-
import static software.amazon.awssdk.http.HttpMetric.PENDING_CONNECTION_ACQUIRES;
23+
import static software.amazon.awssdk.http.HttpMetric.LEASED_CONCURRENCY;
24+
import static software.amazon.awssdk.http.HttpMetric.MAX_CONCURRENCY;
25+
import static software.amazon.awssdk.http.HttpMetric.PENDING_CONCURRENCY_ACQUIRES;
2626
import static software.amazon.awssdk.utils.NumericUtils.saturatedCast;
2727

2828
import java.io.IOException;
@@ -300,10 +300,10 @@ private void collectPoolMetric(MetricCollector metricCollector) {
300300
if (cm instanceof PoolingHttpClientConnectionManager) {
301301
PoolingHttpClientConnectionManager poolingCm = (PoolingHttpClientConnectionManager) cm;
302302
PoolStats totalStats = poolingCm.getTotalStats();
303-
metricCollector.reportMetric(MAX_CONNECTIONS, totalStats.getMax());
304-
metricCollector.reportMetric(AVAILABLE_CONNECTIONS, totalStats.getAvailable());
305-
metricCollector.reportMetric(LEASED_CONNECTIONS, totalStats.getLeased());
306-
metricCollector.reportMetric(PENDING_CONNECTION_ACQUIRES, totalStats.getPending());
303+
metricCollector.reportMetric(MAX_CONCURRENCY, totalStats.getMax());
304+
metricCollector.reportMetric(AVAILABLE_CONCURRENCY, totalStats.getAvailable());
305+
metricCollector.reportMetric(LEASED_CONCURRENCY, totalStats.getLeased());
306+
metricCollector.reportMetric(PENDING_CONCURRENCY_ACQUIRES, totalStats.getPending());
307307
}
308308
}
309309

http-clients/apache-client/src/test/java/software/amazon/awssdk/http/apache/MetricReportingTest.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@
1919
import static org.mockito.Matchers.any;
2020
import static org.mockito.Mockito.mock;
2121
import static org.mockito.Mockito.when;
22-
import static software.amazon.awssdk.http.HttpMetric.AVAILABLE_CONNECTIONS;
22+
import static software.amazon.awssdk.http.HttpMetric.AVAILABLE_CONCURRENCY;
2323
import static software.amazon.awssdk.http.HttpMetric.HTTP_CLIENT_NAME;
24-
import static software.amazon.awssdk.http.HttpMetric.LEASED_CONNECTIONS;
25-
import static software.amazon.awssdk.http.HttpMetric.MAX_CONNECTIONS;
26-
import static software.amazon.awssdk.http.HttpMetric.PENDING_CONNECTION_ACQUIRES;
24+
import static software.amazon.awssdk.http.HttpMetric.LEASED_CONCURRENCY;
25+
import static software.amazon.awssdk.http.HttpMetric.MAX_CONCURRENCY;
26+
import static software.amazon.awssdk.http.HttpMetric.PENDING_CONCURRENCY_ACQUIRES;
2727
import java.io.IOException;
2828
import java.time.Duration;
2929
import org.apache.http.HttpVersion;
@@ -77,10 +77,10 @@ public void prepareRequest_callableCalled_metricsReported() throws IOException {
7777
MetricCollection collected = collector.collect();
7878

7979
assertThat(collected.metricValues(HTTP_CLIENT_NAME)).containsExactly("Apache");
80-
assertThat(collected.metricValues(LEASED_CONNECTIONS)).containsExactly(1);
81-
assertThat(collected.metricValues(PENDING_CONNECTION_ACQUIRES)).containsExactly(2);
82-
assertThat(collected.metricValues(AVAILABLE_CONNECTIONS)).containsExactly(3);
83-
assertThat(collected.metricValues(MAX_CONNECTIONS)).containsExactly(4);
80+
assertThat(collected.metricValues(LEASED_CONCURRENCY)).containsExactly(1);
81+
assertThat(collected.metricValues(PENDING_CONCURRENCY_ACQUIRES)).containsExactly(2);
82+
assertThat(collected.metricValues(AVAILABLE_CONCURRENCY)).containsExactly(3);
83+
assertThat(collected.metricValues(MAX_CONCURRENCY)).containsExactly(4);
8484
}
8585

8686
@Test
@@ -95,10 +95,10 @@ public void prepareRequest_connectionManagerNotPooling_callableCalled_metricsRep
9595
MetricCollection collected = collector.collect();
9696

9797
assertThat(collected.metricValues(HTTP_CLIENT_NAME)).containsExactly("Apache");
98-
assertThat(collected.metricValues(LEASED_CONNECTIONS)).isEmpty();
99-
assertThat(collected.metricValues(PENDING_CONNECTION_ACQUIRES)).isEmpty();
100-
assertThat(collected.metricValues(AVAILABLE_CONNECTIONS)).isEmpty();
101-
assertThat(collected.metricValues(MAX_CONNECTIONS)).isEmpty();
98+
assertThat(collected.metricValues(LEASED_CONCURRENCY)).isEmpty();
99+
assertThat(collected.metricValues(PENDING_CONCURRENCY_ACQUIRES)).isEmpty();
100+
assertThat(collected.metricValues(AVAILABLE_CONCURRENCY)).isEmpty();
101+
assertThat(collected.metricValues(MAX_CONCURRENCY)).isEmpty();
102102
}
103103

104104
private ApacheHttpClient newClient() {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.netty.channel.Channel;
1919
import io.netty.handler.codec.http.LastHttpContent;
2020
import io.netty.handler.codec.http2.Http2Connection;
21+
import io.netty.handler.codec.http2.Http2FrameStream;
2122
import io.netty.util.AttributeKey;
2223
import java.nio.ByteBuffer;
2324
import java.util.concurrent.CompletableFuture;
@@ -61,6 +62,13 @@ public final class ChannelAttributeKey {
6162
public static final AttributeKey<Long> MAX_CONCURRENT_STREAMS = AttributeKey.newInstance(
6263
"aws.http.nio.netty.async.maxConcurrentStreams");
6364

65+
/**
66+
* The {@link Http2FrameStream} associated with this stream channel. This is added to stream channels when they are created,
67+
* before they are fully initialized.
68+
*/
69+
public static final AttributeKey<Http2FrameStream> HTTP2_FRAME_STREAM = AttributeKey.newInstance(
70+
"aws.http.nio.netty.async.http2FrameStream");
71+
6472
/**
6573
* {@link AttributeKey} to keep track of whether we should close the connection after this request
6674
* has completed.

0 commit comments

Comments
 (0)