From ff40c8b6f5272aa56cff52c04ecb7ca54c3d8b08 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 21 Mar 2022 15:22:38 +0000 Subject: [PATCH] Use MeterBinders to bind DataSource metrics Closes gh-30282 --- ...ataSourcePoolMetricsAutoConfiguration.java | 115 +++++++++++------- ...urcePoolMetricsAutoConfigurationTests.java | 31 ++++- 2 files changed, 102 insertions(+), 44 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfiguration.java index c78bea92e5..8816f91ce5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfiguration.java @@ -28,11 +28,11 @@ import com.zaxxer.hikari.HikariConfigMXBean; import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.metrics.micrometer.MicrometerMetricsTrackerFactory; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.binder.MeterBinder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.ObjectProvider; -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.jdbc.DataSourcePoolMetrics; @@ -43,6 +43,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.jdbc.DataSourceUnwrapper; import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.log.LogMessage; import org.springframework.util.StringUtils; @@ -66,33 +67,52 @@ public class DataSourcePoolMetricsAutoConfiguration { private static final String DATASOURCE_SUFFIX = "dataSource"; - @Autowired - void bindDataSourcesToRegistry(Map dataSources, MeterRegistry registry, + @Bean + DataSourcePoolMetadataMeterBinder dataSourcePoolMetadataMeterBinder(Map dataSources, ObjectProvider metadataProviders) { - List metadataProvidersList = metadataProviders.stream() - .collect(Collectors.toList()); - dataSources.forEach( - (name, dataSource) -> bindDataSourceToRegistry(name, dataSource, metadataProvidersList, registry)); + return new DataSourcePoolMetadataMeterBinder(dataSources, metadataProviders); } - private void bindDataSourceToRegistry(String beanName, DataSource dataSource, - Collection metadataProviders, MeterRegistry registry) { - String dataSourceName = getDataSourceName(beanName); - new DataSourcePoolMetrics(dataSource, metadataProviders, dataSourceName, Collections.emptyList()) - .bindTo(registry); - } + static class DataSourcePoolMetadataMeterBinder implements MeterBinder { + + private final Map dataSources; + + private final ObjectProvider metadataProviders; + + DataSourcePoolMetadataMeterBinder(Map dataSources, + ObjectProvider metadataProviders) { + this.dataSources = dataSources; + this.metadataProviders = metadataProviders; + } - /** - * 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() - && StringUtils.endsWithIgnoreCase(beanName, DATASOURCE_SUFFIX)) { - return beanName.substring(0, beanName.length() - DATASOURCE_SUFFIX.length()); + @Override + public void bindTo(MeterRegistry registry) { + List metadataProvidersList = this.metadataProviders.stream() + .collect(Collectors.toList()); + this.dataSources.forEach((name, dataSource) -> bindDataSourceToRegistry(name, dataSource, + metadataProvidersList, registry)); + } + + private void bindDataSourceToRegistry(String beanName, DataSource dataSource, + Collection metadataProviders, MeterRegistry registry) { + String dataSourceName = getDataSourceName(beanName); + new DataSourcePoolMetrics(dataSource, metadataProviders, dataSourceName, Collections.emptyList()) + .bindTo(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() + && StringUtils.endsWithIgnoreCase(beanName, DATASOURCE_SUFFIX)) { + return beanName.substring(0, beanName.length() - DATASOURCE_SUFFIX.length()); + } + return beanName; } - return beanName; + } } @@ -101,34 +121,43 @@ public class DataSourcePoolMetricsAutoConfiguration { @ConditionalOnClass(HikariDataSource.class) static class HikariDataSourceMetricsConfiguration { - private static final Log logger = LogFactory.getLog(HikariDataSourceMetricsConfiguration.class); + @Bean + HikariDataSourceMeterBinder hikariDataSourceMeterBinder(ObjectProvider dataSources) { + return new HikariDataSourceMeterBinder(dataSources); + } - private final MeterRegistry registry; + static class HikariDataSourceMeterBinder implements MeterBinder { - HikariDataSourceMetricsConfiguration(MeterRegistry registry) { - this.registry = registry; - } + private static final Log logger = LogFactory.getLog(HikariDataSourceMeterBinder.class); - @Autowired - void bindMetricsRegistryToHikariDataSources(Collection dataSources) { - for (DataSource dataSource : dataSources) { - HikariDataSource hikariDataSource = DataSourceUnwrapper.unwrap(dataSource, HikariConfigMXBean.class, - HikariDataSource.class); - if (hikariDataSource != null) { - bindMetricsRegistryToHikariDataSource(hikariDataSource); - } + private final ObjectProvider dataSources; + + HikariDataSourceMeterBinder(ObjectProvider dataSources) { + this.dataSources = dataSources; } - } - private void bindMetricsRegistryToHikariDataSource(HikariDataSource hikari) { - if (hikari.getMetricRegistry() == null && hikari.getMetricsTrackerFactory() == null) { - try { - hikari.setMetricsTrackerFactory(new MicrometerMetricsTrackerFactory(this.registry)); + @Override + public void bindTo(MeterRegistry registry) { + for (DataSource dataSource : this.dataSources) { + HikariDataSource hikariDataSource = DataSourceUnwrapper.unwrap(dataSource, HikariConfigMXBean.class, + HikariDataSource.class); + if (hikariDataSource != null) { + bindMetricsRegistryToHikariDataSource(hikariDataSource, registry); + } } - catch (Exception ex) { - logger.warn(LogMessage.format("Failed to bind Hikari metrics: %s", ex.getMessage())); + } + + private void bindMetricsRegistryToHikariDataSource(HikariDataSource hikari, MeterRegistry registry) { + if (hikari.getMetricRegistry() == null && hikari.getMetricsTrackerFactory() == null) { + try { + hikari.setMetricsTrackerFactory(new MicrometerMetricsTrackerFactory(registry)); + } + catch (Exception ex) { + logger.warn(LogMessage.format("Failed to bind Hikari metrics: %s", ex.getMessage())); + } } } + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfigurationTests.java index 77d37405d3..460232c3bb 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 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. @@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.LazyInitializationBeanFactoryPostProcessor; import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; @@ -90,6 +91,19 @@ class DataSourcePoolMetricsAutoConfigurationTests { }); } + @Test + void allDataSourcesCanBeInstrumentedWithLazyInitialization() { + this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class)).withInitializer( + (context) -> context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor())) + .withUserConfiguration(TwoDataSourcesConfiguration.class).run((context) -> { + context.getBean("firstDataSource", DataSource.class).getConnection().getMetaData(); + context.getBean("secondOne", DataSource.class).getConnection().getMetaData(); + MeterRegistry registry = context.getBean(MeterRegistry.class); + registry.get("jdbc.connections.max").tags("name", "first").meter(); + registry.get("jdbc.connections.max").tags("name", "secondOne").meter(); + }); + } + @Test void autoConfiguredHikariDataSourceIsInstrumented() { this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class)) @@ -167,6 +181,21 @@ class DataSourcePoolMetricsAutoConfigurationTests { }); } + @Test + void allHikariDataSourcesCanBeInstrumentedWhenUsingLazyInitialization() { + this.contextRunner.withUserConfiguration(TwoHikariDataSourcesConfiguration.class) + .withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class)) + .withInitializer((context) -> context + .addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor())) + .run((context) -> { + context.getBean("firstDataSource", DataSource.class).getConnection(); + context.getBean("secondOne", DataSource.class).getConnection(); + MeterRegistry registry = context.getBean(MeterRegistry.class); + registry.get("hikaricp.connections").tags("pool", "firstDataSource").meter(); + registry.get("hikaricp.connections").tags("pool", "secondOne").meter(); + }); + } + @Test void hikariProxiedDataSourceCanBeInstrumented() { this.contextRunner.withUserConfiguration(ProxiedHikariDataSourcesConfiguration.class)