Use MeterBinders to bind DataSource metrics

Closes gh-30282
pull/29812/head
Andy Wilkinson 3 years ago
parent 5e76671859
commit ff40c8b6f5

@ -28,11 +28,11 @@ import com.zaxxer.hikari.HikariConfigMXBean;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.metrics.micrometer.MicrometerMetricsTrackerFactory; import com.zaxxer.hikari.metrics.micrometer.MicrometerMetricsTrackerFactory;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.MeterBinder;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider; 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.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.metrics.jdbc.DataSourcePoolMetrics; 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.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.jdbc.DataSourceUnwrapper; import org.springframework.boot.jdbc.DataSourceUnwrapper;
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider; import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.log.LogMessage; import org.springframework.core.log.LogMessage;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -66,33 +67,52 @@ public class DataSourcePoolMetricsAutoConfiguration {
private static final String DATASOURCE_SUFFIX = "dataSource"; private static final String DATASOURCE_SUFFIX = "dataSource";
@Autowired @Bean
void bindDataSourcesToRegistry(Map<String, DataSource> dataSources, MeterRegistry registry, DataSourcePoolMetadataMeterBinder dataSourcePoolMetadataMeterBinder(Map<String, DataSource> dataSources,
ObjectProvider<DataSourcePoolMetadataProvider> metadataProviders) { ObjectProvider<DataSourcePoolMetadataProvider> metadataProviders) {
List<DataSourcePoolMetadataProvider> metadataProvidersList = metadataProviders.stream() return new DataSourcePoolMetadataMeterBinder(dataSources, metadataProviders);
.collect(Collectors.toList());
dataSources.forEach(
(name, dataSource) -> bindDataSourceToRegistry(name, dataSource, metadataProvidersList, registry));
} }
private void bindDataSourceToRegistry(String beanName, DataSource dataSource, static class DataSourcePoolMetadataMeterBinder implements MeterBinder {
Collection<DataSourcePoolMetadataProvider> metadataProviders, MeterRegistry registry) {
String dataSourceName = getDataSourceName(beanName); private final Map<String, DataSource> dataSources;
new DataSourcePoolMetrics(dataSource, metadataProviders, dataSourceName, Collections.emptyList())
.bindTo(registry); private final ObjectProvider<DataSourcePoolMetadataProvider> metadataProviders;
}
DataSourcePoolMetadataMeterBinder(Map<String, DataSource> dataSources,
ObjectProvider<DataSourcePoolMetadataProvider> metadataProviders) {
this.dataSources = dataSources;
this.metadataProviders = metadataProviders;
}
/** @Override
* Get the name of a DataSource based on its {@code beanName}. public void bindTo(MeterRegistry registry) {
* @param beanName the name of the data source bean List<DataSourcePoolMetadataProvider> metadataProvidersList = this.metadataProviders.stream()
* @return a name for the given data source .collect(Collectors.toList());
*/ this.dataSources.forEach((name, dataSource) -> bindDataSourceToRegistry(name, dataSource,
private String getDataSourceName(String beanName) { metadataProvidersList, registry));
if (beanName.length() > DATASOURCE_SUFFIX.length() }
&& StringUtils.endsWithIgnoreCase(beanName, DATASOURCE_SUFFIX)) {
return beanName.substring(0, beanName.length() - DATASOURCE_SUFFIX.length()); private void bindDataSourceToRegistry(String beanName, DataSource dataSource,
Collection<DataSourcePoolMetadataProvider> 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) @ConditionalOnClass(HikariDataSource.class)
static class HikariDataSourceMetricsConfiguration { static class HikariDataSourceMetricsConfiguration {
private static final Log logger = LogFactory.getLog(HikariDataSourceMetricsConfiguration.class); @Bean
HikariDataSourceMeterBinder hikariDataSourceMeterBinder(ObjectProvider<DataSource> dataSources) {
return new HikariDataSourceMeterBinder(dataSources);
}
private final MeterRegistry registry; static class HikariDataSourceMeterBinder implements MeterBinder {
HikariDataSourceMetricsConfiguration(MeterRegistry registry) { private static final Log logger = LogFactory.getLog(HikariDataSourceMeterBinder.class);
this.registry = registry;
}
@Autowired private final ObjectProvider<DataSource> dataSources;
void bindMetricsRegistryToHikariDataSources(Collection<DataSource> dataSources) {
for (DataSource dataSource : dataSources) { HikariDataSourceMeterBinder(ObjectProvider<DataSource> dataSources) {
HikariDataSource hikariDataSource = DataSourceUnwrapper.unwrap(dataSource, HikariConfigMXBean.class, this.dataSources = dataSources;
HikariDataSource.class);
if (hikariDataSource != null) {
bindMetricsRegistryToHikariDataSource(hikariDataSource);
}
} }
}
private void bindMetricsRegistryToHikariDataSource(HikariDataSource hikari) { @Override
if (hikari.getMetricRegistry() == null && hikari.getMetricsTrackerFactory() == null) { public void bindTo(MeterRegistry registry) {
try { for (DataSource dataSource : this.dataSources) {
hikari.setMetricsTrackerFactory(new MicrometerMetricsTrackerFactory(this.registry)); 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()));
}
} }
} }
} }
} }

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.aop.framework.ProxyFactory;
import org.springframework.beans.factory.config.BeanPostProcessor; 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.actuate.autoconfigure.metrics.test.MetricsRun;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 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 @Test
void autoConfiguredHikariDataSourceIsInstrumented() { void autoConfiguredHikariDataSourceIsInstrumented() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class)) 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 @Test
void hikariProxiedDataSourceCanBeInstrumented() { void hikariProxiedDataSourceCanBeInstrumented() {
this.contextRunner.withUserConfiguration(ProxiedHikariDataSourcesConfiguration.class) this.contextRunner.withUserConfiguration(ProxiedHikariDataSourcesConfiguration.class)

Loading…
Cancel
Save