From 18d7634947a1f7d89c0cb74795a95f4f4623ea2e Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 1 Jun 2015 17:41:59 +0200 Subject: [PATCH] Add basic cache metrics support for Infinispan Closes gh-3066 --- spring-boot-actuator/pom.xml | 5 ++ .../CacheStatisticsAutoConfiguration.java | 19 +++- ...> AbstractJmxCacheStatisticsProvider.java} | 90 ++++++++++--------- .../InfinispanCacheStatisticsProvider.java | 63 +++++++++++++ .../cache/JCacheCacheStatisticsProvider.java | 61 +++++++++++++ ...CacheStatisticsAutoConfigurationTests.java | 54 ++++++++--- .../test/resources/cache/test-infinispan.xml | 9 ++ .../src/main/resources/infinispan.xml | 2 +- 8 files changed, 247 insertions(+), 56 deletions(-) rename spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cache/{JCacheStatisticsProvider.java => AbstractJmxCacheStatisticsProvider.java} (53%) create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cache/InfinispanCacheStatisticsProvider.java create mode 100644 spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cache/JCacheCacheStatisticsProvider.java create mode 100644 spring-boot-actuator/src/test/resources/cache/test-infinispan.xml diff --git a/spring-boot-actuator/pom.xml b/spring-boot-actuator/pom.xml index 932748cc70..a61dbf5925 100644 --- a/spring-boot-actuator/pom.xml +++ b/spring-boot-actuator/pom.xml @@ -97,6 +97,11 @@ hibernate-validator true + + org.infinispan + infinispan-spring4 + true + org.springframework spring-messaging diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/CacheStatisticsAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/CacheStatisticsAutoConfiguration.java index 4719ea719d..b985f6ff58 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/CacheStatisticsAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/CacheStatisticsAutoConfiguration.java @@ -21,6 +21,7 @@ import javax.cache.Caching; import com.hazelcast.core.IMap; import com.hazelcast.spring.cache.HazelcastCache; import net.sf.ehcache.Ehcache; +import org.infinispan.spring.provider.SpringCache; import org.springframework.boot.actuate.cache.CacheStatistics; import org.springframework.boot.actuate.cache.CacheStatisticsProvider; @@ -29,7 +30,8 @@ import org.springframework.boot.actuate.cache.DefaultCacheStatistics; import org.springframework.boot.actuate.cache.EhCacheStatisticsProvider; import org.springframework.boot.actuate.cache.GuavaCacheStatisticsProvider; import org.springframework.boot.actuate.cache.HazelcastCacheStatisticsProvider; -import org.springframework.boot.actuate.cache.JCacheStatisticsProvider; +import org.springframework.boot.actuate.cache.InfinispanCacheStatisticsProvider; +import org.springframework.boot.actuate.cache.JCacheCacheStatisticsProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration; @@ -61,8 +63,8 @@ public class CacheStatisticsAutoConfiguration { static class JCacheCacheStatisticsProviderConfiguration { @Bean - public JCacheStatisticsProvider jCacheStatisticsProvider() { - return new JCacheStatisticsProvider(); + public JCacheCacheStatisticsProvider jCacheCacheStatisticsProvider() { + return new JCacheCacheStatisticsProvider(); } } @@ -88,6 +90,17 @@ public class CacheStatisticsAutoConfiguration { } } + @Configuration + @ConditionalOnClass({ SpringCache.class }) + static class InfinispanCacheStatisticsProviderConfiguration { + + @Bean + public InfinispanCacheStatisticsProvider infinispanCacheStatisticsProvider() { + return new InfinispanCacheStatisticsProvider(); + } + + } + @Configuration @ConditionalOnClass(com.google.common.cache.Cache.class) static class GuavaCacheStatisticsConfiguration { diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cache/JCacheStatisticsProvider.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cache/AbstractJmxCacheStatisticsProvider.java similarity index 53% rename from spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cache/JCacheStatisticsProvider.java rename to spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cache/AbstractJmxCacheStatisticsProvider.java index 8d1c619ca2..d7f6668194 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cache/JCacheStatisticsProvider.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cache/AbstractJmxCacheStatisticsProvider.java @@ -18,42 +18,44 @@ package org.springframework.boot.actuate.cache; import java.lang.management.ManagementFactory; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; - import javax.management.AttributeNotFoundException; import javax.management.InstanceNotFoundException; import javax.management.MBeanException; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; -import javax.management.ObjectInstance; import javax.management.ObjectName; import javax.management.ReflectionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; -import org.springframework.cache.jcache.JCacheCache; /** - * {@link CacheStatisticsProvider} implementation for a JSR-107 compliant cache. + * Base {@link CacheStatisticsProvider} implementation that uses JMX to + * retrieve the cache statistics. * * @author Stephane Nicoll * @since 1.3.0 */ -public class JCacheStatisticsProvider implements CacheStatisticsProvider { +public abstract class AbstractJmxCacheStatisticsProvider + implements CacheStatisticsProvider { private static final Logger logger = LoggerFactory - .getLogger(JCacheStatisticsProvider.class); + .getLogger(AbstractJmxCacheStatisticsProvider.class); private MBeanServer mBeanServer; - private Map caches = new ConcurrentHashMap(); + private Map caches = + new ConcurrentHashMap(); + @Override - public CacheStatistics getCacheStatistics(CacheManager cacheManager, JCacheCache cache) { + public CacheStatistics getCacheStatistics(CacheManager cacheManager, C cache) { try { - ObjectName objectName = getObjectName(cache); + ObjectName objectName = internalGetObjectName(cache); if (objectName != null) { return getCacheStatistics(objectName); } @@ -64,35 +66,33 @@ public class JCacheStatisticsProvider implements CacheStatisticsProvider 0 || missPercentage > 0)) { - statistics.setHitRatio(hitPercentage / (double) 100); - statistics.setMissRatio(missPercentage / (double) 100); - } - return statistics; - } + /** + * Return the {@link ObjectName} of the MBean that is managing the specified cache or + * {@code null} if none is found. + * @param cache the cache to handle + * @return the object name of the cache statistics MBean + */ + protected abstract ObjectName getObjectName(C cache) throws MalformedObjectNameException; - protected ObjectName getObjectName(JCacheCache cache) + /** + * Return the current {@link CacheStatistics} snapshot from the MBean identified by the + * specified {@link ObjectName}. + * @param objectName the object name of the cache statistics MBean + * @return the current cache statistics + */ + protected abstract CacheStatistics getCacheStatistics(ObjectName objectName); + + + private ObjectName internalGetObjectName(C cache) throws MalformedObjectNameException { - if (this.caches.containsKey(cache)) { - return this.caches.get(cache); + String cacheName = cache.getName(); + ObjectNameWrapper value = this.caches.get(cacheName); + if (value != null) { + return value.objectName; } - Set instances = getMBeanServer().queryMBeans( - new ObjectName("javax.cache:type=CacheStatistics,Cache=" - + cache.getName() + ",*"), null); - if (instances.size() == 1) { - ObjectName objectName = instances.iterator().next().getObjectName(); - this.caches.put(cache, objectName); - return objectName; - } - return null; // None or more than one + ObjectName objectName = getObjectName(cache); + this.caches.put(cacheName, new ObjectNameWrapper(objectName)); + return objectName; } protected MBeanServer getMBeanServer() { @@ -102,18 +102,17 @@ public class JCacheStatisticsProvider implements CacheStatisticsProvider T getAttribute(MBeanServer mBeanServer, ObjectName objectName, - String attributeName, Class type) { + protected T getAttribute(ObjectName objectName, String attributeName, Class type) { try { - Object attribute = mBeanServer.getAttribute(objectName, attributeName); + Object attribute = getMBeanServer().getAttribute(objectName, attributeName); return type.cast(attribute); } catch (MBeanException ex) { throw new IllegalStateException(ex); } catch (AttributeNotFoundException ex) { - throw new IllegalStateException("Unexpected: jcache provider does not " - + "expose standard attribute " + attributeName, ex); + throw new IllegalStateException("Unexpected: MBean with name '" + objectName + "' " + + "does not expose attribute with name " + attributeName, ex); } catch (ReflectionException ex) { throw new IllegalStateException(ex); @@ -124,4 +123,13 @@ public class JCacheStatisticsProvider implements CacheStatisticsProvider { + + @Override + protected ObjectName getObjectName(SpringCache cache) throws MalformedObjectNameException { + Set instances = getMBeanServer().queryMBeans( + new ObjectName("org.infinispan:component=Statistics,type=Cache," + + "name=\"" + cache.getName() + "(local)\",*"), null); + if (instances.size() == 1) { + return instances.iterator().next().getObjectName(); + } + return null; // None or more than one + } + + protected CacheStatistics getCacheStatistics(ObjectName objectName) { + DefaultCacheStatistics statistics = new DefaultCacheStatistics(); + Integer size = getAttribute(objectName, "numberOfEntries", + Integer.class); + if (size != null) { + statistics.setSize((long) size); + if (size > 0) { // Let's initialize the stats if we have some data + Double hitRatio = getAttribute(objectName, "hitRatio", + Double.class); + if ((hitRatio != null)) { + statistics.setHitRatio(hitRatio); + statistics.setMissRatio(1 - hitRatio); + } + } + } + return statistics; + } + +} diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cache/JCacheCacheStatisticsProvider.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cache/JCacheCacheStatisticsProvider.java new file mode 100644 index 0000000000..dce29a125e --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cache/JCacheCacheStatisticsProvider.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-2015 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.cache; + +import java.util.Set; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectInstance; +import javax.management.ObjectName; + +import org.springframework.cache.jcache.JCacheCache; + +/** + * {@link CacheStatisticsProvider} implementation for a JSR-107 compliant cache. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +public class JCacheCacheStatisticsProvider extends AbstractJmxCacheStatisticsProvider { + + + protected ObjectName getObjectName(JCacheCache cache) + throws MalformedObjectNameException { + + Set instances = getMBeanServer().queryMBeans( + new ObjectName("javax.cache:type=CacheStatistics,Cache=" + + cache.getName() + ",*"), null); + if (instances.size() == 1) { + return instances.iterator().next().getObjectName(); + } + return null; // None or more than one + } + + protected CacheStatistics getCacheStatistics(ObjectName objectName) { + DefaultCacheStatistics statistics = new DefaultCacheStatistics(); + Float hitPercentage = getAttribute(objectName, "CacheHitPercentage", + Float.class); + Float missPercentage = getAttribute(objectName, + "CacheMissPercentage", Float.class); + if ((hitPercentage != null && missPercentage != null) + && (hitPercentage > 0 || missPercentage > 0)) { + statistics.setHitRatio(hitPercentage / (double) 100); + statistics.setMissRatio(missPercentage / (double) 100); + } + return statistics; + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CacheStatisticsAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CacheStatisticsAutoConfigurationTests.java index c3128b72ca..cec8aa3a47 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CacheStatisticsAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CacheStatisticsAutoConfigurationTests.java @@ -17,14 +17,24 @@ package org.springframework.boot.actuate.autoconfigure; import java.io.IOException; +import java.io.InputStream; import java.util.Arrays; - import javax.cache.Caching; import javax.cache.configuration.MutableConfiguration; +import com.google.common.cache.CacheBuilder; +import com.hazelcast.cache.HazelcastCachingProvider; +import com.hazelcast.config.Config; +import com.hazelcast.config.XmlConfigBuilder; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.spring.cache.HazelcastCacheManager; +import org.infinispan.manager.DefaultCacheManager; +import org.infinispan.manager.EmbeddedCacheManager; +import org.infinispan.spring.provider.SpringEmbeddedCacheManager; import org.junit.After; import org.junit.Test; -import org.springframework.boot.actuate.autoconfigure.CacheStatisticsAutoConfiguration; + import org.springframework.boot.actuate.cache.CacheStatistics; import org.springframework.boot.actuate.cache.CacheStatisticsProvider; import org.springframework.cache.Cache; @@ -42,14 +52,6 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.util.Assert; -import com.google.common.cache.CacheBuilder; -import com.hazelcast.cache.HazelcastCachingProvider; -import com.hazelcast.config.Config; -import com.hazelcast.config.XmlConfigBuilder; -import com.hazelcast.core.Hazelcast; -import com.hazelcast.core.HazelcastInstance; -import com.hazelcast.spring.cache.HazelcastCacheManager; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -76,7 +78,7 @@ public class CacheStatisticsAutoConfigurationTests { public void basicJCacheCacheStatistics() { load(JCacheCacheConfig.class); CacheStatisticsProvider provider = this.context.getBean( - "jCacheStatisticsProvider", CacheStatisticsProvider.class); + "jCacheCacheStatisticsProvider", CacheStatisticsProvider.class); doTestCoreStatistics(provider, false); } @@ -96,6 +98,14 @@ public class CacheStatisticsAutoConfigurationTests { doTestCoreStatistics(provider, true); } + @Test + public void basicInfinispanCacheStatistics() { + load(InfinispanConfig.class); + CacheStatisticsProvider provider = this.context.getBean( + "infinispanCacheStatisticsProvider", CacheStatisticsProvider.class); + doTestCoreStatistics(provider, true); + } + @Test public void basicGuavaCacheStatistics() { load(GuavaConfig.class); @@ -246,6 +256,28 @@ public class CacheStatisticsAutoConfigurationTests { } + @Configuration + static class InfinispanConfig { + + @Bean + public SpringEmbeddedCacheManager cacheManager() throws IOException { + return new SpringEmbeddedCacheManager(embeddedCacheManager()); + } + + @Bean + public EmbeddedCacheManager embeddedCacheManager() throws IOException { + Resource resource = new ClassPathResource("cache/test-infinispan.xml"); + InputStream in = resource.getInputStream(); + try { + return new DefaultCacheManager(in); + } + finally { + in.close(); + } + } + + } + @Configuration static class GuavaConfig { diff --git a/spring-boot-actuator/src/test/resources/cache/test-infinispan.xml b/spring-boot-actuator/src/test/resources/cache/test-infinispan.xml new file mode 100644 index 0000000000..f23c3e736a --- /dev/null +++ b/spring-boot-actuator/src/test/resources/cache/test-infinispan.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/spring-boot-samples/spring-boot-sample-cache/src/main/resources/infinispan.xml b/spring-boot-samples/spring-boot-sample-cache/src/main/resources/infinispan.xml index acf90677df..0dc58a3b48 100644 --- a/spring-boot-samples/spring-boot-sample-cache/src/main/resources/infinispan.xml +++ b/spring-boot-samples/spring-boot-sample-cache/src/main/resources/infinispan.xml @@ -2,7 +2,7 @@ - +