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.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<String, DataSource> dataSources, MeterRegistry registry,
@Bean
DataSourcePoolMetadataMeterBinder dataSourcePoolMetadataMeterBinder(Map<String, DataSource> dataSources,
ObjectProvider<DataSourcePoolMetadataProvider> metadataProviders) {
List<DataSourcePoolMetadataProvider> 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<DataSourcePoolMetadataProvider> metadataProviders, MeterRegistry registry) {
String dataSourceName = getDataSourceName(beanName);
new DataSourcePoolMetrics(dataSource, metadataProviders, dataSourceName, Collections.emptyList())
.bindTo(registry);
}
static class DataSourcePoolMetadataMeterBinder implements MeterBinder {
private final Map<String, DataSource> dataSources;
private final ObjectProvider<DataSourcePoolMetadataProvider> metadataProviders;
DataSourcePoolMetadataMeterBinder(Map<String, DataSource> dataSources,
ObjectProvider<DataSourcePoolMetadataProvider> 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<DataSourcePoolMetadataProvider> 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<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)
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) {
this.registry = registry;
}
private static final Log logger = LogFactory.getLog(HikariDataSourceMeterBinder.class);
@Autowired
void bindMetricsRegistryToHikariDataSources(Collection<DataSource> dataSources) {
for (DataSource dataSource : dataSources) {
HikariDataSource hikariDataSource = DataSourceUnwrapper.unwrap(dataSource, HikariConfigMXBean.class,
HikariDataSource.class);
if (hikariDataSource != null) {
bindMetricsRegistryToHikariDataSource(hikariDataSource);
}
private final ObjectProvider<DataSource> dataSources;
HikariDataSourceMeterBinder(ObjectProvider<DataSource> 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()));
}
}
}
}
}

@ -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)

Loading…
Cancel
Save