Skip to content

Commit 698575c

Browse files
authored
Diagnostics/debug info for AsyncHttpClient, close AsyncHttpClient#1130
2 parents d8f9f64 + 5f7089e commit 698575c

File tree

12 files changed

+443
-4
lines changed

12 files changed

+443
-4
lines changed

client/src/main/java/org/asynchttpclient/AsyncHttpClient.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,4 +266,11 @@ public interface AsyncHttpClient extends Closeable {
266266
* @return a {@link Future} of type Response
267267
*/
268268
ListenableFuture<Response> executeRequest(RequestBuilder requestBuilder);
269+
270+
/***
271+
* Return details about pooled connections.
272+
*
273+
* @return a {@link ClientStats}
274+
*/
275+
ClientStats getClientStats();
269276
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright (c) 2014 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* Unless required by applicable law or agreed to in writing,
10+
* software distributed under the Apache License Version 2.0 is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
13+
*/
14+
package org.asynchttpclient;
15+
16+
import java.util.Collections;
17+
import java.util.Map;
18+
import java.util.Objects;
19+
20+
/**
21+
* A record class representing the state of an (@link org.asynchttpclient.AsyncHttpClient).
22+
*/
23+
public class ClientStats {
24+
25+
private final Map<String, HostStats> statsPerHost;
26+
27+
public ClientStats(Map<String, HostStats> statsPerHost) {
28+
this.statsPerHost = Collections.unmodifiableMap(statsPerHost);
29+
}
30+
31+
/**
32+
* @return A map from hostname to statistics on that host's connections.
33+
* The returned map is an {@link java.util.Collections.UnmodifiableMap}.
34+
*/
35+
public Map<String, HostStats> getStatsPerHost() {
36+
return statsPerHost;
37+
}
38+
39+
/**
40+
* @return The sum of {@link #getTotalActiveConnectionCount()} and {@link #getTotalIdleConnectionCount()},
41+
* a long representing the total number of connections in the connection pool.
42+
*/
43+
public long getTotalConnectionCount() {
44+
return statsPerHost
45+
.values()
46+
.stream()
47+
.mapToLong(HostStats::getHostConnectionCount)
48+
.sum();
49+
}
50+
51+
/**
52+
* @return A long representing the number of active connections in the connection pool.
53+
*/
54+
public long getTotalActiveConnectionCount() {
55+
return statsPerHost
56+
.values()
57+
.stream()
58+
.mapToLong(HostStats::getHostActiveConnectionCount)
59+
.sum();
60+
}
61+
62+
/**
63+
* @return A long representing the number of idle connections in the connection pool.
64+
*/
65+
public long getTotalIdleConnectionCount() {
66+
return statsPerHost
67+
.values()
68+
.stream()
69+
.mapToLong(HostStats::getHostIdleConnectionCount)
70+
.sum();
71+
}
72+
73+
@Override
74+
public String toString() {
75+
return "There are " + getTotalConnectionCount() +
76+
" total connections, " + getTotalActiveConnectionCount() +
77+
" are active and " + getTotalIdleConnectionCount() + " are idle.";
78+
}
79+
80+
@Override
81+
public boolean equals(final Object o) {
82+
if (this == o) return true;
83+
if (o == null || getClass() != o.getClass()) return false;
84+
final ClientStats that = (ClientStats) o;
85+
return Objects.equals(statsPerHost, that.statsPerHost);
86+
}
87+
88+
@Override
89+
public int hashCode() {
90+
return Objects.hashCode(statsPerHost);
91+
}
92+
}

client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,11 @@ public EventLoopGroup getEventLoopGroup() {
255255
return channelManager.getEventLoopGroup();
256256
}
257257

258+
@Override
259+
public ClientStats getClientStats() {
260+
return channelManager.getClientStats();
261+
}
262+
258263
protected BoundRequestBuilder requestBuilder(String method, String url) {
259264
return new BoundRequestBuilder(this, method, config.isDisableUrlEncodingForBoundRequests()).setUrl(url).setSignatureCalculator(signatureCalculator);
260265
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright (c) 2014 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* Unless required by applicable law or agreed to in writing,
10+
* software distributed under the Apache License Version 2.0 is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
13+
*/
14+
package org.asynchttpclient;
15+
16+
import java.util.Objects;
17+
18+
/**
19+
* A record class representing the status of connections to some host.
20+
*/
21+
public class HostStats {
22+
23+
private final long activeConnectionCount;
24+
private final long idleConnectionCount;
25+
26+
public HostStats(long activeConnectionCount,
27+
long idleConnectionCount) {
28+
this.activeConnectionCount = activeConnectionCount;
29+
this.idleConnectionCount = idleConnectionCount;
30+
}
31+
32+
/**
33+
* @return The sum of {@link #getHostActiveConnectionCount()} and {@link #getHostIdleConnectionCount()},
34+
* a long representing the total number of connections to this host.
35+
*/
36+
public long getHostConnectionCount() {
37+
return activeConnectionCount + idleConnectionCount;
38+
}
39+
40+
/**
41+
* @return A long representing the number of active connections to the host.
42+
*/
43+
public long getHostActiveConnectionCount() {
44+
return activeConnectionCount;
45+
}
46+
47+
/**
48+
* @return A long representing the number of idle connections in the connection pool.
49+
*/
50+
public long getHostIdleConnectionCount() {
51+
return idleConnectionCount;
52+
}
53+
54+
@Override
55+
public String toString() {
56+
return "There are " + getHostConnectionCount() +
57+
" total connections, " + getHostActiveConnectionCount() +
58+
" are active and " + getHostIdleConnectionCount() + " are idle.";
59+
}
60+
61+
@Override
62+
public boolean equals(final Object o) {
63+
if (this == o) return true;
64+
if (o == null || getClass() != o.getClass()) return false;
65+
final HostStats hostStats = (HostStats) o;
66+
return activeConnectionCount == hostStats.activeConnectionCount &&
67+
idleConnectionCount == hostStats.idleConnectionCount;
68+
}
69+
70+
@Override
71+
public int hashCode() {
72+
return Objects.hash(activeConnectionCount, idleConnectionCount);
73+
}
74+
}

client/src/main/java/org/asynchttpclient/channel/ChannelPool.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
*/
1414
package org.asynchttpclient.channel;
1515

16+
import java.util.Map;
17+
1618
import io.netty.channel.Channel;
1719

1820
public interface ChannelPool {
@@ -70,4 +72,9 @@ public interface ChannelPool {
7072
* @param selector the selector
7173
*/
7274
void flushPartitions(ChannelPoolPartitionSelector selector);
75+
76+
/**
77+
* @return The number of idle channels per host.
78+
*/
79+
Map<String, Long> getIdleChannelCountPerHost();
7380
}

client/src/main/java/org/asynchttpclient/channel/NoopChannelPool.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
*/
1414
package org.asynchttpclient.channel;
1515

16+
import java.util.Collections;
17+
import java.util.Map;
18+
1619
import io.netty.channel.Channel;
1720

1821
public enum NoopChannelPool implements ChannelPool {
@@ -50,4 +53,9 @@ public void flushPartition(Object partitionKey) {
5053
@Override
5154
public void flushPartitions(ChannelPoolPartitionSelector selector) {
5255
}
56+
57+
@Override
58+
public Map<String, Long> getIdleChannelCountPerHost() {
59+
return Collections.emptyMap();
60+
}
5361
}

client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,23 @@
4040
import io.netty.util.concurrent.GlobalEventExecutor;
4141

4242
import java.io.IOException;
43+
import java.net.InetSocketAddress;
44+
import java.util.Map;
4345
import java.util.Map.Entry;
46+
import java.util.Optional;
47+
import java.util.OptionalLong;
4448
import java.util.concurrent.ConcurrentHashMap;
4549
import java.util.concurrent.Semaphore;
4650
import java.util.concurrent.ThreadFactory;
4751
import java.util.concurrent.TimeUnit;
52+
import java.util.function.Function;
53+
import java.util.stream.Collectors;
54+
import java.util.stream.Stream;
4855

4956
import javax.net.ssl.SSLEngine;
5057
import javax.net.ssl.SSLException;
5158

52-
import org.asynchttpclient.AsyncHandler;
53-
import org.asynchttpclient.AsyncHttpClientConfig;
54-
import org.asynchttpclient.SslEngineFactory;
59+
import org.asynchttpclient.*;
5560
import org.asynchttpclient.channel.ChannelPool;
5661
import org.asynchttpclient.channel.ChannelPoolPartitioning;
5762
import org.asynchttpclient.channel.NoopChannelPool;
@@ -488,4 +493,28 @@ public ChannelPool getChannelPool() {
488493
public EventLoopGroup getEventLoopGroup() {
489494
return eventLoopGroup;
490495
}
496+
497+
public ClientStats getClientStats() {
498+
final Map<String, Long> totalConnectionsPerHost = openChannels
499+
.stream()
500+
.map(Channel::remoteAddress)
501+
.filter(a -> a.getClass() == InetSocketAddress.class)
502+
.map(a -> (InetSocketAddress) a)
503+
.map(InetSocketAddress::getHostName)
504+
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
505+
final Map<String, Long> idleConnectionsPerHost = channelPool.getIdleChannelCountPerHost();
506+
final Map<String, HostStats> statsPerHost = totalConnectionsPerHost
507+
.entrySet()
508+
.stream()
509+
.collect(Collectors.toMap(
510+
Entry::getKey,
511+
entry -> {
512+
final long totalConnectionCount = entry.getValue();
513+
final long idleConnectionCount = idleConnectionsPerHost.getOrDefault(entry.getKey(), 0L);
514+
final long activeConnectionCount = totalConnectionCount - idleConnectionCount;
515+
return new HostStats(activeConnectionCount, idleConnectionCount);
516+
}
517+
));
518+
return new ClientStats(statsPerHost);
519+
}
491520
}

client/src/main/java/org/asynchttpclient/netty/channel/DefaultChannelPool.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@
2121
import io.netty.util.Timer;
2222
import io.netty.util.TimerTask;
2323

24+
import java.net.InetSocketAddress;
2425
import java.util.*;
2526
import java.util.concurrent.ConcurrentHashMap;
2627
import java.util.concurrent.ConcurrentLinkedDeque;
2728
import java.util.concurrent.TimeUnit;
2829
import java.util.concurrent.atomic.AtomicBoolean;
30+
import java.util.function.Function;
31+
import java.util.stream.Collectors;
2932

3033
import org.asynchttpclient.AsyncHttpClientConfig;
3134
import org.asynchttpclient.channel.ChannelPool;
@@ -116,6 +119,10 @@ public boolean takeOwnership() {
116119
return owned.compareAndSet(false, true);
117120
}
118121

122+
public Channel getChannel() {
123+
return channel;
124+
}
125+
119126
@Override
120127
// only depends on channel
121128
public boolean equals(Object o) {
@@ -352,6 +359,19 @@ public void flushPartitions(ChannelPoolPartitionSelector selector) {
352359
}
353360
}
354361

362+
@Override
363+
public Map<String, Long> getIdleChannelCountPerHost() {
364+
return partitions
365+
.values()
366+
.stream()
367+
.flatMap(ConcurrentLinkedDeque::stream)
368+
.map(idle -> idle.getChannel().remoteAddress())
369+
.filter(a -> a.getClass() == InetSocketAddress.class)
370+
.map(a -> (InetSocketAddress) a)
371+
.map(InetSocketAddress::getHostName)
372+
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
373+
}
374+
355375
public enum PoolLeaseStrategy {
356376
LIFO {
357377
public <E> E lease(Deque<E> d) {

0 commit comments

Comments
 (0)