Commit 81c18214 authored by bono007's avatar bono007 Committed by Andy Wilkinson

Auto-configure Mongo metrics

See gh-23990
parent ef986b13
/*
* 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;
import com.mongodb.MongoClientSettings;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.mongodb.DefaultMongoMetricsCommandTagsProvider;
import io.micrometer.core.instrument.binder.mongodb.DefaultMongoMetricsConnectionPoolTagsProvider;
import io.micrometer.core.instrument.binder.mongodb.MongoMetricsCommandListener;
import io.micrometer.core.instrument.binder.mongodb.MongoMetricsCommandTagsProvider;
import io.micrometer.core.instrument.binder.mongodb.MongoMetricsConnectionPoolListener;
import io.micrometer.core.instrument.binder.mongodb.MongoMetricsConnectionPoolTagsProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
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.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoClientSettingsBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Mongo metrics.
*
* @author Chris Bono
* @author Jonatan Ivanov
* @since 2.5.0
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(MongoAutoConfiguration.class)
@AutoConfigureAfter({ MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class })
@ConditionalOnClass(MongoClientSettings.class)
@ConditionalOnBean(MeterRegistry.class)
public class MongoMetricsAutoConfiguration {
@ConditionalOnClass(MongoMetricsCommandListener.class)
@ConditionalOnProperty(name = "management.metrics.mongo.command.enabled", havingValue = "true",
matchIfMissing = true)
static class MongoCommandMetricsConfiguration {
@Bean
@ConditionalOnMissingBean
MongoMetricsCommandListener mongoMetricsCommandListener(MeterRegistry meterRegistry,
MongoMetricsCommandTagsProvider mongoMetricsCommandTagsProvider) {
return new MongoMetricsCommandListener(meterRegistry, mongoMetricsCommandTagsProvider);
}
@Bean
@ConditionalOnMissingBean
MongoMetricsCommandTagsProvider mongoMetricsCommandTagsProvider() {
return new DefaultMongoMetricsCommandTagsProvider();
}
@Bean
MongoClientSettingsBuilderCustomizer mongoMetricsCommandListenerClientSettingsBuilderCustomizer(
MongoMetricsCommandListener mongoMetricsCommandListener) {
return (clientSettingsBuilder) -> clientSettingsBuilder.addCommandListener(mongoMetricsCommandListener);
}
}
@ConditionalOnClass(MongoMetricsConnectionPoolListener.class)
@ConditionalOnProperty(name = "management.metrics.mongo.connectionpool.enabled", havingValue = "true",
matchIfMissing = true)
static class MongoConnectionPoolMetricsConfiguration {
@Bean
@ConditionalOnMissingBean
MongoMetricsConnectionPoolListener mongoMetricsConnectionPoolListener(MeterRegistry meterRegistry,
MongoMetricsConnectionPoolTagsProvider mongoMetricsConnectionPoolTagsProvider) {
return new MongoMetricsConnectionPoolListener(meterRegistry, mongoMetricsConnectionPoolTagsProvider);
}
@Bean
@ConditionalOnMissingBean
MongoMetricsConnectionPoolTagsProvider mongoMetricsConnectionPoolTagsProvider() {
return new DefaultMongoMetricsConnectionPoolTagsProvider();
}
@Bean
MongoClientSettingsBuilderCustomizer mongoMetricsConnectionPoolListenerClientSettingsBuilderCustomizer(
MongoMetricsConnectionPoolListener mongoMetricsConnectionPoolListener) {
return (clientSettingsBuilder) -> clientSettingsBuilder
.applyToConnectionPoolSettings((connectionPoolSettingsBuilder) -> connectionPoolSettingsBuilder
.addConnectionPoolListener(mongoMetricsConnectionPoolListener));
}
}
}
......@@ -521,6 +521,16 @@
"level": "error"
}
},
{
"name": "management.metrics.mongo.command.enabled",
"description": "Whether to enable Mongo client command metrics.",
"defaultValue": true
},
{
"name": "management.metrics.mongo.connectionpool.enabled",
"description": "Whether to enable Mongo connection pool metrics.",
"defaultValue": true
},
{
"name": "management.metrics.web.client.request.autotime.enabled",
"description": "Whether to automatically time web client requests.",
......
......@@ -46,6 +46,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.Log4J2MetricsAutoConfigur
org.springframework.boot.actuate.autoconfigure.metrics.LogbackMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.MetricsEndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.MongoMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.SystemMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.amqp.RabbitMetricsAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.metrics.cache.CacheMetricsAutoConfiguration,\
......
/*
* Copyright 2012-2021 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;
import java.util.List;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.connection.ConnectionPoolSettings;
import com.mongodb.event.ConnectionPoolListener;
import io.micrometer.core.instrument.binder.mongodb.DefaultMongoMetricsCommandTagsProvider;
import io.micrometer.core.instrument.binder.mongodb.DefaultMongoMetricsConnectionPoolTagsProvider;
import io.micrometer.core.instrument.binder.mongodb.MongoMetricsCommandListener;
import io.micrometer.core.instrument.binder.mongodb.MongoMetricsCommandTagsProvider;
import io.micrometer.core.instrument.binder.mongodb.MongoMetricsConnectionPoolListener;
import io.micrometer.core.instrument.binder.mongodb.MongoMetricsConnectionPoolTagsProvider;
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.mongo.MongoAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link MongoMetricsAutoConfiguration}.
*
* @author Chris Bono
*/
class MongoMetricsAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MongoMetricsAutoConfiguration.class));
@Test
void whenThereIsAMeterRegistryThenMetricsCommandListenerIsAdded() {
this.contextRunner.with(MetricsRun.simple())
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class)).run((context) -> {
assertThat(context).hasSingleBean(MongoMetricsCommandListener.class);
assertThat(getActualMongoClientSettingsUsedToConstructClient(context)).isNotNull()
.extracting(MongoClientSettings::getCommandListeners).asList()
.containsExactly(context.getBean(MongoMetricsCommandListener.class));
assertThat(getMongoMetricsCommandTagsProviderUsedToConstructListener(context))
.isInstanceOf(DefaultMongoMetricsCommandTagsProvider.class);
});
}
@Test
void whenThereIsAMeterRegistryThenMetricsConnectionPoolListenerIsAdded() {
this.contextRunner.with(MetricsRun.simple())
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class)).run((context) -> {
assertThat(context).hasSingleBean(MongoMetricsConnectionPoolListener.class);
assertThat(getConnectionPoolListenersFromClient(context))
.containsExactly(context.getBean(MongoMetricsConnectionPoolListener.class));
assertThat(getMongoMetricsConnectionPoolTagsProviderUsedToConstructListener(context))
.isInstanceOf(DefaultMongoMetricsConnectionPoolTagsProvider.class);
});
}
@Test
void whenThereIsNoMeterRegistryThenNoMetricsCommandListenerIsAdded() {
this.contextRunner.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class))
.run((context) -> assertThatMetricsCommandListenerNotAdded());
}
@Test
void whenThereIsNoMeterRegistryThenNoMetricsConnectionPoolListenerIsAdded() {
this.contextRunner.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class))
.run((context) -> assertThatMetricsConnectionPoolListenerNotAdded());
}
@Test
void whenThereIsACustomMetricsCommandTagsProviderItIsUsed() {
final MongoMetricsCommandTagsProvider customTagsProvider = mock(MongoMetricsCommandTagsProvider.class);
this.contextRunner.with(MetricsRun.simple())
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class))
.withBean("customMongoMetricsCommandTagsProvider", MongoMetricsCommandTagsProvider.class,
() -> customTagsProvider)
.run((context) -> assertThat(getMongoMetricsCommandTagsProviderUsedToConstructListener(context))
.isSameAs(customTagsProvider));
}
@Test
void whenThereIsACustomMetricsConnectionPoolTagsProviderItIsUsed() {
final MongoMetricsConnectionPoolTagsProvider customTagsProvider = mock(
MongoMetricsConnectionPoolTagsProvider.class);
this.contextRunner.with(MetricsRun.simple())
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class))
.withBean("customMongoMetricsConnectionPoolTagsProvider", MongoMetricsConnectionPoolTagsProvider.class,
() -> customTagsProvider)
.run((context) -> assertThat(getMongoMetricsConnectionPoolTagsProviderUsedToConstructListener(context))
.isSameAs(customTagsProvider));
}
@Test
void whenThereIsNoMongoClientSettingsOnClasspathThenNoMetricsCommandListenerIsAdded() {
this.contextRunner.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class))
.withClassLoader(new FilteredClassLoader(MongoClientSettings.class))
.run((context) -> assertThatMetricsCommandListenerNotAdded());
}
@Test
void whenThereIsNoMongoClientSettingsOnClasspathThenNoMetricsConnectionPoolListenerIsAdded() {
this.contextRunner.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class))
.withClassLoader(new FilteredClassLoader(MongoClientSettings.class))
.run((context) -> assertThatMetricsConnectionPoolListenerNotAdded());
}
@Test
void whenThereIsNoMongoMetricsCommandListenerOnClasspathThenNoMetricsCommandListenerIsAdded() {
this.contextRunner.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class))
.withClassLoader(new FilteredClassLoader(MongoMetricsCommandListener.class))
.run((context) -> assertThatMetricsCommandListenerNotAdded());
}
@Test
void whenThereIsNoMongoMetricsConnectionPoolListenerOnClasspathThenNoMetricsConnectionPoolListenerIsAdded() {
this.contextRunner.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class))
.withClassLoader(new FilteredClassLoader(MongoMetricsConnectionPoolListener.class))
.run((context) -> assertThatMetricsConnectionPoolListenerNotAdded());
}
@Test
void whenMetricsCommandListenerEnabledPropertyFalseThenNoMetricsCommandListenerIsAdded() {
this.contextRunner.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class))
.withPropertyValues("management.metrics.mongo.command.enabled:false")
.run((context) -> assertThatMetricsCommandListenerNotAdded());
}
@Test
void whenMetricsConnectionPoolListenerEnabledPropertyFalseThenNoMetricsConnectionPoolListenerIsAdded() {
this.contextRunner.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class))
.withPropertyValues("management.metrics.mongo.connectionpool.enabled:false")
.run((context) -> assertThatMetricsConnectionPoolListenerNotAdded());
}
private ContextConsumer<AssertableApplicationContext> assertThatMetricsCommandListenerNotAdded() {
return (context) -> {
assertThat(context).doesNotHaveBean(MongoMetricsCommandListener.class);
assertThat(getActualMongoClientSettingsUsedToConstructClient(context)).isNotNull()
.extracting(MongoClientSettings::getCommandListeners).asList().isEmpty();
};
}
private ContextConsumer<AssertableApplicationContext> assertThatMetricsConnectionPoolListenerNotAdded() {
return (context) -> {
assertThat(context).doesNotHaveBean(MongoMetricsConnectionPoolListener.class);
assertThat(getConnectionPoolListenersFromClient(context)).isEmpty();
};
}
private MongoClientSettings getActualMongoClientSettingsUsedToConstructClient(
final AssertableApplicationContext context) {
final MongoClient mongoClient = context.getBean(MongoClient.class);
return (MongoClientSettings) ReflectionTestUtils.getField(mongoClient, "settings");
}
private List<ConnectionPoolListener> getConnectionPoolListenersFromClient(
final AssertableApplicationContext context) {
MongoClientSettings mongoClientSettings = getActualMongoClientSettingsUsedToConstructClient(context);
ConnectionPoolSettings connectionPoolSettings = mongoClientSettings.getConnectionPoolSettings();
@SuppressWarnings("unchecked")
List<ConnectionPoolListener> listeners = (List<ConnectionPoolListener>) ReflectionTestUtils
.getField(connectionPoolSettings, "connectionPoolListeners");
return listeners;
}
private MongoMetricsCommandTagsProvider getMongoMetricsCommandTagsProviderUsedToConstructListener(
final AssertableApplicationContext context) {
MongoMetricsCommandListener listener = context.getBean(MongoMetricsCommandListener.class);
return (MongoMetricsCommandTagsProvider) ReflectionTestUtils.getField(listener, "tagsProvider");
}
private MongoMetricsConnectionPoolTagsProvider getMongoMetricsConnectionPoolTagsProviderUsedToConstructListener(
final AssertableApplicationContext context) {
MongoMetricsConnectionPoolListener listener = context.getBean(MongoMetricsConnectionPoolListener.class);
return (MongoMetricsConnectionPoolTagsProvider) ReflectionTestUtils.getField(listener, "tagsProvider");
}
}
......@@ -2135,6 +2135,7 @@ Spring Boot registers the following core metrics when applicable:
* Kafka consumer, producer, and streams metrics
* Log4j2 metrics: record the number of events logged to Log4j2 at each level
* Logback metrics: record the number of events logged to Logback at each level
* MongoDB metrics for all commands issued from the client and for client connection pool information
* Uptime metrics: report a gauge for uptime and a fixed gauge representing the application's absolute start time
* Tomcat metrics (`server.tomcat.mbeanregistry.enabled` must be set to `true` for all Tomcat metrics to be registered)
* {spring-integration-docs}system-management.html#micrometer-integration[Spring Integration] metrics
......@@ -2396,6 +2397,94 @@ For more details refer to {spring-kafka-docs}#micrometer-native[Micrometer Nativ
[[production-ready-metrics-mongodb]]
==== MongoDB Metrics
===== Command Metrics
Auto-configuration will register a `MongoMetricsCommandListener` for the auto-configured MongoClient.
A timer metric with the name `mongodb.driver.commands` is created for each command issued to the underlying MongoDB driver.
Each metric is tagged with the following information by default:
|===
| Tag | Description
| `command`
| Name of the command issued
| `cluster.id`
| Identifier of the cluster the command was sent to
| `server.address`
| Address of the server the command was sent to
| `status`
| Outcome of the command - one of (`SUCCESS`, `FAILED`)
|===
You can replace the default metric tags by providing a `MongoMetricsCommandTagsProvider` bean, as shown in the following example:
[source,java,pending-extract=true,indent=0]
----
@Bean
MongoMetricsCommandTagsProvider mongoMetricsCommandTagsProvider() {
return new MyCustomMongoMetricsCommandTagsProvider();
}
----
If you want to disable the auto-configured command metrics, you can set the following property:
[source,yaml,indent=0,configprops,configblocks]
----
management:
metrics:
mongo:
command:
enabled: false
----
===== Connection Pool Metrics
Auto-configuration will register a `MongoMetricsConnectionPoolListener` for the auto-configured MongoClient.
The following gauge metrics are created for the connection pool:
* `mongodb.driver.pool.size` that reports the current size of the connection pool, including idle and and in-use members
* `mongodb.driver.pool.checkedout` that reports the count of connections that are currently in use
* `mongodb.driver.pool.waitqueuesize` that reports the current size of the wait queue for a connection from the pool
Each metric is tagged with the following information by default:
|===
| Tag | Description
| `cluster.id`
| Identifier of the cluster the connection pool corresponds to
| `server.address`
| Address of the server the connection pool corresponds to
|===
You can replace the default metric tags by providing a `MongoMetricsConnectionPoolTagsProvider` bean, as shown in the following example:
[source,java,pending-extract=true,indent=0]
----
@Bean
@ConditionalOnMissingBean
MongoMetricsConnectionPoolTagsProvider mongoMetricsConnectionPoolTagsProvider() {
return new DefaultMongoMetricsConnectionPoolTagsProvider();
}
----
If you want to disable the auto-configured connection pool metrics, you can set the following property:
[source,yaml,indent=0,configprops,configblocks]
----
management:
metrics:
mongo:
connectionpool:
enabled: false
----
[[production-ready-metrics-custom]]
=== Registering custom metrics
To register custom metrics, inject `MeterRegistry` into your component, as shown in the following example:
......
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