Skip to content

Commit

Permalink
Added metrics for OkHttp connection pool #1875
Browse files Browse the repository at this point in the history
  • Loading branch information
benhubert authored and Jon Schneider committed May 8, 2020
1 parent b8e36b9 commit a1b04f5
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 1 deletion.
1 change: 1 addition & 0 deletions dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def VERSIONS = [
'org.junit.vintage:junit-vintage-engine:latest.release',
'org.latencyutils:LatencyUtils:latest.release',
'org.mockito:mockito-core:latest.release',
'org.mockito:mockito-inline:latest.release',
'org.mongodb:mongo-java-driver:latest.release',
'org.slf4j:slf4j-api:1.7.+',
'org.springframework:spring-context:latest.release',
Expand Down
2 changes: 1 addition & 1 deletion micrometer-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ dependencies {
// Eclipse still needs this (as of 4.7.1a)
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

testImplementation 'org.mockito:mockito-core'
testImplementation 'org.mockito:mockito-inline'

testImplementation 'org.hsqldb:hsqldb'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package io.micrometer.core.instrument.binder.okhttp3;

import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.binder.MeterBinder;
import okhttp3.ConnectionPool;

import java.util.Collections;
import java.util.Optional;

/**
* MeterBinder for collecting metrics of a given OkHttp {@link ConnectionPool}.
*
* Example usage:
* <pre>
* return new ConnectionPool(connectionPoolSize, connectionPoolKeepAliveMs, TimeUnit.MILLISECONDS);
* new OkHttpConnectionPoolMetrics(connectionPool, "okhttp.pool", Tags.of());
* </pre>
*/
public class OkHttpConnectionPoolMetrics implements MeterBinder {

private static final String DEFAULT_NAME = "okhttp.pool";

private final ConnectionPool connectionPool;
private final String name;
private final Iterable<Tag> tags;
private final Double maxIdleConnectionCount;

/**
* Creates a meter binder for the given connection pool.
* Metrics will be exposed using {@link #DEFAULT_NAME} as name.
*
* @param connectionPool The connection pool to monitor. Must not be null.
*/
public OkHttpConnectionPoolMetrics(ConnectionPool connectionPool) {
this(connectionPool, DEFAULT_NAME, Collections.emptyList(), null);
}

/**
* Creates a meter binder for the given connection pool.
*
* @param connectionPool The connection pool to monitor. Must not be null.
* @param name The desired name for the exposed metrics. Must not be null.
*/
public OkHttpConnectionPoolMetrics(ConnectionPool connectionPool, String name) {
this(connectionPool, name, Collections.emptyList(), null);
}

/**
* Creates a meter binder for the given connection pool.
* Metrics will be exposed using {@link #DEFAULT_NAME} as name.
*
* @param connectionPool The connection pool to monitor. Must not be null.
* @param tags A list of tags which will be passed for all meters. Must not be null.
*/
public OkHttpConnectionPoolMetrics(ConnectionPool connectionPool, Iterable<Tag> tags) {
this(connectionPool, DEFAULT_NAME, tags, null);
}

/**
* Creates a meter binder for the given connection pool.
*
* @param connectionPool The connection pool to monitor. Must not be null.
* @param name The desired name for the exposed metrics. Must not be null.
* @param tags A list of tags which will be passed for all meters. Must not be null.
*/
public OkHttpConnectionPoolMetrics(ConnectionPool connectionPool, String name, Iterable<Tag> tags) {
this(connectionPool, name, tags, null);
}

/**
* Creates a meter binder for the given connection pool.
*
* @param connectionPool The connection pool to monitor. Must not be null.
* @param name The desired name for the exposed metrics. Must not be null.
* @param tags A list of tags which will be passed for all meters. Must not be null.
* @param maxIdleConnections The maximum number of idle connections this pool will hold. This
* value is passed to the {@link ConnectionPool} constructor but is
* not exposed by this instance. Therefore this meter allows to pass
* it, to be able to monitor it.
*/
public OkHttpConnectionPoolMetrics(ConnectionPool connectionPool, String name, Iterable<Tag> tags, Integer maxIdleConnections) {
if (connectionPool == null) {
throw new IllegalArgumentException("Given ConnectionPool must not be null.");
}
if (name == null) {
throw new IllegalArgumentException("Given name must not be null.");
}
if (tags == null) {
throw new IllegalArgumentException("Given list of tags must not be null.");
}
this.connectionPool = connectionPool;
this.name = name;
this.tags = tags;
this.maxIdleConnectionCount = Optional.ofNullable(maxIdleConnections)
.map(Integer::doubleValue)
.orElse(null);
}

@Override
public void bindTo(MeterRegistry registry) {
Gauge.builder(name + ".connection.count", () -> Integer.valueOf(connectionPool.connectionCount()).doubleValue())
.tags(Tags.of(tags).and("state", "total"))
.register(registry);
Gauge.builder(name + ".connection.count", () -> Integer.valueOf(connectionPool.idleConnectionCount()).doubleValue())
.tags(Tags.of(tags).and("state", "idle"))
.register(registry);
if (this.maxIdleConnectionCount != null) {
Gauge.builder(name + ".connection.limit", () -> this.maxIdleConnectionCount)
.tags(Tags.of(tags).and("state", "idle"))
.register(registry);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package io.micrometer.core.instrument.binder.okhttp3;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MockClock;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.search.MeterNotFoundException;
import io.micrometer.core.instrument.simple.SimpleConfig;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import okhttp3.ConnectionPool;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class OkHttpConnectionPoolMetricsTest {

private MeterRegistry registry;
private ConnectionPool connectionPool;

@BeforeEach
void setup() {
registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock());
connectionPool = mock(ConnectionPool.class);
}

@Test
void creationWithNullConnectionPoolThrowsException() {
assertThrows(IllegalArgumentException.class, () -> {
new OkHttpConnectionPoolMetrics(null);
});
assertThrows(IllegalArgumentException.class, () -> {
new OkHttpConnectionPoolMetrics(null, "irrelevant");
});
assertThrows(IllegalArgumentException.class, () -> {
new OkHttpConnectionPoolMetrics(null, Tags.empty());
});
assertThrows(IllegalArgumentException.class, () -> {
new OkHttpConnectionPoolMetrics(null, "irrelevant", Tags.empty());
});
}

@Test
void creationWithNullNameThrowsException() {
assertThrows(IllegalArgumentException.class, () -> {
new OkHttpConnectionPoolMetrics(connectionPool, (String) null);
});
assertThrows(IllegalArgumentException.class, () -> {
new OkHttpConnectionPoolMetrics(connectionPool, null, Tags.empty());
});
}

@Test
void creationWithNullTagsThrowsException() {
assertThrows(IllegalArgumentException.class, () -> {
new OkHttpConnectionPoolMetrics(connectionPool, (Tags) null);
});
assertThrows(IllegalArgumentException.class, () -> {
new OkHttpConnectionPoolMetrics(connectionPool, "irrelevant.name", null);
});
}

@Test
void instanceUsesDefaultName() {
OkHttpConnectionPoolMetrics instance = new OkHttpConnectionPoolMetrics(connectionPool);
instance.bindTo(registry);
registry.get("okhttp.pool.connection.count"); // does not throw MeterNotFoundException
}

@Test
void instanceUsesDefaultNameAndGivenTag() {
OkHttpConnectionPoolMetrics instance = new OkHttpConnectionPoolMetrics(connectionPool, Tags.of("foo", "bar"));
instance.bindTo(registry);
registry.get("okhttp.pool.connection.count").tags("foo", "bar"); // does not throw MeterNotFoundException
}

@Test
void instanceUsesGivenName() {
OkHttpConnectionPoolMetrics instance = new OkHttpConnectionPoolMetrics(connectionPool, "some.meter");
instance.bindTo(registry);
registry.get("some.meter.connection.count"); // does not throw MeterNotFoundException
}

@Test
void instanceUsesGivenNameAndTag() {
OkHttpConnectionPoolMetrics instance = new OkHttpConnectionPoolMetrics(connectionPool, "another.meter", Tags.of("bar", "baz"));
instance.bindTo(registry);
registry.get("another.meter.connection.count").tags("bar", "baz"); // does not throw MeterNotFoundException
}

@Test
void total() {
OkHttpConnectionPoolMetrics instance = new OkHttpConnectionPoolMetrics(connectionPool, Tags.of("foo", "bar"));
instance.bindTo(registry);
when(connectionPool.connectionCount()).thenReturn(17);
assertThat(registry.get("okhttp.pool.connection.count")
.tags(Tags.of("foo", "bar").and("state", "total"))
.gauge().value()).isEqualTo(17.0);
}

@Test
void idle() {
OkHttpConnectionPoolMetrics instance = new OkHttpConnectionPoolMetrics(connectionPool, Tags.of("foo", "bar"));
instance.bindTo(registry);
when(connectionPool.idleConnectionCount()).thenReturn(13);
assertThat(registry.get("okhttp.pool.connection.count")
.tags(Tags.of("foo", "bar").and("state", "idle"))
.gauge().value()).isEqualTo(13.0);
}

@Test
void maxIfGiven() {
OkHttpConnectionPoolMetrics instance = new OkHttpConnectionPoolMetrics(connectionPool, "huge.pool", Tags.of("foo", "bar"), 1234);
instance.bindTo(registry);
assertThat(registry.get("huge.pool.connection.limit")
.tags(Tags.of("foo", "bar"))
.gauge().value()).isEqualTo(1234.0);
}

@Test
void maxIfNotGiven() {
OkHttpConnectionPoolMetrics instance = new OkHttpConnectionPoolMetrics(connectionPool, "huge.pool", Tags.of("foo", "bar"), null);
instance.bindTo(registry);
assertThrows(MeterNotFoundException.class, () -> {
registry.get("huge.pool.connection.limit")
.tags(Tags.of("foo", "bar"))
.gauge();
});
}

}

0 comments on commit a1b04f5

Please sign in to comment.