Skip to content
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
7 changes: 7 additions & 0 deletions client/src/main/java/org/asynchttpclient/AsyncHttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -266,4 +266,11 @@ public interface AsyncHttpClient extends Closeable {
* @return a {@link Future} of type Response
*/
ListenableFuture<Response> executeRequest(RequestBuilder requestBuilder);

/***
* Return details about pooled connections.
*
* @return a {@link ClientStats}
*/
ClientStats getClientStats();
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's aim for AHC 2.1 so we can change this API.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Then I should reopen this PR against branch "2.1", right?

Copy link
Contributor

Choose a reason for hiding this comment

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

No. The 2.1 branch will be dropped by a few weeks when I'll move the master to Netty 4.1. This branch was solely for the Play people so they can make progress on their own upgrade. It lags behind master in terms for bug fixes and refactoring.

}
92 changes: 92 additions & 0 deletions client/src/main/java/org/asynchttpclient/ClientStats.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2014 AsyncHttpClient Project. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
package org.asynchttpclient;

import java.util.Collections;
import java.util.Map;
import java.util.Objects;

/**
* A record class representing the state of an (@link org.asynchttpclient.AsyncHttpClient).
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

please remove

Copy link
Contributor

Choose a reason for hiding this comment

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

javadoc

public class ClientStats {

private final Map<String, HostStats> statsPerHost;

public ClientStats(Map<String, HostStats> statsPerHost) {
this.statsPerHost = Collections.unmodifiableMap(statsPerHost);
Copy link
Contributor Author

@Diagoras Diagoras Nov 6, 2016

Choose a reason for hiding this comment

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

Java doesn't have immutable collections and Guava (with its ImmutableMap) isn't in scope, so I went with this. Sadly, there's no way to have the type reflect that this Map is immutable, so it's noted in the Javadoc instead.

}

/**
* @return A map from hostname to statistics on that host's connections.
* The returned map is an {@link java.util.Collections.UnmodifiableMap}.
*/
public Map<String, HostStats> getStatsPerHost() {
return statsPerHost;
}

/**
* @return The sum of {@link #getTotalActiveConnectionCount()} and {@link #getTotalIdleConnectionCount()},
* a long representing the total number of connections in the connection pool.
*/
public long getTotalConnectionCount() {
Copy link
Contributor

Choose a reason for hiding this comment

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

javadoc

return statsPerHost
.values()
.stream()
.mapToLong(HostStats::getHostConnectionCount)
.sum();
}

/**
* @return A long representing the number of active connections in the connection pool.
*/
public long getTotalActiveConnectionCount() {
return statsPerHost
.values()
.stream()
.mapToLong(HostStats::getHostActiveConnectionCount)
.sum();
}

/**
* @return A long representing the number of idle connections in the connection pool.
*/
public long getTotalIdleConnectionCount() {
return statsPerHost
.values()
.stream()
.mapToLong(HostStats::getHostIdleConnectionCount)
.sum();
}

@Override
public String toString() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Went ahead and kept the old toString, but I'm wondering if it should be changed to also call toString on all the HostStatistics in the "statsPerHost" Map, or if that would be too verbose for a default.

return "There are " + getTotalConnectionCount() +
" total connections, " + getTotalActiveConnectionCount() +
" are active and " + getTotalIdleConnectionCount() + " are idle.";
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final ClientStats that = (ClientStats) o;
return Objects.equals(statsPerHost, that.statsPerHost);
}

@Override
public int hashCode() {
return Objects.hashCode(statsPerHost);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

implement equals and hashcode

}
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,11 @@ public EventLoopGroup getEventLoopGroup() {
return channelManager.getEventLoopGroup();
}

@Override
public ClientStats getClientStats() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be declared in AsyncHttpClient interface.
Implementation here should be flagged with @OverRide

Copy link
Contributor

Choose a reason for hiding this comment

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

Performing the computation should be the responsibility of ChannelManager, not DefaultAsyncHttpClient.

return channelManager.getClientStats();
}

protected BoundRequestBuilder requestBuilder(String method, String url) {
return new BoundRequestBuilder(this, method, config.isDisableUrlEncodingForBoundRequests()).setUrl(url).setSignatureCalculator(signatureCalculator);
}
Expand Down
74 changes: 74 additions & 0 deletions client/src/main/java/org/asynchttpclient/HostStats.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright (c) 2014 AsyncHttpClient Project. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
package org.asynchttpclient;

import java.util.Objects;

/**
* A record class representing the status of connections to some host.
*/
public class HostStats {

private final long activeConnectionCount;
private final long idleConnectionCount;

public HostStats(long activeConnectionCount,
long idleConnectionCount) {
this.activeConnectionCount = activeConnectionCount;
this.idleConnectionCount = idleConnectionCount;
}

/**
* @return The sum of {@link #getHostActiveConnectionCount()} and {@link #getHostIdleConnectionCount()},
* a long representing the total number of connections to this host.
*/
public long getHostConnectionCount() {
return activeConnectionCount + idleConnectionCount;
}

/**
* @return A long representing the number of active connections to the host.
*/
public long getHostActiveConnectionCount() {
return activeConnectionCount;
}

/**
* @return A long representing the number of idle connections in the connection pool.
*/
public long getHostIdleConnectionCount() {
return idleConnectionCount;
}

@Override
public String toString() {
return "There are " + getHostConnectionCount() +
" total connections, " + getHostActiveConnectionCount() +
" are active and " + getHostIdleConnectionCount() + " are idle.";
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final HostStats hostStats = (HostStats) o;
return activeConnectionCount == hostStats.activeConnectionCount &&
idleConnectionCount == hostStats.idleConnectionCount;
}

@Override
public int hashCode() {
return Objects.hash(activeConnectionCount, idleConnectionCount);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
*/
package org.asynchttpclient.channel;

import java.util.Map;

import io.netty.channel.Channel;

public interface ChannelPool {
Expand Down Expand Up @@ -70,4 +72,9 @@ public interface ChannelPool {
* @param selector the selector
*/
void flushPartitions(ChannelPoolPartitionSelector selector);

/**
* @return The number of idle channels per host.
*/
Map<String, Long> getIdleChannelCountPerHost();
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
*/
package org.asynchttpclient.channel;

import java.util.Collections;
import java.util.Map;

import io.netty.channel.Channel;

public enum NoopChannelPool implements ChannelPool {
Expand Down Expand Up @@ -50,4 +53,9 @@ public void flushPartition(Object partitionKey) {
@Override
public void flushPartitions(ChannelPoolPartitionSelector selector) {
}

@Override
public Map<String, Long> getIdleChannelCountPerHost() {
return Collections.emptyMap();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,23 @@
import io.netty.util.concurrent.GlobalEventExecutor;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;

import org.asynchttpclient.AsyncHandler;
import org.asynchttpclient.AsyncHttpClientConfig;
import org.asynchttpclient.SslEngineFactory;
import org.asynchttpclient.*;
import org.asynchttpclient.channel.ChannelPool;
import org.asynchttpclient.channel.ChannelPoolPartitioning;
import org.asynchttpclient.channel.NoopChannelPool;
Expand Down Expand Up @@ -488,4 +493,28 @@ public ChannelPool getChannelPool() {
public EventLoopGroup getEventLoopGroup() {
return eventLoopGroup;
}

public ClientStats getClientStats() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The implementation of this method does not make me happy, but I really don't see a better way to do this with Java 8 Streams. If you think there's a better approach, let me know.

final Map<String, Long> totalConnectionsPerHost = openChannels
.stream()
.map(Channel::remoteAddress)
.filter(a -> a.getClass() == InetSocketAddress.class)
.map(a -> (InetSocketAddress) a)
Copy link
Contributor Author

@Diagoras Diagoras Nov 6, 2016

Choose a reason for hiding this comment

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

So this sucks, but I didn't really see another way out. Are there any other types of socket that are likely to live here or in DefaultChannelPool, or is this a mostly "safe" thing to do?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's pretty safe to cast

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, the filter beforehand means we're not going to see an exception. I was just worried about leaving out other kinds of sockets from the stats, but it sounds like we're good.

.map(InetSocketAddress::getHostName)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
final Map<String, Long> idleConnectionsPerHost = channelPool.getIdleChannelCountPerHost();
final Map<String, HostStats> statsPerHost = totalConnectionsPerHost
.entrySet()
.stream()
.collect(Collectors.toMap(
Entry::getKey,
entry -> {
final long totalConnectionCount = entry.getValue();
final long idleConnectionCount = idleConnectionsPerHost.getOrDefault(entry.getKey(), 0L);
final long activeConnectionCount = totalConnectionCount - idleConnectionCount;
return new HostStats(activeConnectionCount, idleConnectionCount);
}
));
return new ClientStats(statsPerHost);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@
import io.netty.util.Timer;
import io.netty.util.TimerTask;

import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.asynchttpclient.AsyncHttpClientConfig;
import org.asynchttpclient.channel.ChannelPool;
Expand Down Expand Up @@ -120,6 +123,10 @@ public boolean takeOwnership() {
return owned.compareAndSet(false, true);
}

public Channel getChannel() {
return channel;
}

@Override
// only depends on channel
public boolean equals(Object o) {
Expand Down Expand Up @@ -356,6 +363,19 @@ public void flushPartitions(ChannelPoolPartitionSelector selector) {
}
}

@Override
public Map<String, Long> getIdleChannelCountPerHost() {
return partitions
.values()
.stream()
.flatMap(ConcurrentLinkedDeque::stream)
.map(idle -> idle.getChannel().remoteAddress())
.filter(a -> a.getClass() == InetSocketAddress.class)
.map(a -> (InetSocketAddress) a)
.map(InetSocketAddress::getHostName)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
}

public enum PoolLeaseStrategy {
LIFO {
public <E> E lease(Deque<E> d) {
Expand Down
Loading