Browse Source
This commit adds metrics support for `ConnectionPool` beans. See gh-19988 Co-authored-by: Mark Paluch <mpaluch@pivotal.io> Co-authored-by: Tadaya Tsuyukubo <tadaya@ttddyy.net>pull/20318/head
9 changed files with 384 additions and 0 deletions
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
/* |
||||
* 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)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
/* |
||||
* 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; |
||||
@ -0,0 +1,112 @@
@@ -0,0 +1,112 @@
|
||||
/* |
||||
* 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()); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,80 @@
@@ -0,0 +1,80 @@
|
||||
/* |
||||
* 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; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
/* |
||||
* 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; |
||||
@ -0,0 +1,91 @@
@@ -0,0 +1,91 @@
|
||||
/* |
||||
* 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); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue