Commit 45e60587 authored by Stephane Nicoll's avatar Stephane Nicoll

Add R2BC connection pool metrics

This commit adds metrics support for `ConnectionPool` beans.

See gh-19988
Co-authored-by: 's avatarMark Paluch <mpaluch@pivotal.io>
Co-authored-by: 's avatarTadaya Tsuyukubo <tadaya@ttddyy.net>
parent bee7302f
......@@ -61,6 +61,7 @@ dependencies {
optional("io.micrometer:micrometer-registry-statsd")
optional("io.micrometer:micrometer-registry-wavefront")
optional("io.projectreactor.netty:reactor-netty")
optional("io.r2dbc:r2dbc-pool")
optional("io.r2dbc:r2dbc-spi")
optional("jakarta.jms:jakarta.jms-api")
optional("jakarta.servlet:jakarta.servlet-api")
......
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 org.springframework.boot.actuate.autoconfigure.metrics.r2dbc;
import java.util.Map;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.r2dbc.pool.ConnectionPool;
import io.r2dbc.spi.ConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.metrics.r2dbc.ConnectionPoolMetrics;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
import org.springframework.context.annotation.Configuration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for metrics on all available
* {@link ConnectionFactory R2DBC connection factories}.
*
* @author Tadaya Tsuyukubo
* @author Stephane Nicoll
* @since 2.3.0
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter({ MetricsAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class,
R2dbcAutoConfiguration.class })
@ConditionalOnClass({ ConnectionPool.class, MeterRegistry.class })
@ConditionalOnBean({ ConnectionPool.class, MeterRegistry.class })
public class ConnectionPoolMetricsAutoConfiguration {
@Autowired
public void bindConnectionPoolsToRegistry(Map<String, ConnectionPool> connectionPools, MeterRegistry registry) {
connectionPools.forEach((beanName,
connectionPool) -> new ConnectionPoolMetrics(connectionPool, beanName, Tags.empty()).bindTo(registry));
}
}
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.
*/
/**
* Auto-configuration for R2DBC metrics.
*/
package org.springframework.boot.actuate.autoconfigure.metrics.r2dbc;
......@@ -67,6 +67,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.export.wavefront.Wavefron
org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.jersey.JerseyServerMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa.HibernateMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.r2dbc.ConnectionPoolMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.web.jetty.JettyMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.web.reactive.WebFluxMetricsAutoConfiguration,\
......
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 org.springframework.boot.actuate.autoconfigure.metrics.r2dbc;
import java.util.Collections;
import java.util.UUID;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.r2dbc.h2.CloseableConnectionFactory;
import io.r2dbc.h2.H2ConnectionFactory;
import io.r2dbc.h2.H2ConnectionOption;
import io.r2dbc.pool.ConnectionPool;
import io.r2dbc.pool.ConnectionPoolConfiguration;
import io.r2dbc.spi.ConnectionFactory;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConnectionPoolMetricsAutoConfiguration}.
*
* @author Tadaya Tsuyukubo
* @author Stephane Nicoll
*/
class ConnectionPoolMetricsAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withPropertyValues("spring.r2dbc.generate-unique-name=true").with(MetricsRun.simple())
.withConfiguration(AutoConfigurations.of(ConnectionPoolMetricsAutoConfiguration.class))
.withUserConfiguration(BaseConfiguration.class);
@Test
void autoConfiguredDataSourceIsInstrumented() {
this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class)).run((context) -> {
MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("r2dbc.pool.acquired").gauges()).hasSize(1);
});
}
@Test
void connectionPoolInstrumentationCanBeDisabled() {
this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class))
.withPropertyValues("management.metrics.enable.r2dbc=false").run((context) -> {
MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("r2dbc.pool.acquired").gauge()).isNull();
});
}
@Test
void allConnectionPoolsCanBeInstrumented() {
this.contextRunner.withUserConfiguration(TwoConnectionPoolsConfiguration.class).run((context) -> {
MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("r2dbc.pool.acquired").gauges()).extracting(Meter::getId)
.extracting((id) -> id.getTag("name")).containsExactlyInAnyOrder("firstPool", "secondPool");
});
}
@Configuration(proxyBeanMethods = false)
static class BaseConfiguration {
@Bean
SimpleMeterRegistry registry() {
return new SimpleMeterRegistry();
}
}
@Configuration(proxyBeanMethods = false)
static class TwoConnectionPoolsConfiguration {
@Bean
CloseableConnectionFactory connectionFactory() {
return H2ConnectionFactory.inMemory("db-" + UUID.randomUUID(), "sa", "",
Collections.singletonMap(H2ConnectionOption.DB_CLOSE_DELAY, "-1"));
}
@Bean
ConnectionPool firstPool(ConnectionFactory connectionFactory) {
return new ConnectionPool(ConnectionPoolConfiguration.builder(connectionFactory).build());
}
@Bean
ConnectionPool secondPool(ConnectionFactory connectionFactory) {
return new ConnectionPool(ConnectionPoolConfiguration.builder(connectionFactory).build());
}
}
}
......@@ -24,6 +24,7 @@ dependencies {
optional("io.micrometer:micrometer-core")
optional("io.micrometer:micrometer-registry-prometheus")
optional("io.prometheus:simpleclient_pushgateway")
optional("io.r2dbc:r2dbc-pool")
optional("io.r2dbc:r2dbc-spi")
optional("io.reactivex:rxjava-reactive-streams")
optional("org.elasticsearch.client:elasticsearch-rest-client")
......
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 org.springframework.boot.actuate.metrics.r2dbc;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.Gauge.Builder;
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 io.r2dbc.pool.ConnectionPool;
import io.r2dbc.pool.PoolMetrics;
/**
* A {@link MeterBinder} for a {@link ConnectionPool}.
*
* @author Tadaya Tsuyukubo
* @author Stephane Nicoll
* @since 2.3.0
*/
public class ConnectionPoolMetrics implements MeterBinder {
private static final String CONNECTIONS = "connections";
private final ConnectionPool pool;
private final Iterable<Tag> tags;
public ConnectionPoolMetrics(ConnectionPool pool, String name, Iterable<Tag> tags) {
this.pool = pool;
this.tags = Tags.concat(tags, "name", name);
}
@Override
public void bindTo(MeterRegistry registry) {
this.pool.getMetrics().ifPresent((poolMetrics) -> {
bindConnectionPoolMetric(registry,
Gauge.builder(metricKey("acquired"), poolMetrics, PoolMetrics::acquiredSize)
.description("Size of successfully acquired connections which are in active use."));
bindConnectionPoolMetric(registry,
Gauge.builder(metricKey("allocated"), poolMetrics, PoolMetrics::allocatedSize)
.description("Size of allocated connections in the pool which are in active use or idle."));
bindConnectionPoolMetric(registry, Gauge.builder(metricKey("idle"), poolMetrics, PoolMetrics::idleSize)
.description("Size of idle connections in the pool."));
bindConnectionPoolMetric(registry,
Gauge.builder(metricKey("pending"), poolMetrics, PoolMetrics::pendingAcquireSize).description(
"Size of pending to acquire connections from the underlying connection factory."));
bindConnectionPoolMetric(registry,
Gauge.builder(metricKey("max.allocated"), poolMetrics, PoolMetrics::getMaxAllocatedSize)
.description("Maximum size of allocated connections that this pool allows."));
bindConnectionPoolMetric(registry,
Gauge.builder(metricKey("max.pending"), poolMetrics, PoolMetrics::getMaxPendingAcquireSize)
.description(
"Maximum size of pending state to acquire connections that this pool allows."));
});
}
private void bindConnectionPoolMetric(MeterRegistry registry, Builder<?> builder) {
builder.tags(this.tags).baseUnit(CONNECTIONS).register(registry);
}
private static String metricKey(String name) {
return "r2dbc.pool." + name;
}
}
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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.
*/
/**
* Actuator support for R2DBC metrics.
*/
package org.springframework.boot.actuate.metrics.r2dbc;
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 org.springframework.boot.actuate.metrics.r2dbc;
import java.util.Collections;
import java.util.UUID;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.r2dbc.h2.CloseableConnectionFactory;
import io.r2dbc.h2.H2ConnectionFactory;
import io.r2dbc.h2.H2ConnectionOption;
import io.r2dbc.pool.ConnectionPool;
import io.r2dbc.pool.ConnectionPoolConfiguration;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import reactor.test.StepVerifier;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConnectionPoolMetrics}.
*
* @author Tadaya Tsuyukubo
* @author Mark Paluch
* @author Stephane Nicoll
*/
class ConnectionPoolMetricsTests {
private static final Tag testTag = Tag.of("test", "yes");
private static final Tag regionTag = Tag.of("region", "eu-2");
private CloseableConnectionFactory connectionFactory;
@BeforeEach
void init() {
this.connectionFactory = H2ConnectionFactory.inMemory("db-" + UUID.randomUUID(), "sa", "",
Collections.singletonMap(H2ConnectionOption.DB_CLOSE_DELAY, "-1"));
}
@AfterEach
void close() {
if (this.connectionFactory != null) {
this.connectionFactory.close();
}
}
@Test
void connectionFactoryIsInstrumented() {
SimpleMeterRegistry registry = new SimpleMeterRegistry();
ConnectionPool connectionPool = new ConnectionPool(
ConnectionPoolConfiguration.builder(this.connectionFactory).initialSize(3).maxSize(7).build());
ConnectionPoolMetrics metrics = new ConnectionPoolMetrics(connectionPool, "test-pool",
Tags.of(testTag, regionTag));
metrics.bindTo(registry);
// acquire two connections
connectionPool.create().as(StepVerifier::create).expectNextCount(1).verifyComplete();
connectionPool.create().as(StepVerifier::create).expectNextCount(1).verifyComplete();
assertGauge(registry, "r2dbc.pool.acquired", 2);
assertGauge(registry, "r2dbc.pool.allocated", 3);
assertGauge(registry, "r2dbc.pool.idle", 1);
assertGauge(registry, "r2dbc.pool.pending", 0);
assertGauge(registry, "r2dbc.pool.max.allocated", 7);
assertGauge(registry, "r2dbc.pool.max.pending", Integer.MAX_VALUE);
}
private void assertGauge(SimpleMeterRegistry registry, String metric, int expectedValue) {
Gauge gauge = registry.get(metric).gauge();
assertThat(gauge.value()).isEqualTo(expectedValue);
assertThat(gauge.getId().getTags()).containsExactlyInAnyOrder(Tag.of("name", "test-pool"), testTag, regionTag);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment