Skip to content
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

Add support for Apache HttpClient 5.x #1543

Merged
merged 2 commits into from
Feb 24, 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
59 changes: 59 additions & 0 deletions metrics-httpclient5/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-parent</artifactId>
<version>4.1.4-SNAPSHOT</version>
</parent>

<artifactId>metrics-httpclient5</artifactId>
<name>Metrics Integration for Apache HttpClient 5.x</name>
<packaging>bundle</packaging>
<description>
An Apache HttpClient 5.x wrapper providing Metrics instrumentation of connection pools, request
durations and rates, and other useful information.
</description>

<properties>
<javaModuleName>com.codahale.metrics.httpclient</javaModuleName>
<http-client.version>5.0</http-client.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-bom</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>${http-client.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>4.0.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.codahale.metrics.httpclient5;

import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.net.URIBuilder;

import java.net.URISyntaxException;
import java.util.Locale;

import static com.codahale.metrics.MetricRegistry.name;

public class HttpClientMetricNameStrategies {

public static final HttpClientMetricNameStrategy METHOD_ONLY =
(name, request) -> name(HttpClient.class,
name,
methodNameString(request));

public static final HttpClientMetricNameStrategy HOST_AND_METHOD =
(name, request) -> {
try {
return name(HttpClient.class,
name,
request.getUri().getHost(),
methodNameString(request));
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
};

public static final HttpClientMetricNameStrategy QUERYLESS_URL_AND_METHOD =
(name, request) -> {
try {
final URIBuilder url = new URIBuilder(request.getUri());
return name(HttpClient.class,
name,
url.removeQuery().build().toString(),
methodNameString(request));
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
}
};

private static String methodNameString(HttpRequest request) {
return request.getMethod().toLowerCase(Locale.ROOT) + "-requests";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.codahale.metrics.httpclient5;

import com.codahale.metrics.MetricRegistry;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.core5.http.HttpRequest;

@FunctionalInterface
public interface HttpClientMetricNameStrategy {

String getNameFor(String name, HttpRequest request);

default String getNameFor(String name, Exception exception) {
return MetricRegistry.name(HttpClient.class,
name,
exception.getClass().getSimpleName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package com.codahale.metrics.httpclient5;

import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry;
import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.SchemePortResolver;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.apache.hc.client5.http.nio.AsyncClientConnectionManager;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.core5.http.URIScheme;
import org.apache.hc.core5.http.config.Lookup;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
import org.apache.hc.core5.pool.PoolReusePolicy;
import org.apache.hc.core5.util.TimeValue;

import static com.codahale.metrics.MetricRegistry.name;
import static java.util.Objects.requireNonNull;

/**
* A {@link HttpClientConnectionManager} which monitors the number of open connections.
*/
public class InstrumentedAsyncClientConnectionManager extends PoolingAsyncClientConnectionManager {
private static final String METRICS_PREFIX = AsyncClientConnectionManager.class.getName();

protected static Registry<TlsStrategy> getDefaultTlsStrategy() {
return RegistryBuilder.<TlsStrategy>create()
.register(URIScheme.HTTPS.id, DefaultClientTlsStrategy.getDefault())
.build();
}

private final MetricRegistry metricsRegistry;
private final String name;

InstrumentedAsyncClientConnectionManager(final MetricRegistry metricRegistry,
final String name,
final Lookup<TlsStrategy> tlsStrategyLookup,
final PoolConcurrencyPolicy poolConcurrencyPolicy,
final PoolReusePolicy poolReusePolicy,
final TimeValue timeToLive,
final SchemePortResolver schemePortResolver,
final DnsResolver dnsResolver) {

super(tlsStrategyLookup, poolConcurrencyPolicy, poolReusePolicy, timeToLive, schemePortResolver, dnsResolver);
this.metricsRegistry = requireNonNull(metricRegistry, "metricRegistry");
this.name = name;

metricRegistry.register(name(METRICS_PREFIX, name, "available-connections"),
(Gauge<Integer>) () -> {
// this acquires a lock on the connection pool; remove if contention sucks
return getTotalStats().getAvailable();
});
metricRegistry.register(name(METRICS_PREFIX, name, "leased-connections"),
(Gauge<Integer>) () -> {
// this acquires a lock on the connection pool; remove if contention sucks
return getTotalStats().getLeased();
});
metricRegistry.register(name(METRICS_PREFIX, name, "max-connections"),
(Gauge<Integer>) () -> {
// this acquires a lock on the connection pool; remove if contention sucks
return getTotalStats().getMax();
});
metricRegistry.register(name(METRICS_PREFIX, name, "pending-connections"),
(Gauge<Integer>) () -> {
// this acquires a lock on the connection pool; remove if contention sucks
return getTotalStats().getPending();
});
}

/**
* {@inheritDoc}
*/
@Override
public void close() {
close(CloseMode.GRACEFUL);
}

/**
* {@inheritDoc}
*/
@Override
public void close(CloseMode closeMode) {
super.close(closeMode);
metricsRegistry.remove(name(METRICS_PREFIX, name, "available-connections"));
metricsRegistry.remove(name(METRICS_PREFIX, name, "leased-connections"));
metricsRegistry.remove(name(METRICS_PREFIX, name, "max-connections"));
metricsRegistry.remove(name(METRICS_PREFIX, name, "pending-connections"));
}

public static Builder builder(MetricRegistry metricsRegistry) {
return new Builder().metricsRegistry(metricsRegistry);
}

public static class Builder {
private MetricRegistry metricsRegistry;
private String name;
private Lookup<TlsStrategy> tlsStrategyLookup = getDefaultTlsStrategy();
private SchemePortResolver schemePortResolver;
private DnsResolver dnsResolver;
private PoolConcurrencyPolicy poolConcurrencyPolicy;
private PoolReusePolicy poolReusePolicy;
private TimeValue timeToLive = TimeValue.NEG_ONE_MILLISECOND;

Builder() {
}

public Builder metricsRegistry(MetricRegistry metricRegistry) {
this.metricsRegistry = requireNonNull(metricRegistry, "metricRegistry");
return this;
}

public Builder name(final String name) {
this.name = name;
return this;
}

public Builder schemePortResolver(SchemePortResolver schemePortResolver) {
this.schemePortResolver = schemePortResolver;
return this;
}

public Builder dnsResolver(DnsResolver dnsResolver) {
this.dnsResolver = dnsResolver;
return this;
}

public Builder timeToLive(TimeValue timeToLive) {
this.timeToLive = timeToLive;
return this;
}

public Builder tlsStrategyLookup(Lookup<TlsStrategy> tlsStrategyLookup) {
this.tlsStrategyLookup = tlsStrategyLookup;
return this;
}

public Builder poolConcurrencyPolicy(PoolConcurrencyPolicy poolConcurrencyPolicy) {
this.poolConcurrencyPolicy = poolConcurrencyPolicy;
return this;
}

public Builder poolReusePolicy(PoolReusePolicy poolReusePolicy) {
this.poolReusePolicy = poolReusePolicy;
return this;
}

public InstrumentedAsyncClientConnectionManager build() {
return new InstrumentedAsyncClientConnectionManager(
metricsRegistry,
name,
tlsStrategyLookup,
poolConcurrencyPolicy,
poolReusePolicy,
timeToLive,
schemePortResolver,
dnsResolver);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.codahale.metrics.httpclient5;

import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import org.apache.hc.client5.http.async.AsyncExecCallback;
import org.apache.hc.client5.http.async.AsyncExecChain;
import org.apache.hc.client5.http.async.AsyncExecChainHandler;
import org.apache.hc.core5.http.EntityDetails;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.nio.AsyncDataConsumer;
import org.apache.hc.core5.http.nio.AsyncEntityProducer;

import java.io.IOException;

import static java.util.Objects.requireNonNull;

class InstrumentedAsyncExecChainHandler implements AsyncExecChainHandler {
private final MetricRegistry registry;
private final HttpClientMetricNameStrategy metricNameStrategy;
private final String name;

public InstrumentedAsyncExecChainHandler(MetricRegistry registry, HttpClientMetricNameStrategy metricNameStrategy) {
this(registry, metricNameStrategy, null);
}

public InstrumentedAsyncExecChainHandler(MetricRegistry registry,
HttpClientMetricNameStrategy metricNameStrategy,
String name) {
this.registry = requireNonNull(registry, "registry");
this.metricNameStrategy = requireNonNull(metricNameStrategy, "metricNameStrategy");
this.name = name;
}

@Override
public void execute(HttpRequest request,
AsyncEntityProducer entityProducer,
AsyncExecChain.Scope scope,
AsyncExecChain chain,
AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
final InstrumentedAsyncExecCallback instrumentedAsyncExecCallback =
new InstrumentedAsyncExecCallback(registry, metricNameStrategy, name, asyncExecCallback, request);
chain.proceed(request, entityProducer, scope, instrumentedAsyncExecCallback);

}

final static class InstrumentedAsyncExecCallback implements AsyncExecCallback {
private final MetricRegistry registry;
private final HttpClientMetricNameStrategy metricNameStrategy;
private final String name;
private final AsyncExecCallback delegate;
private final Timer.Context timerContext;

public InstrumentedAsyncExecCallback(MetricRegistry registry,
HttpClientMetricNameStrategy metricNameStrategy,
String name,
AsyncExecCallback delegate,
HttpRequest request) {
this.registry = registry;
this.metricNameStrategy = metricNameStrategy;
this.name = name;
this.delegate = delegate;
this.timerContext = timer(request).time();
}

@Override
public AsyncDataConsumer handleResponse(HttpResponse response, EntityDetails entityDetails) throws HttpException, IOException {
return delegate.handleResponse(response, entityDetails);
}

@Override
public void handleInformationResponse(HttpResponse response) throws HttpException, IOException {
delegate.handleInformationResponse(response);
}

@Override
public void completed() {
delegate.completed();
timerContext.stop();
}

@Override
public void failed(Exception cause) {
delegate.failed(cause);
meter(cause).mark();
timerContext.stop();
}

private Timer timer(HttpRequest request) {
return registry.timer(metricNameStrategy.getNameFor(name, request));
}

private Meter meter(Exception e) {
return registry.meter(metricNameStrategy.getNameFor(name, e));
}
}
}
Loading