diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfiguration.java index 92d005e7c5..95cb212f07 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAutoConfiguration.java @@ -27,6 +27,7 @@ import io.micrometer.core.instrument.composite.CompositeMeterRegistry; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; +import org.springframework.boot.actuate.autoconfigure.metrics.cache.CacheMetricsConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter; import org.springframework.boot.actuate.autoconfigure.metrics.export.atlas.AtlasExportConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.datadog.DatadogExportConfiguration; @@ -45,6 +46,7 @@ import org.springframework.boot.actuate.metrics.MetricsEndpoint; import org.springframework.boot.actuate.metrics.integration.SpringIntegrationMetrics; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -68,12 +70,13 @@ import org.springframework.integration.support.management.IntegrationManagementC @EnableConfigurationProperties(MetricsProperties.class) @Import({ MeterBindersConfiguration.class, WebMvcMetricsConfiguration.class, WebFluxMetricsConfiguration.class, RestTemplateMetricsConfiguration.class, - DataSourcePoolMetricsConfiguration.class, AtlasExportConfiguration.class, - DatadogExportConfiguration.class, GangliaExportConfiguration.class, - GraphiteExportConfiguration.class, InfluxExportConfiguration.class, - JmxExportConfiguration.class, PrometheusExportConfiguration.class, - SimpleExportConfiguration.class, StatsdExportConfiguration.class }) -@AutoConfigureAfter(DataSourceAutoConfiguration.class) + CacheMetricsConfiguration.class, DataSourcePoolMetricsConfiguration.class, + AtlasExportConfiguration.class, DatadogExportConfiguration.class, + GangliaExportConfiguration.class, GraphiteExportConfiguration.class, + InfluxExportConfiguration.class, JmxExportConfiguration.class, + PrometheusExportConfiguration.class, SimpleExportConfiguration.class, + StatsdExportConfiguration.class }) +@AutoConfigureAfter({ CacheAutoConfiguration.class, DataSourceAutoConfiguration.class }) public class MetricsAutoConfiguration { @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMeterBinderProvidersConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMeterBinderProvidersConfiguration.java new file mode 100644 index 0000000000..e0f7e6223a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMeterBinderProvidersConfiguration.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-2018 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.metrics.cache; + +import com.hazelcast.core.Hazelcast; +import com.hazelcast.spring.cache.HazelcastCache; +import io.micrometer.core.instrument.binder.MeterBinder; +import net.sf.ehcache.Ehcache; + +import org.springframework.boot.actuate.metrics.cache.CacheMeterBinderProvider; +import org.springframework.boot.actuate.metrics.cache.CacheMeterBinderProviders; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.cache.caffeine.CaffeineCache; +import org.springframework.cache.ehcache.EhCacheCache; +import org.springframework.cache.jcache.JCacheCache; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link Configuration Auto-configuration} for {@link CacheMeterBinderProvider} beans. + * + * @author Stephane Nicoll + * @since 2.0.0 + * @see CacheMeterBinderProviders + */ +@Configuration +@ConditionalOnClass(MeterBinder.class) +class CacheMeterBinderProvidersConfiguration { + + @Configuration + @ConditionalOnClass({ CaffeineCache.class, com.github.benmanes.caffeine.cache.Cache.class }) + static class CaffeineCacheMeterBinderProviderConfiguration { + + @Bean + public CacheMeterBinderProvider caffeineCacheMeterBinderProvider() { + return new CacheMeterBinderProviders.CaffeineCacheMeterBinderProvider(); + } + + } + + @Configuration + @ConditionalOnClass({ EhCacheCache.class, Ehcache.class }) + static class EhCache2CacheMeterBinderProviderConfiguration { + + @Bean + public CacheMeterBinderProvider ehCache2CacheMeterBinderProvider() { + return new CacheMeterBinderProviders.EhCache2CacheMeterBinderProvider(); + } + + } + + @Configuration + @ConditionalOnClass({ HazelcastCache.class, Hazelcast.class }) + static class HazelcastCacheMeterBinderProviderConfiguration { + + @Bean + public CacheMeterBinderProvider hazelcastCacheMeterBinderProvider() { + return new CacheMeterBinderProviders.HazelcastCacheMeterBinderProvider(); + } + + } + + @Configuration + @ConditionalOnClass({ JCacheCache.class, javax.cache.CacheManager.class }) + static class JCacheCacheMeterBinderProviderConfiguration { + + @Bean + public CacheMeterBinderProvider jCacheCacheMeterBinderProvider() { + return new CacheMeterBinderProviders.JCacheCacheMeterBinderProvider(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsConfiguration.java new file mode 100644 index 0000000000..fc35039387 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsConfiguration.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2018 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.metrics.cache; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * Configure metrics for all available {@link Cache caches}. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +@Configuration +@ConditionalOnBean(CacheManager.class) +@ConditionalOnProperty(value = "management.metrics.cache.instrument-cache", matchIfMissing = true) +@EnableConfigurationProperties(CacheMetricsProperties.class) +@Import({ CacheMeterBinderProvidersConfiguration.class, CacheMetricsRegistrarConfiguration.class }) +public class CacheMetricsConfiguration { + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsProperties.java new file mode 100644 index 0000000000..2a2833020c --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsProperties.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2018 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.metrics.cache; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties for Cache-based metrics. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +@ConfigurationProperties("management.metrics.cache") +public class CacheMetricsProperties { + + /** + * Name of the metric for cache usage. + */ + private String cacheMetricName = "cache"; + + public String getCacheMetricName() { + return this.cacheMetricName; + } + + public void setCacheMetricName(String cacheMetricName) { + this.cacheMetricName = cacheMetricName; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsRegistrar.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsRegistrar.java new file mode 100644 index 0000000000..9383fb68a4 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsRegistrar.java @@ -0,0 +1,98 @@ +/* + * Copyright 2012-2018 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.metrics.cache; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.binder.MeterBinder; + +import org.springframework.boot.actuate.metrics.cache.CacheMeterBinderProvider; +import org.springframework.cache.Cache; + +/** + * Register supported {@link Cache} to a {@link MeterRegistry}. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +public class CacheMetricsRegistrar { + + private final MeterRegistry registry; + + private final String metricName; + + private final Collection cacheMeterBinderProviders; + + /** + * Creates a new registrar. + * @param registry the {@link MeterRegistry} to use + * @param metricName the name of the metric + * @param binderProviders the {@link CacheMeterBinderProvider} instances that should + * be used to detect compatible caches + */ + public CacheMetricsRegistrar(MeterRegistry registry, String metricName, + Collection binderProviders) { + this.registry = registry; + this.metricName = metricName; + this.cacheMeterBinderProviders = binderProviders; + } + + /** + * Attempt to bind the specified {@link Cache} to the registry. Return {@code true} + * if the cache is supported and was bound to the registry, {@code false} otherwise. + * @param cache the cache to handle + * @param tags the tags to associate with the metrics of that cache + * @return {@code true} if the {@code cache} is supported and was registered + */ + public boolean bindCacheToRegistry(Cache cache, Tag... tags) { + List allTags = new ArrayList(Arrays.asList(tags)); + MeterBinder meterBinder = getMeterBinder(cache, allTags); + if (meterBinder != null) { + meterBinder.bindTo(this.registry); + return true; + } + return false; + } + + private MeterBinder getMeterBinder(Cache cache, List tags) { + tags.addAll(getAdditionalTags(cache)); + for (CacheMeterBinderProvider binderProvider : this.cacheMeterBinderProviders) { + MeterBinder meterBinder = binderProvider.getMeterBinder(cache, + this.metricName, tags); + if (meterBinder != null) { + return meterBinder; + } + } + return null; + } + + /** + * Return additional {@link Tag tags} to be associated with the given {@link Cache}. + * @param cache the cache + * @return a list of additional tags to associate to that {@code cache}. + */ + protected List getAdditionalTags(Cache cache) { + return Tags.zip("name", cache.getName()); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsRegistrarConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsRegistrarConfiguration.java new file mode 100644 index 0000000000..8c5ae0aa23 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsRegistrarConfiguration.java @@ -0,0 +1,98 @@ +/* + * Copyright 2012-2018 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.metrics.cache; + +import java.util.Collection; +import java.util.Map; + +import javax.annotation.PostConstruct; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; + +import org.springframework.boot.actuate.metrics.cache.CacheMeterBinderProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + +/** + * Configure a {@link CacheMetricsRegistrar} and register all available + * {@link Cache caches}. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +@Configuration +@ConditionalOnBean(CacheMeterBinderProvider.class) +class CacheMetricsRegistrarConfiguration { + + private static final String CACHE_MANAGER_SUFFIX = "cacheManager"; + + private final MeterRegistry registry; + + private final CacheMetricsProperties properties; + + private final Collection cacheMeterBinderProviders; + + private final Map cacheManagers; + + CacheMetricsRegistrarConfiguration(MeterRegistry registry, + CacheMetricsProperties properties, + Collection cacheMeterBinderProviders, + Map cacheManagers) { + this.registry = registry; + this.cacheMeterBinderProviders = cacheMeterBinderProviders; + this.properties = properties; + this.cacheManagers = cacheManagers; + } + + @Bean + public CacheMetricsRegistrar cacheMetricsRegistrar() { + return new CacheMetricsRegistrar(this.registry, + this.properties.getCacheMetricName(), this.cacheMeterBinderProviders); + } + + @PostConstruct + public void bindCachesToRegistry() { + this.cacheManagers.forEach((beanName, cacheManager) -> cacheManager.getCacheNames() + .forEach((cacheName) -> + bindCacheToRegistry(beanName, cacheManager.getCache(cacheName)))); + } + + private void bindCacheToRegistry(String beanName, Cache cache) { + Tag cacheManagerTag = Tag.of("cacheManager", getCacheManagerName(beanName)); + cacheMetricsRegistrar().bindCacheToRegistry(cache, cacheManagerTag); + } + + /** + * Get the name of a {@link CacheManager} based on its {@code beanName}. + * @param beanName the name of the {@link CacheManager} bean + * @return a name for the given cache manager + */ + private String getCacheManagerName(String beanName) { + if (beanName.length() > CACHE_MANAGER_SUFFIX.length() + && StringUtils.endsWithIgnoreCase(beanName, CACHE_MANAGER_SUFFIX)) { + return beanName.substring(0, + beanName.length() - CACHE_MANAGER_SUFFIX.length()); + } + return beanName; + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 16aa476e43..d82d6af79b 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -215,6 +215,12 @@ "description": "Whether to enable uptime metrics.", "defaultValue": true }, + { + "name": "management.metrics.cache.instrument-cache", + "type": "java.lang.Boolean", + "description": "Instrument all available caches.", + "defaultValue": true + }, { "name": "management.metrics.export.jmx.enabled", "type": "java.lang.Boolean", diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsConfigurationTests.java new file mode 100644 index 0000000000..cf769be4b6 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsConfigurationTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2012-2018 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.metrics.cache; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.Test; + +import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link CacheMetricsConfiguration}. + * + * @author Stephane Nicoll + */ +public class CacheMetricsConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withUserConfiguration(RegistryConfiguration.class) + .withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class)) + .withPropertyValues("management.metrics.use-global-registry=false"); + + @Test + public void autoConfiguredCacheManagerIsInstrumented() { + this.contextRunner + .withConfiguration( + AutoConfigurations.of(CacheAutoConfiguration.class)) + .withPropertyValues("spring.cache.type=caffeine", + "spring.cache.cache-names=cache1,cache2") + .run((context) -> { + MeterRegistry registry = context.getBean(MeterRegistry.class); + assertThat(registry.find("cache.requests") + .tags("name", "cache1") + .tags("cacheManager", "cacheManager").meter()).isPresent(); + assertThat(registry.find("cache.requests") + .tags("name", "cache2") + .tags("cacheManager", "cacheManager").meter()).isPresent(); + }); + } + + @Test + public void autoConfiguredCacheManagerWithCustomMetricName() { + this.contextRunner + .withConfiguration( + AutoConfigurations.of(CacheAutoConfiguration.class)) + .withPropertyValues( + "management.metrics.cache.cache-metric-name=custom.name", + "spring.cache.type=caffeine", "spring.cache.cache-names=cache1") + .run((context) -> { + MeterRegistry registry = context.getBean(MeterRegistry.class); + assertThat(registry.find("custom.name.requests") + .tags("name", "cache1") + .tags("cacheManager", "cacheManager").meter()).isPresent(); + }); + } + + @Test + public void autoConfiguredNonSupportedCacheManagerIsIgnored() { + this.contextRunner + .withConfiguration( + AutoConfigurations.of(CacheAutoConfiguration.class)) + .withPropertyValues("spring.cache.type=simple", + "spring.cache.cache-names=cache1,cache2") + .run((context) -> { + MeterRegistry registry = context.getBean(MeterRegistry.class); + assertThat(registry.find("cache.requests") + .tags("name", "cache1") + .tags("cacheManager", "cacheManager").meter()).isNotPresent(); + assertThat(registry.find("cache.requests") + .tags("name", "cache2") + .tags("cacheManager", "cacheManager").meter()).isNotPresent(); + }); + } + + @Test + public void cacheInstrumentationCanBeDisabled() { + this.contextRunner + .withConfiguration( + AutoConfigurations.of(CacheAutoConfiguration.class)) + .withPropertyValues("management.metrics.cache.instrument-cache=false", + "spring.cache.type=caffeine", "spring.cache.cache-names=cache1") + .run((context) -> { + MeterRegistry registry = context.getBean(MeterRegistry.class); + assertThat(registry.find("cache.requests") + .tags("name", "cache1") + .tags("cacheManager", "cacheManager").meter()).isNotPresent(); + }); + } + + + @Configuration + @EnableCaching + static class RegistryConfiguration { + + @Bean + public MeterRegistry meterRegistry() { + return new SimpleMeterRegistry(); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsRegistrarTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsRegistrarTests.java new file mode 100644 index 0000000000..f8d91d50dd --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/cache/CacheMetricsRegistrarTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2018 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.metrics.cache; + +import java.util.Collections; + +import com.github.benmanes.caffeine.cache.Caffeine; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.Test; + +import org.springframework.boot.actuate.metrics.cache.CacheMeterBinderProviders; +import org.springframework.cache.caffeine.CaffeineCache; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link CacheMetricsRegistrar}. + * + * @author Stephane Nicoll + */ +public class CacheMetricsRegistrarTests { + + private final MeterRegistry meterRegistry = new SimpleMeterRegistry(); + + @Test + public void bindToSupportedCache() { + CacheMetricsRegistrar registrar = new CacheMetricsRegistrar(this.meterRegistry, + "root", Collections.singleton( + new CacheMeterBinderProviders.CaffeineCacheMeterBinderProvider())); + assertThat(registrar.bindCacheToRegistry( + new CaffeineCache("test", Caffeine.newBuilder().build()))).isTrue(); + assertThat(this.meterRegistry.find("root.requests") + .tags("name", "test").meter()).isPresent(); + } + + @Test + public void bindToUnsupportedCache() { + CacheMetricsRegistrar registrar = new CacheMetricsRegistrar(this.meterRegistry, + "root", Collections.EMPTY_LIST); + assertThat(registrar.bindCacheToRegistry( + new CaffeineCache("test", Caffeine.newBuilder().build()))).isFalse(); + assertThat(this.meterRegistry.find("root.requests") + .tags("name", "test").meter()).isNotPresent(); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/pom.xml b/spring-boot-project/spring-boot-actuator/pom.xml index 301f5ed191..9d5e6a65f4 100644 --- a/spring-boot-project/spring-boot-actuator/pom.xml +++ b/spring-boot-project/spring-boot-actuator/pom.xml @@ -26,6 +26,16 @@ jackson-databind true + + com.hazelcast + hazelcast + true + + + com.hazelcast + hazelcast-spring + true + com.sun.mail javax.mail @@ -56,11 +66,21 @@ jest true + + javax.cache + cache-api + true + javax.jms javax.jms-api true + + net.sf.ehcache + ehcache + true + org.apache.tomcat.embed tomcat-embed-core diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/CacheMeterBinderProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/CacheMeterBinderProvider.java new file mode 100644 index 0000000000..2bc9a093f2 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/CacheMeterBinderProvider.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2018 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.cache; + +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.binder.MeterBinder; + +import org.springframework.cache.Cache; + +/** + * Provide a {@link MeterBinder} based on a {@link Cache}. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +public interface CacheMeterBinderProvider { + + /** + * Return the {@link MeterBinder} managing the specified {@link Cache} or {@code null} + * if the specified {@link Cache} is not supported. + * @param cache the cache to instrument + * @param name the name prefix of the metrics + * @param tags tags to apply to all recorded metrics + * @return a {@link MeterBinder} handling the specified {@link Cache} or {@code null} + */ + MeterBinder getMeterBinder(Cache cache, String name, Iterable tags); + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/CacheMeterBinderProviders.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/CacheMeterBinderProviders.java new file mode 100644 index 0000000000..15b2ea9a6a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/cache/CacheMeterBinderProviders.java @@ -0,0 +1,104 @@ +/* + * Copyright 2012-2018 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.cache; + +import com.hazelcast.core.IMap; +import com.hazelcast.spring.cache.HazelcastCache; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.binder.MeterBinder; +import io.micrometer.core.instrument.binder.cache.CaffeineCacheMetrics; +import io.micrometer.core.instrument.binder.cache.EhCache2Metrics; +import io.micrometer.core.instrument.binder.cache.HazelcastCacheMetrics; +import io.micrometer.core.instrument.binder.cache.JCacheMetrics; + +import org.springframework.cache.Cache; +import org.springframework.cache.caffeine.CaffeineCache; +import org.springframework.cache.ehcache.EhCacheCache; +import org.springframework.cache.jcache.JCacheCache; + +/** + * Common {@link CacheMeterBinderProvider} implementations. + * + * @author Stephane Nicoll + * @since 2.0.0 + */ +public abstract class CacheMeterBinderProviders { + + /** + * {@link CacheMeterBinderProvider} implementation for Caffeine. + */ + public static class CaffeineCacheMeterBinderProvider + implements CacheMeterBinderProvider { + + @Override + public MeterBinder getMeterBinder(Cache cache, String name, Iterable tags) { + if (cache instanceof CaffeineCache) { + return new CaffeineCacheMetrics( + ((CaffeineCache) cache).getNativeCache(), tags, name); + } + return null; + } + } + + /** + * {@link CacheMeterBinderProvider} implementation for EhCache2. + */ + public static class EhCache2CacheMeterBinderProvider + implements CacheMeterBinderProvider { + + @Override + public MeterBinder getMeterBinder(Cache cache, String name, Iterable tags) { + if (cache instanceof EhCacheCache) { + return new EhCache2Metrics(((EhCacheCache) cache).getNativeCache(), + name, tags); + } + return null; + } + } + + /** + * {@link CacheMeterBinderProvider} implementation for Hazelcast. + */ + public static class HazelcastCacheMeterBinderProvider + implements CacheMeterBinderProvider { + + @Override + public MeterBinder getMeterBinder(Cache cache, String name, Iterable tags) { + if (cache instanceof HazelcastCache) { + IMap nativeCache = (IMap) ((HazelcastCache) cache).getNativeCache(); + return new HazelcastCacheMetrics(nativeCache, name, tags); + } + return null; + } + } + + /** + * {@link CacheMeterBinderProvider} implementation for JCache. + */ + public static class JCacheCacheMeterBinderProvider + implements CacheMeterBinderProvider { + @Override + public MeterBinder getMeterBinder(Cache cache, String name, Iterable tags) { + if (cache instanceof JCacheCache) { + return new JCacheMetrics(((JCacheCache) cache).getNativeCache(), + name, tags); + } + return null; + } + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/CacheMeterBinderProvidersTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/CacheMeterBinderProvidersTests.java new file mode 100644 index 0000000000..424531b0d0 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/cache/CacheMeterBinderProvidersTests.java @@ -0,0 +1,135 @@ +/* + * Copyright 2012-2018 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.cache; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collections; + +import com.github.benmanes.caffeine.cache.Caffeine; +import com.hazelcast.core.IMap; +import com.hazelcast.spring.cache.HazelcastCache; +import io.micrometer.core.instrument.binder.MeterBinder; +import io.micrometer.core.instrument.binder.cache.CaffeineCacheMetrics; +import io.micrometer.core.instrument.binder.cache.EhCache2Metrics; +import io.micrometer.core.instrument.binder.cache.HazelcastCacheMetrics; +import io.micrometer.core.instrument.binder.cache.JCacheMetrics; +import net.sf.ehcache.Cache; +import net.sf.ehcache.CacheManager; +import net.sf.ehcache.config.CacheConfiguration; +import net.sf.ehcache.config.Configuration; +import org.junit.Test; + +import org.springframework.boot.actuate.metrics.cache.CacheMeterBinderProviders.CaffeineCacheMeterBinderProvider; +import org.springframework.boot.actuate.metrics.cache.CacheMeterBinderProviders.EhCache2CacheMeterBinderProvider; +import org.springframework.boot.actuate.metrics.cache.CacheMeterBinderProviders.HazelcastCacheMeterBinderProvider; +import org.springframework.boot.actuate.metrics.cache.CacheMeterBinderProviders.JCacheCacheMeterBinderProvider; +import org.springframework.cache.caffeine.CaffeineCache; +import org.springframework.cache.concurrent.ConcurrentMapCache; +import org.springframework.cache.ehcache.EhCacheCache; +import org.springframework.cache.jcache.JCacheCache; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link CacheMeterBinderProviders}. + * + * @author Stephane Nicoll + */ +public class CacheMeterBinderProvidersTests { + + @Test + public void caffeineCacheProvider() { + CaffeineCache cache = new CaffeineCache("test", Caffeine.newBuilder().build()); + MeterBinder meterBinder = new CaffeineCacheMeterBinderProvider().getMeterBinder( + cache, "test", Collections.EMPTY_LIST); + assertThat(meterBinder).isInstanceOf(CaffeineCacheMetrics.class); + } + + @Test + public void caffeineCacheProviderWithUnsupportedCache() { + MeterBinder meterBinder = new CaffeineCacheMeterBinderProvider().getMeterBinder( + new ConcurrentMapCache("test"), "test", Collections.EMPTY_LIST); + assertThat(meterBinder).isNull(); + } + + @Test + public void ehCache2CacheProvider() { + CacheManager cacheManager = new CacheManager( + new Configuration().name("EhCacheCacheTests").defaultCache( + new CacheConfiguration("default", 100))); + try { + Cache nativeCache = new Cache( + new CacheConfiguration("test", 100)); + cacheManager.addCache(nativeCache); + EhCacheCache cache = new EhCacheCache(nativeCache); + MeterBinder meterBinder = new EhCache2CacheMeterBinderProvider().getMeterBinder( + cache, "test", Collections.EMPTY_LIST); + assertThat(meterBinder).isInstanceOf(EhCache2Metrics.class); + } + finally { + cacheManager.shutdown(); + } + } + + @Test + public void ehCache2CacheProviderWithUnsupportedCache() { + MeterBinder meterBinder = new EhCache2CacheMeterBinderProvider().getMeterBinder( + new ConcurrentMapCache("test"), "test", Collections.EMPTY_LIST); + assertThat(meterBinder).isNull(); + } + + @Test + public void hazelcastCacheProvider() { + IMap nativeCache = mock(IMap.class); + given(nativeCache.getName()).willReturn("test"); + HazelcastCache cache = new HazelcastCache(nativeCache); + MeterBinder meterBinder = new HazelcastCacheMeterBinderProvider().getMeterBinder( + cache, "test", Collections.EMPTY_LIST); + assertThat(meterBinder).isInstanceOf(HazelcastCacheMetrics.class); + } + + @Test + public void hazelcastCacheProviderWithUnsupportedCache() { + MeterBinder meterBinder = new HazelcastCacheMeterBinderProvider().getMeterBinder( + new ConcurrentMapCache("test"), "test", Collections.EMPTY_LIST); + assertThat(meterBinder).isNull(); + } + + @Test + public void jCacheCacheProvider() throws URISyntaxException { + javax.cache.CacheManager cacheManager = mock(javax.cache.CacheManager.class); + given(cacheManager.getURI()).willReturn(new URI("/test")); + javax.cache.Cache nativeCache = mock(javax.cache.Cache.class); + given(nativeCache.getCacheManager()).willReturn(cacheManager); + given(nativeCache.getName()).willReturn("test"); + JCacheCache cache = new JCacheCache(nativeCache); + MeterBinder meterBinder = new JCacheCacheMeterBinderProvider().getMeterBinder( + cache, "test", Collections.EMPTY_LIST); + assertThat(meterBinder).isInstanceOf(JCacheMetrics.class); + } + + @Test + public void jCacheCacheWithUnsupportedCache() { + MeterBinder meterBinder = new JCacheCacheMeterBinderProvider().getMeterBinder( + new ConcurrentMapCache("test"), "test", Collections.EMPTY_LIST); + assertThat(meterBinder).isNull(); + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 9fb3b078b9..108efcd43d 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -1256,6 +1256,8 @@ content into your application. Rather, pick only the properties that you need. management.metrics.binders.logback.enabled=true # Whether to enable Logback metrics. management.metrics.binders.processor.enabled=true # Whether to enable processor metrics. management.metrics.binders.uptime.enabled=true # Whether to enable uptime metrics. + management.metrics.cache.cache-metric-name=cache # Name of the metric for cache usage. + management.metrics.cache.instrument-cache=true # Instrument all available caches. management.metrics.export.atlas.batch-size= # Number of measurements per request to use for the backend. If more measurements are found, then multiple requests will be made. management.metrics.export.atlas.config-refresh-frequency= # Frequency for refreshing config settings from the LWC service. management.metrics.export.atlas.config-time-to-live= # Time to live for subscriptions from the LWC service. diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc index 31a6480e34..48af0c0396 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/production-ready-features.adoc @@ -1017,6 +1017,30 @@ following information: +[[production-ready-metrics-cache]] +=== Cache metrics +Auto-configuration will enable the instrumentation of all available ``Cache``s on startup +with a metric named `cache`. The prefix can be customized by using the +`management.metrics.cache.cache-metric-name` property. Cache instrumentation is specific +to each cache library, refer to https://micrometer.io/docs[the micrometer documentation] +for more details. + +The following cache libraries are supported: + +* Caffeine +* EhCache 2 +* Hazelcast +* Any compliant JCache (JSR-107) implementation + +Metrics will also be tagged by the name of the `CacheManager` computed based on the bean +name. + +NOTE: Only caches that are available on startup are bound to the registry. For caches +created on-the-fly or programmatically after the startup phase, an explicit registration +is required. A `CacheMetricsRegistrar` bean is made available to make that process easier. + + + [[production-ready-metrics-jdbc]] === DataSource metrics Auto-configuration will enable the instrumentation of all available ``DataSource``s with a