diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index eecfd9a32d..496c39fa43 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -131,6 +131,21 @@ spring-rabbit true + + org.apache.tomcat + tomcat-jdbc + true + + + com.zaxxer + HikariCP + true + + + commons-dbcp + commons-dbcp + true + ch.qos.logback diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricDataSourceAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricDataSourceAutoConfiguration.java new file mode 100644 index 0000000000..7e6030261c --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/MetricDataSourceAutoConfiguration.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2014 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; + +import javax.sql.DataSource; + +import org.springframework.boot.actuate.endpoint.DataSourcePublicMetrics; +import org.springframework.boot.actuate.metrics.jdbc.DataSourceMetadataProvider; +import org.springframework.boot.actuate.metrics.jdbc.DataSourceMetadataProvidersConfiguration; +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.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +/** + * {@link EnableAutoConfiguration Auto-configuration} that provides + * metrics on dataSource usage. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +@ConditionalOnBean(DataSource.class) +@AutoConfigureAfter(DataSourceAutoConfiguration.class) +@Import(DataSourceMetadataProvidersConfiguration.class) +public class MetricDataSourceAutoConfiguration { + + @Bean + @ConditionalOnBean(DataSourceMetadataProvider.class) + @ConditionalOnMissingBean(DataSourcePublicMetrics.class) + DataSourcePublicMetrics dataSourcePublicMetrics() { + return new DataSourcePublicMetrics(); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/DataSourcePublicMetrics.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/DataSourcePublicMetrics.java new file mode 100644 index 0000000000..b860f9c475 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/DataSourcePublicMetrics.java @@ -0,0 +1,141 @@ +/* + * Copyright 2012-2014 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.endpoint; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.sql.DataSource; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.boot.actuate.metrics.jdbc.CompositeDataSourceMetadataProvider; +import org.springframework.boot.actuate.metrics.jdbc.DataSourceMetadata; +import org.springframework.boot.actuate.metrics.jdbc.DataSourceMetadataProvider; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Primary; + +/** + * A {@link PublicMetrics} implementation that provides data source usage + * statistics. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +public class DataSourcePublicMetrics implements PublicMetrics { + + private static final String DATASOURCE_SUFFIX = "dataSource"; + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private Collection dataSourceMetadataProviders; + + private final Map dataSourceMetadataByPrefix + = new HashMap(); + + @PostConstruct + public void initialize() { + Map dataSources = this.applicationContext.getBeansOfType(DataSource.class); + DataSource primaryDataSource = getPrimaryDataSource(); + + + DataSourceMetadataProvider provider = new CompositeDataSourceMetadataProvider(this.dataSourceMetadataProviders); + for (Map.Entry entry : dataSources.entrySet()) { + String prefix = createPrefix(entry.getKey(), entry.getValue(), entry.getValue().equals(primaryDataSource)); + DataSourceMetadata dataSourceMetadata = provider.getDataSourceMetadata(entry.getValue()); + if (dataSourceMetadata != null) { + dataSourceMetadataByPrefix.put(prefix, dataSourceMetadata); + } + } + } + + @Override + public Collection> metrics() { + Collection> result = new LinkedHashSet>(); + for (Map.Entry entry : dataSourceMetadataByPrefix.entrySet()) { + String prefix = entry.getKey(); + // Make sure the prefix ends with a dot + if (!prefix.endsWith(".")) { + prefix = prefix + "."; + } + DataSourceMetadata dataSourceMetadata = entry.getValue(); + Integer poolSize = dataSourceMetadata.getPoolSize(); + if (poolSize != null) { + result.add(new Metric(prefix + "active", poolSize)); + } + Float poolUsage = dataSourceMetadata.getPoolUsage(); + if (poolUsage != null) { + result.add(new Metric(prefix + "usage", poolUsage)); + } + } + return result; + } + + /** + * Create the prefix to use for the metrics to associate with the given {@link DataSource}. + * @param dataSourceName the name of the data source bean + * @param dataSource the data source to configure + * @param primary if this data source is the primary data source + * @return a prefix for the given data source + */ + protected String createPrefix(String dataSourceName, DataSource dataSource, boolean primary) { + StringBuilder sb = new StringBuilder("datasource."); + if (primary) { + sb.append("primary"); + } + else if (endWithDataSource(dataSourceName)) { // Strip the data source part out of the name + sb.append(dataSourceName.substring(0, dataSourceName.length() - DATASOURCE_SUFFIX.length())); + } + else { + sb.append(dataSourceName); + } + return sb.toString(); + } + + /** + * Specify if the given value ends with {@value #DATASOURCE_SUFFIX}. + */ + protected boolean endWithDataSource(String value) { + int suffixLength = DATASOURCE_SUFFIX.length(); + int valueLength = value.length(); + if (valueLength > suffixLength) { + String suffix = value.substring(valueLength - suffixLength, valueLength); + return suffix.equalsIgnoreCase(DATASOURCE_SUFFIX); + } + return false; + } + + /** + * Attempt to locate the primary {@link DataSource} (i.e. either the only data source + * available or the one amongst the candidates marked as {@link Primary}. Return + * {@code null} if there no primary data source could be found. + */ + private DataSource getPrimaryDataSource() { + try { + return applicationContext.getBean(DataSource.class); + } + catch (NoSuchBeanDefinitionException e) { + return null; + } + } +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/AbstractDataSourceMetadata.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/AbstractDataSourceMetadata.java new file mode 100644 index 0000000000..92b1587ebe --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/AbstractDataSourceMetadata.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2014 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.metrics.jdbc; + +import javax.sql.DataSource; + +/** + * A base {@link DataSourceMetadata} implementation. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +public abstract class AbstractDataSourceMetadata implements DataSourceMetadata { + + private final D dataSource; + + /** + * Create an instance with the data source to use. + */ + protected AbstractDataSourceMetadata(D dataSource) { + this.dataSource = dataSource; + } + + @Override + public Float getPoolUsage() { + Integer max = getMaxPoolSize(); + if (max == null) { + return null; + } + if (max < 0) { + return -1F; + } + Integer current = getPoolSize(); + if (current == null) { + return null; + } + if (current == 0) { + return 0F; + } + return (float) current / max; // something like that + } + + protected final D getDataSource() { + return dataSource; + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/CommonsDbcpDataSourceMetadata.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/CommonsDbcpDataSourceMetadata.java new file mode 100644 index 0000000000..49f67955e2 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/CommonsDbcpDataSourceMetadata.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-2014 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.metrics.jdbc; + +import org.apache.commons.dbcp.BasicDataSource; + +/** + * A {@link DataSourceMetadata} implementation for the commons dbcp + * data source. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +public class CommonsDbcpDataSourceMetadata extends AbstractDataSourceMetadata { + + public CommonsDbcpDataSourceMetadata(BasicDataSource dataSource) { + super(dataSource); + } + + @Override + public Integer getPoolSize() { + return getDataSource().getNumActive(); + } + + @Override + public Integer getMaxPoolSize() { + return getDataSource().getMaxActive(); + } + + @Override + public Integer getMinPoolSize() { + return getDataSource().getMinIdle(); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/CompositeDataSourceMetadataProvider.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/CompositeDataSourceMetadataProvider.java new file mode 100644 index 0000000000..cf75b800dc --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/CompositeDataSourceMetadataProvider.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2014 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.metrics.jdbc; + +import java.util.ArrayList; +import java.util.Collection; + +import javax.sql.DataSource; + +/** + * A {@link DataSourceMetadataProvider} implementation that returns the first + * {@link DataSourceMetadata} that is found by one of its delegate. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +public class CompositeDataSourceMetadataProvider implements DataSourceMetadataProvider { + + private final Collection providers; + + /** + * Create an instance with an initial collection of delegates to use. + */ + public CompositeDataSourceMetadataProvider(Collection providers) { + this.providers = providers; + } + + /** + * Create an instance with no delegate. + */ + public CompositeDataSourceMetadataProvider() { + this(new ArrayList()); + } + + @Override + public DataSourceMetadata getDataSourceMetadata(DataSource dataSource) { + for (DataSourceMetadataProvider provider : providers) { + DataSourceMetadata dataSourceMetadata = provider.getDataSourceMetadata(dataSource); + if (dataSourceMetadata != null) { + return dataSourceMetadata; + } + } + return null; + } + + /** + * Add a {@link DataSourceMetadataProvider} delegate to the list. + */ + public void addDataSourceMetadataProvider(DataSourceMetadataProvider provider) { + this.providers.add(provider); + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/DataSourceMetadata.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/DataSourceMetadata.java new file mode 100644 index 0000000000..b3e67ab301 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/DataSourceMetadata.java @@ -0,0 +1,67 @@ +/* + * Copyright 2012-2014 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.metrics.jdbc; + +import javax.sql.DataSource; + +/** + * Provide various metadata regarding a {@link DataSource} that + * are shared by most data source types but not accessible in a + * standard manner. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +public interface DataSourceMetadata { + + /** + * Return the usage of the pool as a double value between + * 0 and 1. + *
    + *
  • 1 means that the maximum number of connections + * have been allocated
  • + *
  • 0 means that no connection is currently active
  • + *
  • -1 means there is not limit to the number of connections + * that can be allocated
  • + *
+ * This may also return {@code null} if the data source does + * not provide the necessary information to compute the poll usage. + */ + Float getPoolUsage(); + + /** + * Return the current number of active connections that + * have been allocated from the data source or {@code null} + * if that information is not available. + */ + Integer getPoolSize(); + + /** + * Return the maximum number of active connections that can be + * allocated at the same time or {@code -1} if there is no + * limit. Can also return {@code null} if that information is + * not available. + */ + Integer getMaxPoolSize(); + + /** + * Return the minimum number of idle connections in the pool + * or {@code null} if that information is not available. + */ + Integer getMinPoolSize(); + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/DataSourceMetadataProvider.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/DataSourceMetadataProvider.java new file mode 100644 index 0000000000..6d78238ed7 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/DataSourceMetadataProvider.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2014 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.metrics.jdbc; + +import javax.sql.DataSource; + +/** + * Provide a {@link DataSourceMetadata} based on a {@link DataSource}. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +public interface DataSourceMetadataProvider { + + /** + * Return the {@link DataSourceMetadata} instance able to manage the + * specified {@link DataSource} or {@code null} if the given data + * source could not be handled. + */ + DataSourceMetadata getDataSourceMetadata(DataSource dataSource); + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/DataSourceMetadataProvidersConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/DataSourceMetadataProvidersConfiguration.java new file mode 100644 index 0000000000..156fc25a59 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/DataSourceMetadataProvidersConfiguration.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-2014 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.metrics.jdbc; + +import javax.sql.DataSource; + +import com.zaxxer.hikari.HikariDataSource; +import org.apache.commons.dbcp.BasicDataSource; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Register the {@link DataSourceMetadataProvider} instances for the supported + * data sources. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +@Configuration +public class DataSourceMetadataProvidersConfiguration { + + @Configuration + @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class) + static class TomcatDataSourceProviderConfiguration { + + @Bean + public DataSourceMetadataProvider tomcatDataSourceProvider() { + return new DataSourceMetadataProvider() { + @Override + public DataSourceMetadata getDataSourceMetadata(DataSource dataSource) { + if (dataSource instanceof org.apache.tomcat.jdbc.pool.DataSource) { + return new TomcatDataSourceMetadata((org.apache.tomcat.jdbc.pool.DataSource) dataSource); + } + return null; + } + }; + } + } + + @Configuration + @ConditionalOnClass(HikariDataSource.class) + static class HikariDataSourceProviderConfiguration { + + @Bean + public DataSourceMetadataProvider hikariDataSourceProvider() { + return new DataSourceMetadataProvider() { + @Override + public DataSourceMetadata getDataSourceMetadata(DataSource dataSource) { + if (dataSource instanceof HikariDataSource) { + return new HikariDataSourceMetadata((HikariDataSource) dataSource); + } + return null; + } + }; + } + } + + @Configuration + @ConditionalOnClass(BasicDataSource.class) + static class CommonsDbcpDataSourceProviderConfiguration { + + @Bean + public DataSourceMetadataProvider commonsDbcpDataSourceProvider() { + return new DataSourceMetadataProvider() { + @Override + public DataSourceMetadata getDataSourceMetadata(DataSource dataSource) { + if (dataSource instanceof BasicDataSource) { + return new CommonsDbcpDataSourceMetadata((BasicDataSource) dataSource); + } + return null; + } + }; + } + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/HikariDataSourceMetadata.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/HikariDataSourceMetadata.java new file mode 100644 index 0000000000..d2aaef2c80 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/HikariDataSourceMetadata.java @@ -0,0 +1,106 @@ +/* + * Copyright 2012-2014 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.metrics.jdbc; + +import com.zaxxer.hikari.HikariDataSource; +import com.zaxxer.hikari.pool.HikariPool; + +import org.springframework.beans.BeansException; +import org.springframework.beans.DirectFieldAccessor; + +/** + * A {@link DataSourceMetadata} implementation for the hikari + * data source. + * + * @author Stephane Nicoll + * @since 1.2.0 + */ +public class HikariDataSourceMetadata extends AbstractDataSourceMetadata { + + + private final HikariPoolProvider hikariPoolProvider; + + public HikariDataSourceMetadata(HikariDataSource dataSource) { + super(dataSource); + this.hikariPoolProvider = new HikariPoolProvider(dataSource); + } + + @Override + public Integer getPoolSize() { + HikariPool hikariPool = hikariPoolProvider.getHikariPool(); + if (hikariPool != null) { + return hikariPool.getActiveConnections(); + } + return null; + } + + public Integer getMaxPoolSize() { + return getDataSource().getMaximumPoolSize(); + } + + @Override + public Integer getMinPoolSize() { + return getDataSource().getMinimumIdle(); + } + + /** + * Provide the {@link HikariPool} instance managed internally by + * the {@link HikariDataSource} as there is no other way to retrieve + * that information except JMX access. + */ + private static class HikariPoolProvider { + private final HikariDataSource dataSource; + + private boolean poolAvailable; + + private HikariPoolProvider(HikariDataSource dataSource) { + this.dataSource = dataSource; + this.poolAvailable = isHikariPoolAvailable(); + } + + public HikariPool getHikariPool() { + if (!poolAvailable) { + return null; + } + + Object value = doGetValue(); + if (value instanceof HikariPool) { + return (HikariPool) value; + } + return null; + } + + private boolean isHikariPoolAvailable() { + try { + doGetValue(); + return true; + } + catch (BeansException e) { // No such field + return false; + } + catch (SecurityException e) { // Security manager prevents to read the value + return false; + } + } + + private Object doGetValue() { + DirectFieldAccessor accessor = new DirectFieldAccessor(this.dataSource); + return accessor.getPropertyValue("pool"); + } + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/TomcatDataSourceMetadata.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/TomcatDataSourceMetadata.java new file mode 100644 index 0000000000..7b3adcf109 --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/jdbc/TomcatDataSourceMetadata.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2014 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.metrics.jdbc; + +import org.apache.tomcat.jdbc.pool.ConnectionPool; +import org.apache.tomcat.jdbc.pool.DataSource; + +/** + * + * A {@link DataSourceMetadata} implementation for the tomcat + * data source. + * + * @author Stephane Nicoll + */ +public class TomcatDataSourceMetadata extends AbstractDataSourceMetadata { + + public TomcatDataSourceMetadata(DataSource dataSource) { + super(dataSource); + } + + @Override + public Integer getPoolSize() { + ConnectionPool pool = getDataSource().getPool(); + return (pool == null ? 0 : pool.getActive()); + } + + @Override + public Integer getMaxPoolSize() { + return getDataSource().getMaxActive(); + } + + @Override + public Integer getMinPoolSize() { + return getDataSource().getMinIdle(); + } + +} diff --git a/spring-boot-actuator/src/main/resources/META-INF/spring.factories b/spring-boot-actuator/src/main/resources/META-INF/spring.factories index 59e69a2deb..fc55c4cee0 100644 --- a/spring-boot-actuator/src/main/resources/META-INF/spring.factories +++ b/spring-boot-actuator/src/main/resources/META-INF/spring.factories @@ -8,6 +8,7 @@ org.springframework.boot.actuate.autoconfigure.HealthIndicatorAutoConfiguration, org.springframework.boot.actuate.autoconfigure.JolokiaAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.ManagementSecurityAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration,\ +org.springframework.boot.actuate.autoconfigure.MetricDataSourceAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.MetricRepositoryAutoConfiguration,\ org.springframework.boot.actuate.autoconfigure.TraceRepositoryAutoConfiguration,\ diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricDataSourceAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricDataSourceAutoConfigurationTests.java new file mode 100644 index 0000000000..a9dc552eea --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/MetricDataSourceAutoConfigurationTests.java @@ -0,0 +1,196 @@ +/* + * Copyright 2012-2014 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; + +import static org.junit.Assert.*; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import javax.sql.DataSource; + +import com.zaxxer.hikari.HikariDataSource; +import org.apache.commons.dbcp.BasicDataSource; +import org.junit.After; +import org.junit.Test; + +import org.springframework.boot.actuate.endpoint.DataSourcePublicMetrics; +import org.springframework.boot.actuate.endpoint.PublicMetrics; +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.ConnectionCallback; +import org.springframework.jdbc.core.JdbcTemplate; + +/** + * + * @author Stephane Nicoll + */ +public class MetricDataSourceAutoConfigurationTests { + + private AnnotationConfigApplicationContext context; + + @After + public void after() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void noDataSource() { + load(); + assertEquals(0, this.context.getBeansOfType(PublicMetrics.class).size()); + } + + @Test + public void autoDataSource() { + load(DataSourceAutoConfiguration.class); + PublicMetrics bean = this.context.getBean(PublicMetrics.class); + Collection> metrics = bean.metrics(); + assertMetrics(metrics, "datasource.primary.active", "datasource.primary.usage"); + } + + @Test + public void multipleDataSources() { + load(MultipleDataSourcesConfig.class); + PublicMetrics bean = this.context.getBean(PublicMetrics.class); + Collection> metrics = bean.metrics(); + assertMetrics(metrics, + "datasource.tomcat.active", "datasource.tomcat.usage", + "datasource.commonsDbcp.active", "datasource.commonsDbcp.usage"); + + // Hikari won't work unless a first connection has been retrieved + JdbcTemplate jdbcTemplate = new JdbcTemplate(context.getBean("hikariDS", DataSource.class)); + jdbcTemplate.execute(new ConnectionCallback() { + @Override + public Void doInConnection(Connection connection) throws SQLException, DataAccessException { + return null; + } + }); + + Collection> anotherMetrics = bean.metrics(); + assertMetrics(anotherMetrics, + "datasource.tomcat.active", "datasource.tomcat.usage", + "datasource.hikariDS.active", "datasource.hikariDS.usage", + "datasource.commonsDbcp.active", "datasource.commonsDbcp.usage"); + } + + @Test + public void multipleDataSourcesWithPrimary() { + load(MultipleDataSourcesWithPrimaryConfig.class); + PublicMetrics bean = this.context.getBean(PublicMetrics.class); + Collection> metrics = bean.metrics(); + assertMetrics(metrics, + "datasource.primary.active", "datasource.primary.usage", + "datasource.commonsDbcp.active", "datasource.commonsDbcp.usage"); + } + + @Test + public void customPrefix() { + load(MultipleDataSourcesWithPrimaryConfig.class, CustomDataSourcePublicMetrics.class); + PublicMetrics bean = this.context.getBean(PublicMetrics.class); + Collection> metrics = bean.metrics(); + assertMetrics(metrics, + "ds.first.active", "ds.first.usage", + "ds.second.active", "ds.second.usage"); + + } + + private void assertMetrics(Collection> metrics, String... keys) { + Map content = new HashMap(); + for (Metric metric : metrics) { + content.put(metric.getName(), metric.getValue()); + } + for (String key : keys) { + assertTrue("Key '" + key + "' was not found", content.containsKey(key)); + } + } + + private void load(Class... config) { + this.context = new AnnotationConfigApplicationContext(); + if (config.length > 0) { + this.context.register(config); + } + this.context.register(MetricDataSourceAutoConfiguration.class); + this.context.refresh(); + } + + + @Configuration + static class MultipleDataSourcesConfig { + + @Bean + public DataSource tomcatDataSource() { + return initializeBuilder().type(org.apache.tomcat.jdbc.pool.DataSource.class).build(); + } + + @Bean + public DataSource hikariDS() { + return initializeBuilder().type(HikariDataSource.class).build(); + } + + @Bean + public DataSource commonsDbcpDataSource() { + return initializeBuilder().type(BasicDataSource.class).build(); + } + } + + @Configuration + static class MultipleDataSourcesWithPrimaryConfig { + + @Bean + @Primary + public DataSource myDataSource() { + return initializeBuilder().type(org.apache.tomcat.jdbc.pool.DataSource.class).build(); + } + + @Bean + public DataSource commonsDbcpDataSource() { + return initializeBuilder().type(BasicDataSource.class).build(); + } + } + + @Configuration + static class CustomDataSourcePublicMetrics { + + @Bean + public DataSourcePublicMetrics myDataSourcePublicMetrics() { + return new DataSourcePublicMetrics() { + @Override + protected String createPrefix(String dataSourceName, DataSource dataSource, boolean primary) { + return (primary ? "ds.first." : "ds.second"); + } + }; + } + } + + private static DataSourceBuilder initializeBuilder() { + return DataSourceBuilder.create() + .driverClassName("org.hsqldb.jdbc.JDBCDriver") + .url("jdbc:hsqldb:mem:test") + .username("sa"); + } +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/jdbc/AbstractDataSourceMetadataTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/jdbc/AbstractDataSourceMetadataTests.java new file mode 100644 index 0000000000..43cd8f8a76 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/jdbc/AbstractDataSourceMetadataTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2012-2014 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.metrics.jdbc; + +import static org.junit.Assert.*; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.junit.Test; + +import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.ConnectionCallback; +import org.springframework.jdbc.core.JdbcTemplate; + +/** + * + * @author Stephane Nicoll + */ +public abstract class AbstractDataSourceMetadataTests { + + /** + * Return a data source metadata instance with a min size of 0 and max size of 2. + */ + protected abstract D getDataSourceMetadata(); + + @Test + public void getMaxPoolSize() { + assertEquals(Integer.valueOf(2), getDataSourceMetadata().getMaxPoolSize()); + } + + @Test + public void getMinPoolSize() { + assertEquals(Integer.valueOf(0), getDataSourceMetadata().getMinPoolSize()); + } + + @Test + public void getPoolSizeNoConnection() { + // Make sure the pool is initialized + JdbcTemplate jdbcTemplate = new JdbcTemplate(getDataSourceMetadata().getDataSource()); + jdbcTemplate.execute(new ConnectionCallback() { + @Override + public Void doInConnection(Connection connection) throws SQLException, DataAccessException { + return null; + } + }); + assertEquals(Integer.valueOf(0), getDataSourceMetadata().getPoolSize()); + assertEquals(Float.valueOf(0), getDataSourceMetadata().getPoolUsage()); + } + + @Test + public void getPoolSizeOneConnection() { + JdbcTemplate jdbcTemplate = new JdbcTemplate(getDataSourceMetadata().getDataSource()); + jdbcTemplate.execute(new ConnectionCallback() { + @Override + public Void doInConnection(Connection connection) throws SQLException, DataAccessException { + assertEquals(Integer.valueOf(1), getDataSourceMetadata().getPoolSize()); + assertEquals(Float.valueOf(0.5F), getDataSourceMetadata().getPoolUsage()); + return null; + } + }); + } + + @Test + public void getPoolSizeTwoConnections() { + final JdbcTemplate jdbcTemplate = new JdbcTemplate(getDataSourceMetadata().getDataSource()); + jdbcTemplate.execute(new ConnectionCallback() { + @Override + public Void doInConnection(Connection connection) throws SQLException, DataAccessException { + jdbcTemplate.execute(new ConnectionCallback() { + @Override + public Void doInConnection(Connection connection) throws SQLException, DataAccessException { + assertEquals(Integer.valueOf(2), getDataSourceMetadata().getPoolSize()); + assertEquals(Float.valueOf(1F), getDataSourceMetadata().getPoolUsage()); + return null; + } + }); + return null; + } + }); + } + + protected DataSourceBuilder initializeBuilder() { + return DataSourceBuilder.create() + .driverClassName("org.hsqldb.jdbc.JDBCDriver") + .url("jdbc:hsqldb:mem:test") + .username("sa"); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/jdbc/CommonsDbcpDataSourceMetadataTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/jdbc/CommonsDbcpDataSourceMetadataTests.java new file mode 100644 index 0000000000..bb823ee117 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/jdbc/CommonsDbcpDataSourceMetadataTests.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2014 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.metrics.jdbc; + +import static org.junit.Assert.*; + +import org.apache.commons.dbcp.BasicDataSource; +import org.junit.Before; +import org.junit.Test; + +/** + * + * @author Stephane Nicoll + */ +public class CommonsDbcpDataSourceMetadataTests extends AbstractDataSourceMetadataTests { + + private CommonsDbcpDataSourceMetadata dataSourceMetadata; + + @Before + public void setup() { + this.dataSourceMetadata = createDataSourceMetadata(0, 2); + } + + @Override + protected CommonsDbcpDataSourceMetadata getDataSourceMetadata() { + return this.dataSourceMetadata; + } + + @Test + public void getPoolUsageWithNoCurrent() { + CommonsDbcpDataSourceMetadata dsm = new CommonsDbcpDataSourceMetadata(createDataSource()) { + @Override + public Integer getPoolSize() { + return null; + } + }; + assertNull(dsm.getPoolUsage()); + } + + @Test + public void getPoolUsageWithNoMax() { + CommonsDbcpDataSourceMetadata dsm = new CommonsDbcpDataSourceMetadata(createDataSource()) { + @Override + public Integer getMaxPoolSize() { + return null; + } + }; + assertNull(dsm.getPoolUsage()); + } + + @Test + public void getPoolUsageWithUnlimitedPool() { + DataSourceMetadata unlimitedDataSource = createDataSourceMetadata(0, -1); + assertEquals(Float.valueOf(-1F), unlimitedDataSource.getPoolUsage()); + } + + private CommonsDbcpDataSourceMetadata createDataSourceMetadata(int minSize, int maxSize) { + BasicDataSource dataSource = createDataSource(); + dataSource.setMinIdle(minSize); + dataSource.setMaxActive(maxSize); + return new CommonsDbcpDataSourceMetadata(dataSource); + } + + private BasicDataSource createDataSource() { + return (BasicDataSource) initializeBuilder().type(BasicDataSource.class).build(); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/jdbc/CompositeDataSourceMetadataProviderTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/jdbc/CompositeDataSourceMetadataProviderTests.java new file mode 100644 index 0000000000..f0a16231e1 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/jdbc/CompositeDataSourceMetadataProviderTests.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012-2014 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.metrics.jdbc; + +import static org.junit.Assert.*; +import static org.mockito.BDDMockito.*; + +import java.util.Arrays; + +import javax.sql.DataSource; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * + * @author Stephane Nicoll + */ +public class CompositeDataSourceMetadataProviderTests { + + @Mock + private DataSourceMetadataProvider firstProvider; + + @Mock + private DataSourceMetadata first; + + @Mock + private DataSource firstDataSource; + + @Mock + private DataSourceMetadataProvider secondProvider; + + @Mock + private DataSourceMetadata second; + + @Mock + private DataSource secondDataSource; + + @Mock + private DataSource unknownDataSource; + + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + given(firstProvider.getDataSourceMetadata(firstDataSource)).willReturn(first); + given(firstProvider.getDataSourceMetadata(secondDataSource)).willReturn(second); + } + + @Test + public void createWithProviders() { + CompositeDataSourceMetadataProvider provider = + new CompositeDataSourceMetadataProvider(Arrays.asList(firstProvider, secondProvider)); + assertSame(first, provider.getDataSourceMetadata(firstDataSource)); + assertSame(second, provider.getDataSourceMetadata(secondDataSource)); + assertNull(provider.getDataSourceMetadata(unknownDataSource)); + } + + @Test + public void addProvider() { + CompositeDataSourceMetadataProvider provider = + new CompositeDataSourceMetadataProvider(); + assertNull(provider.getDataSourceMetadata(firstDataSource)); + provider.addDataSourceMetadataProvider(firstProvider); + assertSame(first, provider.getDataSourceMetadata(firstDataSource)); + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/jdbc/HikariDataSourceMetadataTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/jdbc/HikariDataSourceMetadataTests.java new file mode 100644 index 0000000000..287b93396a --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/jdbc/HikariDataSourceMetadataTests.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2014 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.metrics.jdbc; + +import com.zaxxer.hikari.HikariDataSource; +import org.junit.Before; + +/** + * + * @author Stephane Nicoll + */ +public class HikariDataSourceMetadataTests extends AbstractDataSourceMetadataTests { + + private HikariDataSourceMetadata dataSourceMetadata; + + @Before + public void setup() { + this.dataSourceMetadata = createDataSourceMetadata(0, 2); + } + + @Override + protected HikariDataSourceMetadata getDataSourceMetadata() { + return this.dataSourceMetadata; + } + + private HikariDataSourceMetadata createDataSourceMetadata(int minSize, int maxSize) { + HikariDataSource dataSource = (HikariDataSource) initializeBuilder().type(HikariDataSource.class).build(); + dataSource.setMinimumIdle(minSize); + dataSource.setMaximumPoolSize(maxSize); + + return new HikariDataSourceMetadata(dataSource); + } +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/jdbc/TomcatDataSourceMetadataTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/jdbc/TomcatDataSourceMetadataTests.java new file mode 100644 index 0000000000..095fdb81fd --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/jdbc/TomcatDataSourceMetadataTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2014 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.metrics.jdbc; + +import org.apache.tomcat.jdbc.pool.DataSource; +import org.junit.Before; + +/** + * + * @author Stephane Nicoll + */ +public class TomcatDataSourceMetadataTests extends AbstractDataSourceMetadataTests { + + private TomcatDataSourceMetadata dataSourceMetadata; + + @Before + public void setup() { + this.dataSourceMetadata = createDataSourceMetadata(0, 2); + } + + @Override + protected TomcatDataSourceMetadata getDataSourceMetadata() { + return this.dataSourceMetadata; + } + + private TomcatDataSourceMetadata createDataSourceMetadata(int minSize, int maxSize) { + DataSource dataSource = (DataSource) initializeBuilder().type(DataSource.class).build(); + dataSource.setMinIdle(minSize); + dataSource.setMaxActive(maxSize); + + // Avoid warnings + dataSource.setInitialSize(minSize); + dataSource.setMaxIdle(maxSize); + return new TomcatDataSourceMetadata(dataSource); + } + +} diff --git a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 5f518295d1..00d66a0d9d 100644 --- a/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -615,7 +615,10 @@ endpoint you should see a response similar to this: "threads": 15, "threads.daemon": 11, "threads.peak": 15, - "uptime": 494836 + "uptime": 494836, + "instance.uptime": 489782, + "datasource.primary.active": 5, + "datasource.primary.usage": 0.25 } ---- @@ -631,7 +634,26 @@ The `gauge` shows the last response time for a request. So the last request to ` NOTE: In this example we are actually accessing the endpoint over HTTP using the `/metrics` URL, this explains why `metrics` appears in the response. +[[production-ready-datasource-metrics]] +=== DataSource metrics +The following metrics are available for each data source defined in the application: the +number of allocated connection(s) (`.active`) and the current usage of the connection +pool (`.usage`). + +All data source metrics share the `datasource.` prefix. The prefix is further qualified for +each data source: + +* If the data source is the primary data source (that is either the only available data + source or the one flagged `@Primary` amongst the existing ones), the prefix is `datasource.primary` +* If the data source bean name ends with `dataSource`, the prefix is the name of the bean without + it (i.e. `datasource.batch` for `batchDataSource`) +* In all other cases, the name of the bean is used + +It is possible to override part or all of those defaults by registering a bean with a customized +version of `DataSourcePublicMetrics`. Spring Boot provides those metadata for all supported +datasource; you can provide a `DataSourceMetadata` implementation for your favorite data source, +check `DatasourceMetadataProvidersConfiguration` for more details. [[production-ready-recording-metrics]] === Recording your own metrics