Skip to content

Added support for HTTP/2 metrics to Netty. #1885

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@

package software.amazon.awssdk.metrics.internal;

import static java.util.stream.Collectors.toList;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.metrics.MetricCollection;
import software.amazon.awssdk.metrics.MetricRecord;
import software.amazon.awssdk.metrics.SdkMetric;
import software.amazon.awssdk.utils.ToString;

@SdkInternalApi
public final class DefaultMetricCollection implements MetricCollection {
Expand Down Expand Up @@ -52,7 +54,7 @@ public <T> List<T> metricValues(SdkMetric<T> metric) {
List<MetricRecord<?>> metricRecords = metrics.get(metric);
List<?> values = metricRecords.stream()
.map(MetricRecord::value)
.collect(Collectors.toList());
.collect(toList());
return (List<T>) Collections.unmodifiableList(values);
}
return Collections.emptyList();
Expand All @@ -69,4 +71,13 @@ public Iterator<MetricRecord<?>> iterator() {
.flatMap(List::stream)
.iterator();
}

@Override
public String toString() {
return ToString.builder("MetricCollection")
.add("name", name)
.add("metrics", metrics.values().stream().flatMap(List::stream).collect(toList()))
.add("children", children)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.metrics.MetricRecord;
import software.amazon.awssdk.metrics.SdkMetric;
import software.amazon.awssdk.utils.ToString;

@SdkInternalApi
public final class DefaultMetricRecord<T> implements MetricRecord<T> {
Expand All @@ -38,4 +39,12 @@ public SdkMetric<T> metric() {
public T value() {
return value;
}

@Override
public String toString() {
return ToString.builder("MetricRecord")
.add("metric", metric.name())
.add("value", value)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.http;

import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.metrics.MetricCategory;
import software.amazon.awssdk.metrics.SdkMetric;

/**
* Metrics collected by HTTP clients for HTTP/2 operations. See {@link HttpMetric} for metrics that are available on both HTTP/1
* and HTTP/2 operations.
*/
@SdkPublicApi
public final class Http2Metric {
/**
* The local HTTP/2 window size in bytes for the stream that this request was executed on.
*
* <p>See https://http2.github.io/http2-spec/#FlowControl for more information on HTTP/2 window sizes.
*/
public static final SdkMetric<Integer> LOCAL_STREAM_WINDOW_SIZE_IN_BYTES = metric("LocalStreamWindowSize", Integer.class);

/**
* The remote HTTP/2 window size in bytes for the stream that this request was executed on.
*
* <p>See https://http2.github.io/http2-spec/#FlowControl for more information on HTTP/2 window sizes.
*/
public static final SdkMetric<Integer> REMOTE_STREAM_WINDOW_SIZE_IN_BYTES = metric("RemoteStreamWindowSize", Integer.class);

private Http2Metric() {
}

private static <T> SdkMetric<T> metric(String name, Class<T> clzz) {
return SdkMetric.create(name, clzz, MetricCategory.DEFAULT, MetricCategory.HTTP_CLIENT);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
import software.amazon.awssdk.metrics.SdkMetric;

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

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like for h2 operations, all metrics here are for streams, should we add metrics for connections?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely confident that it's particularly useful to do so, but it wouldn't be difficult. Maybe we do it if we find it's needed? I'm worried about confusing people if we expose both.

Copy link
Contributor

@zoewangg zoewangg Jun 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think leasedConnection and availableConnection will provide helpful insights when we troubleshoot async issues. For example, in the past, we spent a lot of time searching logs and found out the streams are not balanced allocated across all connections. It'd save us time if we have such metrics available. That would require us to map connection metric with stream metric though.


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

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

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

private HttpMetric() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
import static software.amazon.awssdk.http.HttpMetric.AVAILABLE_CONNECTIONS;
import static software.amazon.awssdk.http.HttpMetric.AVAILABLE_CONCURRENCY;
import static software.amazon.awssdk.http.HttpMetric.HTTP_CLIENT_NAME;
import static software.amazon.awssdk.http.HttpMetric.LEASED_CONNECTIONS;
import static software.amazon.awssdk.http.HttpMetric.MAX_CONNECTIONS;
import static software.amazon.awssdk.http.HttpMetric.PENDING_CONNECTION_ACQUIRES;
import static software.amazon.awssdk.http.HttpMetric.LEASED_CONCURRENCY;
import static software.amazon.awssdk.http.HttpMetric.MAX_CONCURRENCY;
import static software.amazon.awssdk.http.HttpMetric.PENDING_CONCURRENCY_ACQUIRES;
import static software.amazon.awssdk.utils.NumericUtils.saturatedCast;

import java.io.IOException;
Expand Down Expand Up @@ -300,10 +300,10 @@ private void collectPoolMetric(MetricCollector metricCollector) {
if (cm instanceof PoolingHttpClientConnectionManager) {
PoolingHttpClientConnectionManager poolingCm = (PoolingHttpClientConnectionManager) cm;
PoolStats totalStats = poolingCm.getTotalStats();
metricCollector.reportMetric(MAX_CONNECTIONS, totalStats.getMax());
metricCollector.reportMetric(AVAILABLE_CONNECTIONS, totalStats.getAvailable());
metricCollector.reportMetric(LEASED_CONNECTIONS, totalStats.getLeased());
metricCollector.reportMetric(PENDING_CONNECTION_ACQUIRES, totalStats.getPending());
metricCollector.reportMetric(MAX_CONCURRENCY, totalStats.getMax());
metricCollector.reportMetric(AVAILABLE_CONCURRENCY, totalStats.getAvailable());
metricCollector.reportMetric(LEASED_CONCURRENCY, totalStats.getLeased());
metricCollector.reportMetric(PENDING_CONCURRENCY_ACQUIRES, totalStats.getPending());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static software.amazon.awssdk.http.HttpMetric.AVAILABLE_CONNECTIONS;
import static software.amazon.awssdk.http.HttpMetric.AVAILABLE_CONCURRENCY;
import static software.amazon.awssdk.http.HttpMetric.HTTP_CLIENT_NAME;
import static software.amazon.awssdk.http.HttpMetric.LEASED_CONNECTIONS;
import static software.amazon.awssdk.http.HttpMetric.MAX_CONNECTIONS;
import static software.amazon.awssdk.http.HttpMetric.PENDING_CONNECTION_ACQUIRES;
import static software.amazon.awssdk.http.HttpMetric.LEASED_CONCURRENCY;
import static software.amazon.awssdk.http.HttpMetric.MAX_CONCURRENCY;
import static software.amazon.awssdk.http.HttpMetric.PENDING_CONCURRENCY_ACQUIRES;
import java.io.IOException;
import java.time.Duration;
import org.apache.http.HttpVersion;
Expand Down Expand Up @@ -77,10 +77,10 @@ public void prepareRequest_callableCalled_metricsReported() throws IOException {
MetricCollection collected = collector.collect();

assertThat(collected.metricValues(HTTP_CLIENT_NAME)).containsExactly("Apache");
assertThat(collected.metricValues(LEASED_CONNECTIONS)).containsExactly(1);
assertThat(collected.metricValues(PENDING_CONNECTION_ACQUIRES)).containsExactly(2);
assertThat(collected.metricValues(AVAILABLE_CONNECTIONS)).containsExactly(3);
assertThat(collected.metricValues(MAX_CONNECTIONS)).containsExactly(4);
assertThat(collected.metricValues(LEASED_CONCURRENCY)).containsExactly(1);
assertThat(collected.metricValues(PENDING_CONCURRENCY_ACQUIRES)).containsExactly(2);
assertThat(collected.metricValues(AVAILABLE_CONCURRENCY)).containsExactly(3);
assertThat(collected.metricValues(MAX_CONCURRENCY)).containsExactly(4);
}

@Test
Expand All @@ -95,10 +95,10 @@ public void prepareRequest_connectionManagerNotPooling_callableCalled_metricsRep
MetricCollection collected = collector.collect();

assertThat(collected.metricValues(HTTP_CLIENT_NAME)).containsExactly("Apache");
assertThat(collected.metricValues(LEASED_CONNECTIONS)).isEmpty();
assertThat(collected.metricValues(PENDING_CONNECTION_ACQUIRES)).isEmpty();
assertThat(collected.metricValues(AVAILABLE_CONNECTIONS)).isEmpty();
assertThat(collected.metricValues(MAX_CONNECTIONS)).isEmpty();
assertThat(collected.metricValues(LEASED_CONCURRENCY)).isEmpty();
assertThat(collected.metricValues(PENDING_CONCURRENCY_ACQUIRES)).isEmpty();
assertThat(collected.metricValues(AVAILABLE_CONCURRENCY)).isEmpty();
assertThat(collected.metricValues(MAX_CONCURRENCY)).isEmpty();
}

private ApacheHttpClient newClient() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io.netty.channel.Channel;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http2.Http2Connection;
import io.netty.handler.codec.http2.Http2FrameStream;
import io.netty.util.AttributeKey;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;
Expand Down Expand Up @@ -61,6 +62,13 @@ public final class ChannelAttributeKey {
public static final AttributeKey<Long> MAX_CONCURRENT_STREAMS = AttributeKey.newInstance(
"aws.http.nio.netty.async.maxConcurrentStreams");

/**
* The {@link Http2FrameStream} associated with this stream channel. This is added to stream channels when they are created,
* before they are fully initialized.
*/
public static final AttributeKey<Http2FrameStream> HTTP2_FRAME_STREAM = AttributeKey.newInstance(
"aws.http.nio.netty.async.http2FrameStream");

/**
* {@link AttributeKey} to keep track of whether we should close the connection after this request
* has completed.
Expand Down
Loading