/*
* 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;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.junit.After;
import org.junit.Test;
import org.springframework.boot.actuate.endpoint.CachePublicMetrics;
import org.springframework.boot.actuate.endpoint.DataSourcePublicMetrics;
import org.springframework.boot.actuate.endpoint.MetricReaderPublicMetrics;
import org.springframework.boot.actuate.endpoint.PublicMetrics;
import org.springframework.boot.actuate.endpoint.RichGaugeReaderPublicMetrics;
import org.springframework.boot.actuate.endpoint.SystemPublicMetrics;
import org.springframework.boot.actuate.endpoint.TomcatPublicMetrics;
import org.springframework.boot.actuate.metrics.Metric;
import org.springframework.boot.actuate.metrics.rich.RichGauge;
import org.springframework.boot.actuate.metrics.rich.RichGaugeReader;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.server.MockServletWebServerFactory;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.ConfigurableApplicationContext;
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.core.annotation.Order;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ConnectionCallback;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.SocketUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link PublicMetricsAutoConfiguration}.
*
* @author Stephane Nicoll
* @author Dave Syer
* @author Phillip Webb
*/
public class PublicMetricsAutoConfigurationTests {
private ConfigurableApplicationContext context;
@After
public void after() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void systemPublicMetrics() throws Exception {
load();
assertThat(this.context.getBeansOfType(SystemPublicMetrics.class)).hasSize(1);
}
@Test
public void metricReaderPublicMetrics() throws Exception {
load();
assertThat(this.context.getBeansOfType(MetricReaderPublicMetrics.class))
.hasSize(2);
}
@Test
public void richGaugePublicMetrics() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
RichGaugeReaderConfig.class, MetricRepositoryAutoConfiguration.class,
PublicMetricsAutoConfiguration.class);
RichGaugeReader richGaugeReader = context.getBean(RichGaugeReader.class);
assertThat(richGaugeReader).isNotNull();
given(richGaugeReader.findAll())
.willReturn(Collections.singletonList(new RichGauge("bar", 3.7d)));
RichGaugeReaderPublicMetrics publicMetrics = context
.getBean(RichGaugeReaderPublicMetrics.class);
assertThat(publicMetrics).isNotNull();
Collection<Metric<?>> metrics = publicMetrics.metrics();
assertThat(metrics).isNotNull();
assertThat(6).isEqualTo(metrics.size());
assertHasMetric(metrics, new Metric<>("bar.val", 3.7d));
assertHasMetric(metrics, new Metric<>("bar.avg", 3.7d));
assertHasMetric(metrics, new Metric<>("bar.min", 3.7d));
assertHasMetric(metrics, new Metric<>("bar.max", 3.7d));
assertHasMetric(metrics, new Metric<>("bar.alpha", -1.d));
assertHasMetric(metrics, new Metric<>("bar.count", 1L));
context.close();
}
@Test
public void noDataSource() {
load();
assertThat(this.context.getBeansOfType(DataSourcePublicMetrics.class)).isEmpty();
}
@Test
public void autoDataSource() throws SQLException {
load(DataSourceAutoConfiguration.class);
PublicMetrics bean = this.context.getBean(DataSourcePublicMetrics.class);
this.context.getBean(DataSource.class).getConnection().close();
Collection<Metric<?>> metrics = bean.metrics();
assertMetrics(metrics, "datasource.primary.active", "datasource.primary.usage");
}
@Test
public void multipleDataSources() {
load(MultipleDataSourcesConfig.class);
PublicMetrics bean = this.context.getBean(DataSourcePublicMetrics.class);
Collection<Metric<?>> 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(
this.context.getBean("hikariDS", DataSource.class));
jdbcTemplate.execute(new ConnectionCallback<Void>() {
@Override
public Void doInConnection(Connection connection)
throws SQLException, DataAccessException {
return null;
}
});
Collection<Metric<?>> 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(DataSourcePublicMetrics.class);
Collection<Metric<?>> metrics = bean.metrics();
assertMetrics(metrics, "datasource.primary.active", "datasource.primary.usage",
"datasource.commonsDbcp.active", "datasource.commonsDbcp.usage");
}
@Test
public void multipleDataSourcesWithCustomPrimary() {
load(MultipleDataSourcesWithCustomPrimaryConfig.class);
PublicMetrics bean = this.context.getBean(DataSourcePublicMetrics.class);
Collection<Metric<?>> metrics = bean.metrics();
assertMetrics(metrics, "datasource.primary.active", "datasource.primary.usage",
"datasource.dataSource.active", "datasource.dataSource.usage");
}
@Test
public void customPrefix() {
load(MultipleDataSourcesWithPrimaryConfig.class,
CustomDataSourcePublicMetrics.class);
PublicMetrics bean = this.context.getBean(DataSourcePublicMetrics.class);
Collection<Metric<?>> metrics = bean.metrics();
assertMetrics(metrics, "ds.first.active", "ds.first.usage", "ds.second.active",
"ds.second.usage");
}
@Test
public void tomcatMetrics() throws Exception {
loadWeb(TomcatConfiguration.class);
assertThat(this.context.getBeansOfType(TomcatPublicMetrics.class)).hasSize(1);
}
@Test
public void noCacheMetrics() {
load();
assertThat(this.context.getBeansOfType(CachePublicMetrics.class)).isEmpty();
}
@Test
public void autoCacheManager() {
load(CacheConfiguration.class);
CachePublicMetrics bean = this.context.getBean(CachePublicMetrics.class);
Collection<Metric<?>> metrics = bean.metrics();
assertMetrics(metrics, "cache.books.size", "cache.speakers.size");
}
@Test
public void multipleCacheManagers() {
load(MultipleCacheConfiguration.class);
CachePublicMetrics bean = this.context.getBean(CachePublicMetrics.class);
Collection<Metric<?>> metrics = bean.metrics();
assertMetrics(metrics, "cache.books.size", "cache.second_speakers.size",
"cache.first_speakers.size", "cache.users.size");
}
private void assertHasMetric(Collection<Metric<?>> metrics, Metric<?> metric) {
for (Metric<?> m : metrics) {
if (m.getValue().equals(metric.getValue())
&& m.getName().equals(metric.getName())) {
return;
}
}
fail("Metric " + metric.toString() + " not found in " + metrics.toString());
}
private void assertMetrics(Collection<Metric<?>> metrics, String... keys) {
Map<String, Number> content = new HashMap<>();
for (Metric<?> metric : metrics) {
content.put(metric.getName(), metric.getValue());
}
for (String key : keys) {
assertThat(content).containsKey(key);
}
}
private void loadWeb(Class<?>... config) {
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext();
if (config.length > 0) {
context.register(config);
}
context.register(DataSourcePoolMetadataProvidersConfiguration.class,
CacheStatisticsAutoConfiguration.class,
PublicMetricsAutoConfiguration.class, MockServletWebServerFactory.class);
context.refresh();
this.context = context;
}
private void load(Class<?>... config) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (config.length > 0) {
context.register(config);
}
context.register(DataSourcePoolMetadataProvidersConfiguration.class,
CacheStatisticsAutoConfiguration.class,
PublicMetricsAutoConfiguration.class);
context.refresh();
this.context = context;
}
@Configuration
static class MultipleDataSourcesConfig {
@Bean
public DataSource tomcatDataSource() {
return InitializedBuilder.create()
.type(org.apache.tomcat.jdbc.pool.DataSource.class).build();
}
@Bean
public DataSource hikariDS() {
return InitializedBuilder.create().type(HikariDataSource.class).build();
}
@Bean
public DataSource commonsDbcpDataSource() {
return InitializedBuilder.create().type(BasicDataSource.class).build();
}
}
@Configuration
static class MultipleDataSourcesWithPrimaryConfig {
@Bean
@Primary
public DataSource myDataSource() {
return InitializedBuilder.create()
.type(org.apache.tomcat.jdbc.pool.DataSource.class).build();
}
@Bean
public DataSource commonsDbcpDataSource() {
return InitializedBuilder.create().type(BasicDataSource.class).build();
}
}
@Configuration
static class MultipleDataSourcesWithCustomPrimaryConfig {
@Bean
@Primary
public DataSource myDataSource() {
return InitializedBuilder.create()
.type(org.apache.tomcat.jdbc.pool.DataSource.class).build();
}
@Bean
public DataSource dataSource() {
return InitializedBuilder.create().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");
}
};
}
}
@Configuration
static class RichGaugeReaderConfig {
@Bean
public RichGaugeReader richGaugeReader() {
return mock(RichGaugeReader.class);
}
}
@Configuration
static class TomcatConfiguration {
@Bean
public TomcatServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.setPort(SocketUtils.findAvailableTcpPort(40000));
return factory;
}
}
@Configuration
static class CacheConfiguration {
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("books", "speakers");
}
}
@Configuration
static class MultipleCacheConfiguration {
@Bean
@Order(1)
public CacheManager first() {
return new ConcurrentMapCacheManager("books", "speakers");
}
@Bean
@Order(2)
public CacheManager second() {
return new ConcurrentMapCacheManager("users", "speakers");
}
}
private static class InitializedBuilder {
public static DataSourceBuilder create() {
return DataSourceBuilder.create()
.driverClassName("org.hsqldb.jdbc.JDBCDriver")
.url("jdbc:hsqldb:mem:test").username("sa");
}
}
}