Add auto-configuration for DataSources

This commit automatically instruments all available data sources with
a configurable metric name. The instrumentation can be disabled in case
more control is needed.

Closes gh-10295
pull/10871/head
Stephane Nicoll 7 years ago
parent 5208bd069d
commit 9b6f0c83bf

@ -37,16 +37,19 @@ import org.springframework.boot.actuate.autoconfigure.metrics.export.jmx.JmxExpo
import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusExportConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleExportConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.statsd.StatsdExportConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.reactive.server.WebFluxMetricsConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.web.client.RestTemplateMetricsConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsConfiguration;
import org.springframework.boot.actuate.metrics.MetricsEndpoint;
import org.springframework.boot.actuate.metrics.integration.SpringIntegrationMetrics;
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.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -65,11 +68,13 @@ import org.springframework.integration.support.management.IntegrationManagementC
@EnableConfigurationProperties(MetricsProperties.class)
@Import({ MeterBindersConfiguration.class, WebMvcMetricsConfiguration.class,
WebFluxMetricsConfiguration.class, RestTemplateMetricsConfiguration.class,
DataSourcePoolMetricsConfiguration.class,
AtlasExportConfiguration.class, DatadogExportConfiguration.class,
GangliaExportConfiguration.class, GraphiteExportConfiguration.class,
InfluxExportConfiguration.class, JmxExportConfiguration.class,
PrometheusExportConfiguration.class, SimpleExportConfiguration.class,
StatsdExportConfiguration.class })
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MetricsAutoConfiguration {
@Bean

@ -0,0 +1,86 @@
/*
* Copyright 2012-2017 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
*
* http://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.jdbc;
import java.util.Collection;
import java.util.Map;
import javax.sql.DataSource;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.jdbc.DataSourcePoolMetrics;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
import org.springframework.context.annotation.Configuration;
/**
* Configure metrics for all available {@link DataSource datasources}.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
@Configuration
@ConditionalOnBean({ DataSource.class, DataSourcePoolMetadataProvider.class })
@ConditionalOnProperty(value = "spring.metrics.jdbc.instrument-datasource", matchIfMissing = true)
@EnableConfigurationProperties(JdbcMetricsProperties.class)
public class DataSourcePoolMetricsConfiguration {
private static final String DATASOURCE_SUFFIX = "dataSource";
private final MeterRegistry registry;
private final Collection<DataSourcePoolMetadataProvider> metadataProviders;
private final String metricName;
public DataSourcePoolMetricsConfiguration(MeterRegistry registry,
Collection<DataSourcePoolMetadataProvider> metadataProviders,
JdbcMetricsProperties jdbcMetricsProperties) {
this.registry = registry;
this.metadataProviders = metadataProviders;
this.metricName = jdbcMetricsProperties.getDatasourceMetricName();
}
@Autowired
public void bindDataSourcesToRegistry(Map<String, DataSource> dataSources) {
for (Map.Entry<String, DataSource> entry : dataSources.entrySet()) {
String beanName = entry.getKey();
DataSource dataSource = entry.getValue();
new DataSourcePoolMetrics(dataSource, this.metadataProviders, this.metricName,
Tags.zip("name", getDataSourceName(beanName))).bindTo(this.registry);
}
}
/**
* Get the name of a DataSource based on its {@code beanName}.
* @param beanName the name of the data source bean
* @return a name for the given data source
*/
private String getDataSourceName(String beanName) {
if (beanName.length() > DATASOURCE_SUFFIX.length()
&& beanName.toLowerCase().endsWith(DATASOURCE_SUFFIX.toLowerCase())) {
return beanName.substring(0, beanName.length() - DATASOURCE_SUFFIX.length());
}
return beanName;
}
}

@ -0,0 +1,43 @@
/*
* Copyright 2012-2017 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
*
* http://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.jdbc;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for JDBC-based metrics.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
@ConfigurationProperties("spring.metrics.jdbc")
public class JdbcMetricsProperties {
/**
* Name of the metric for data source usage.
*/
private String datasourceMetricName = "data.source";
public String getDatasourceMetricName() {
return this.datasourceMetricName;
}
public void setDatasourceMetricName(String datasourceMetricName) {
this.datasourceMetricName = datasourceMetricName;
}
}

@ -184,6 +184,12 @@
"description": "Enable the trace servlet filter.",
"defaultValue": true
},
{
"name": "spring.metrics.jdbc.instrument-datasource",
"type": "java.lang.Boolean",
"description": "Instrument all available data sources.",
"defaultValue": true
},
{
"name": "spring.git.properties",
"type": "java.lang.String",

@ -0,0 +1,141 @@
/*
* Copyright 2012-2017 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
*
* http://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.Collections;
import java.util.Map;
import java.util.Set;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.binder.MeterBinder;
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
import io.micrometer.core.instrument.binder.logging.LogbackMetrics;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.ExpectedCount.once;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
/**
* Integration tests for {@link MetricsAutoConfiguration}.
*
* @author Jon Schneider
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = MetricsAutoConfigurationIntegrationTests.MetricsApp.class)
@TestPropertySource(properties = "spring.metrics.use-global-registry=false")
public class MetricsAutoConfigurationIntegrationTests {
@Autowired
private ApplicationContext context;
@Autowired
private RestTemplate external;
@Autowired
private TestRestTemplate loopback;
@Autowired
private MeterRegistry registry;
@SuppressWarnings("unchecked")
@Test
public void restTemplateIsInstrumented() {
MockRestServiceServer server = MockRestServiceServer.bindTo(this.external)
.build();
server.expect(once(), requestTo("/api/external"))
.andExpect(method(HttpMethod.GET)).andRespond(withSuccess(
"{\"message\": \"hello\"}", MediaType.APPLICATION_JSON));
assertThat(this.external.getForObject("/api/external", Map.class))
.containsKey("message");
assertThat(this.registry.find("http.client.requests").value(Statistic.Count, 1.0)
.timer()).isPresent();
}
@Test
public void requestMappingIsInstrumented() {
this.loopback.getForObject("/api/people", Set.class);
assertThat(this.registry.find("http.server.requests").value(Statistic.Count, 1.0)
.timer()).isPresent();
}
@Test
public void automaticallyRegisteredBinders() {
assertThat(this.context.getBeansOfType(MeterBinder.class).values())
.hasAtLeastOneElementOfType(LogbackMetrics.class)
.hasAtLeastOneElementOfType(JvmMemoryMetrics.class);
}
@Configuration
@ImportAutoConfiguration({ MetricsAutoConfiguration.class,
JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class })
@Import(PersonController.class)
static class MetricsApp {
@Bean
public MeterRegistry registry() {
return new SimpleMeterRegistry();
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@RestController
static class PersonController {
@GetMapping("/api/people")
Set<String> personName() {
return Collections.singleton("Jon");
}
}
}

@ -16,124 +16,123 @@
package org.springframework.boot.actuate.autoconfigure.metrics;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.sql.DataSource;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.binder.MeterBinder;
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
import io.micrometer.core.instrument.binder.logging.LogbackMetrics;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.context.ApplicationContext;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.ExpectedCount.once;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
/**
* Tests for {@link MetricsAutoConfiguration}.
*
* @author Jon Schneider
* @author Stephane Nicoll
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = MetricsAutoConfigurationTests.MetricsApp.class)
@TestPropertySource(properties = "metrics.use-global-registry=false")
public class MetricsAutoConfigurationTests {
@Autowired
private ApplicationContext context;
@Autowired
private RestTemplate external;
@Autowired
private TestRestTemplate loopback;
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withUserConfiguration(RegistryConfiguration.class)
.withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class));
@Autowired
private MeterRegistry registry;
@Test
public void autoConfiguredDataSourceIsInstrumented() {
this.contextRunner
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.datasource.generate-unique-name=true",
"spring.metrics.use-global-registry=false")
.run((context) -> {
context.getBean(DataSource.class).getConnection().getMetaData();
MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("data.source.max.connections")
.tags("name", "dataSource").meter()).isPresent();
});
}
@SuppressWarnings("unchecked")
@Test
public void restTemplateIsInstrumented() {
MockRestServiceServer server = MockRestServiceServer.bindTo(this.external)
.build();
server.expect(once(), requestTo("/api/external"))
.andExpect(method(HttpMethod.GET)).andRespond(withSuccess(
"{\"message\": \"hello\"}", MediaType.APPLICATION_JSON));
assertThat(this.external.getForObject("/api/external", Map.class))
.containsKey("message");
assertThat(this.registry.find("http.client.requests").value(Statistic.Count, 1.0)
.timer()).isPresent();
public void autoConfiguredDataSourceWithCustomMetricName() {
this.contextRunner
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.datasource.generate-unique-name=true",
"spring.metrics.jdbc.datasource-metric-name=custom.name",
"spring.metrics.use-global-registry=false")
.run((context) -> {
context.getBean(DataSource.class).getConnection().getMetaData();
MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("custom.name.max.connections")
.tags("name", "dataSource").meter()).isPresent();
});
}
@Test
public void requestMappingIsInstrumented() {
this.loopback.getForObject("/api/people", Set.class);
assertThat(this.registry.find("http.server.requests").value(Statistic.Count, 1.0)
.timer()).isPresent();
public void dataSourceInstrumentationCanBeDisabled() {
this.contextRunner
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.datasource.generate-unique-name=true",
"spring.metrics.jdbc.instrument-datasource=false",
"spring.metrics.use-global-registry=false")
.run((context) -> {
context.getBean(DataSource.class).getConnection().getMetaData();
MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("custom.name.max.connections")
.tags("name", "dataSource").meter()).isNotPresent();
});
}
@Test
public void automaticallyRegisteredBinders() {
assertThat(this.context.getBeansOfType(MeterBinder.class).values())
.hasAtLeastOneElementOfType(LogbackMetrics.class)
.hasAtLeastOneElementOfType(JvmMemoryMetrics.class);
public void allDataSourcesCanBeInstrumented() {
this.contextRunner
.withUserConfiguration(TwoDataSourcesConfiguration.class)
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("metrics.use-global-registry=false")
.run((context) -> {
context.getBean("firstDataSource", DataSource.class)
.getConnection().getMetaData();
context.getBean("secondOne", DataSource.class)
.getConnection().getMetaData();
MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("data.source.max.connections")
.tags("name", "first").meter()).isPresent();
assertThat(registry.find("data.source.max.connections")
.tags("name", "secondOne").meter()).isPresent();
});
}
@Configuration
@ImportAutoConfiguration({ MetricsAutoConfiguration.class,
JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class })
@Import(PersonController.class)
static class MetricsApp {
static class RegistryConfiguration {
@Bean
public MeterRegistry registry() {
public MeterRegistry meterRegistry() {
return new SimpleMeterRegistry();
}
}
@Configuration
static class TwoDataSourcesConfiguration {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
public DataSource firstDataSource() {
return createDataSource();
}
}
@RestController
static class PersonController {
@Bean
public DataSource secondOne() {
return createDataSource();
}
@GetMapping("/api/people")
Set<String> personName() {
return Collections.singleton("Jon");
private DataSource createDataSource() {
String url = "jdbc:hsqldb:mem:test-" + UUID.randomUUID().toString();
return DataSourceBuilder.create().url(url).build();
}
}

@ -1302,6 +1302,8 @@ content into your application; rather pick only the properties that you need.
spring.metrics.ganglia.enabled=true # Whether not exporting of metrics to Ganglia is enabled.
spring.metrics.graphite.enabled=true # Whether not exporting of metrics to Graphite is enabled.
spring.metrics.influx.enabled=true # Whether not exporting of metrics to InfluxDB is enabled.
spring.metrics.jdbc.datasource-metric-name=data.source # Name of the metric for data source usage.
spring.metrics.jdbc.instrument-datasource=true # Instrument all available data sources.
spring.metrics.jmx.enabled=true # Whether not exporting of metrics to JMX is enabled.
spring.metrics.prometheus.enabled=true # Whether not exporting of metrics to Prometheus is enabled.
spring.metrics.simple.enabled=true # Whether not exporting of metrics to a simple in-memory store is enabled.

@ -960,6 +960,16 @@ the following:
[[production-ready-metrics-jdbc]]
=== DataSource metrics
Auto-configuration will enable the instrumentation of all available `DataSources` with a
metric named `data.source`. The name can be customized using the
`spring.metrics.jdbc.datasource-metric-name`.
Metrics will be tagged by the name of the `DataSource` computed based on the bean name.
[[production-ready-metrics-integration]]
=== Spring Integration metrics
Auto-configuration will enable binding of a number of Spring Integration-related

Loading…
Cancel
Save