Move code from spring-boot-actuator to spring-boot-cache
This commit is contained in:
committed by
Phillip Webb
parent
302a4212b5
commit
adec7f2df2
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.metrics;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import com.redis.testcontainers.RedisContainer;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.cache.autoconfigure.CacheAutoConfiguration;
|
||||
import org.springframework.boot.data.redis.autoconfigure.RedisAutoConfiguration;
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||
import org.springframework.boot.testsupport.container.TestImage;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.cache.RedisCache;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link RedisCacheMetrics}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@Testcontainers(disabledWithoutDocker = true)
|
||||
class RedisCacheMetricsTests {
|
||||
|
||||
@Container
|
||||
static final RedisContainer redis = TestImage.container(RedisContainer.class);
|
||||
|
||||
private static final Tags TAGS = Tags.of("app", "test").and("cache", "test");
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class, CacheAutoConfiguration.class))
|
||||
.withUserConfiguration(CachingConfiguration.class)
|
||||
.withPropertyValues("spring.data.redis.host=" + redis.getHost(),
|
||||
"spring.data.redis.port=" + redis.getFirstMappedPort(), "spring.cache.type=redis",
|
||||
"spring.cache.redis.enable-statistics=true");
|
||||
|
||||
@Test
|
||||
void cacheStatisticsAreExposed() {
|
||||
this.contextRunner.run(withCacheMetrics((cache, meterRegistry) -> {
|
||||
assertThat(meterRegistry.find("cache.size").tags(TAGS).functionCounter()).isNull();
|
||||
assertThat(meterRegistry.find("cache.gets").tags(TAGS.and("result", "hit")).functionCounter()).isNotNull();
|
||||
assertThat(meterRegistry.find("cache.gets").tags(TAGS.and("result", "miss")).functionCounter()).isNotNull();
|
||||
assertThat(meterRegistry.find("cache.gets").tags(TAGS.and("result", "pending")).functionCounter())
|
||||
.isNotNull();
|
||||
assertThat(meterRegistry.find("cache.evictions").tags(TAGS).functionCounter()).isNull();
|
||||
assertThat(meterRegistry.find("cache.puts").tags(TAGS).functionCounter()).isNotNull();
|
||||
assertThat(meterRegistry.find("cache.removals").tags(TAGS).functionCounter()).isNotNull();
|
||||
assertThat(meterRegistry.find("cache.lock.duration").tags(TAGS).timeGauge()).isNotNull();
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void cacheHitsAreExposed() {
|
||||
this.contextRunner.run(withCacheMetrics((cache, meterRegistry) -> {
|
||||
String key = UUID.randomUUID().toString();
|
||||
cache.put(key, "test");
|
||||
|
||||
cache.get(key);
|
||||
cache.get(key);
|
||||
assertThat(meterRegistry.get("cache.gets").tags(TAGS.and("result", "hit")).functionCounter().count())
|
||||
.isEqualTo(2.0d);
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void cacheMissesAreExposed() {
|
||||
this.contextRunner.run(withCacheMetrics((cache, meterRegistry) -> {
|
||||
String key = UUID.randomUUID().toString();
|
||||
cache.get(key);
|
||||
cache.get(key);
|
||||
cache.get(key);
|
||||
assertThat(meterRegistry.get("cache.gets").tags(TAGS.and("result", "miss")).functionCounter().count())
|
||||
.isEqualTo(3.0d);
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void cacheMetricsMatchCacheStatistics() {
|
||||
this.contextRunner.run((context) -> {
|
||||
RedisCache cache = getTestCache(context);
|
||||
RedisCacheMetrics cacheMetrics = new RedisCacheMetrics(cache, TAGS);
|
||||
assertThat(cacheMetrics.hitCount()).isEqualTo(cache.getStatistics().getHits());
|
||||
assertThat(cacheMetrics.missCount()).isEqualTo(cache.getStatistics().getMisses());
|
||||
assertThat(cacheMetrics.putCount()).isEqualTo(cache.getStatistics().getPuts());
|
||||
assertThat(cacheMetrics.size()).isNull();
|
||||
assertThat(cacheMetrics.evictionCount()).isNull();
|
||||
});
|
||||
}
|
||||
|
||||
private ContextConsumer<AssertableApplicationContext> withCacheMetrics(
|
||||
BiConsumer<RedisCache, MeterRegistry> stats) {
|
||||
return (context) -> {
|
||||
RedisCache cache = getTestCache(context);
|
||||
SimpleMeterRegistry meterRegistry = new SimpleMeterRegistry();
|
||||
new RedisCacheMetrics(cache, Tags.of("app", "test")).bindTo(meterRegistry);
|
||||
stats.accept(cache, meterRegistry);
|
||||
};
|
||||
}
|
||||
|
||||
private RedisCache getTestCache(AssertableApplicationContext context) {
|
||||
assertThat(context).hasSingleBean(RedisCacheManager.class);
|
||||
RedisCacheManager cacheManager = context.getBean(RedisCacheManager.class);
|
||||
RedisCache cache = (RedisCache) cacheManager.getCache("test");
|
||||
assertThat(cache).isNotNull();
|
||||
return cache;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableCaching
|
||||
static class CachingConfiguration {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.endpoint;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.OperationResponseBody;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.OptionalParameter;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
|
||||
/**
|
||||
* {@link Endpoint @Endpoint} to expose available {@link Cache caches}.
|
||||
*
|
||||
* @author Johannes Edmeier
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@Endpoint(id = "caches")
|
||||
public class CachesEndpoint {
|
||||
|
||||
private final Map<String, CacheManager> cacheManagers;
|
||||
|
||||
/**
|
||||
* Create a new endpoint with the {@link CacheManager} instances to use.
|
||||
* @param cacheManagers the cache managers to use, indexed by name
|
||||
*/
|
||||
public CachesEndpoint(Map<String, CacheManager> cacheManagers) {
|
||||
this.cacheManagers = new LinkedHashMap<>(cacheManagers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link CachesDescriptor} of all available {@link Cache caches}.
|
||||
* @return a caches reports
|
||||
*/
|
||||
@ReadOperation
|
||||
public CachesDescriptor caches() {
|
||||
Map<String, Map<String, CacheDescriptor>> descriptors = new LinkedHashMap<>();
|
||||
getCacheEntries(matchAll(), matchAll()).forEach((entry) -> {
|
||||
String cacheName = entry.getName();
|
||||
String cacheManager = entry.getCacheManager();
|
||||
Map<String, CacheDescriptor> cacheManagerDescriptors = descriptors.computeIfAbsent(cacheManager,
|
||||
(key) -> new LinkedHashMap<>());
|
||||
cacheManagerDescriptors.put(cacheName, new CacheDescriptor(entry.getTarget()));
|
||||
});
|
||||
Map<String, CacheManagerDescriptor> cacheManagerDescriptors = new LinkedHashMap<>();
|
||||
descriptors.forEach((name, entries) -> cacheManagerDescriptors.put(name, new CacheManagerDescriptor(entries)));
|
||||
return new CachesDescriptor(cacheManagerDescriptors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link CacheDescriptor} for the specified cache.
|
||||
* @param cache the name of the cache
|
||||
* @param cacheManager the name of the cacheManager (can be {@code null}
|
||||
* @return the descriptor of the cache or {@code null} if no such cache exists
|
||||
* @throws NonUniqueCacheException if more than one cache with that name exists and no
|
||||
* {@code cacheManager} was provided to identify a unique candidate
|
||||
*/
|
||||
@ReadOperation
|
||||
public CacheEntryDescriptor cache(@Selector String cache, @OptionalParameter String cacheManager) {
|
||||
return extractUniqueCacheEntry(cache, getCacheEntries((name) -> name.equals(cache), isNameMatch(cacheManager)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the available {@link Cache caches}.
|
||||
*/
|
||||
@DeleteOperation
|
||||
public void clearCaches() {
|
||||
getCacheEntries(matchAll(), matchAll()).forEach(this::clearCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the specific {@link Cache}.
|
||||
* @param cache the name of the cache
|
||||
* @param cacheManager the name of the cacheManager (can be {@code null} to match all)
|
||||
* @return {@code true} if the cache was cleared or {@code false} if no such cache
|
||||
* exists
|
||||
* @throws NonUniqueCacheException if more than one cache with that name exists and no
|
||||
* {@code cacheManager} was provided to identify a unique candidate
|
||||
*/
|
||||
@DeleteOperation
|
||||
public boolean clearCache(@Selector String cache, @OptionalParameter String cacheManager) {
|
||||
CacheEntryDescriptor entry = extractUniqueCacheEntry(cache,
|
||||
getCacheEntries((name) -> name.equals(cache), isNameMatch(cacheManager)));
|
||||
return (entry != null && clearCache(entry));
|
||||
}
|
||||
|
||||
private List<CacheEntryDescriptor> getCacheEntries(Predicate<String> cacheNamePredicate,
|
||||
Predicate<String> cacheManagerNamePredicate) {
|
||||
return this.cacheManagers.keySet()
|
||||
.stream()
|
||||
.filter(cacheManagerNamePredicate)
|
||||
.flatMap((cacheManagerName) -> getCacheEntries(cacheManagerName, cacheNamePredicate).stream())
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<CacheEntryDescriptor> getCacheEntries(String cacheManagerName, Predicate<String> cacheNamePredicate) {
|
||||
CacheManager cacheManager = this.cacheManagers.get(cacheManagerName);
|
||||
return cacheManager.getCacheNames()
|
||||
.stream()
|
||||
.filter(cacheNamePredicate)
|
||||
.map(cacheManager::getCache)
|
||||
.filter(Objects::nonNull)
|
||||
.map((cache) -> new CacheEntryDescriptor(cache, cacheManagerName))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private CacheEntryDescriptor extractUniqueCacheEntry(String cache, List<CacheEntryDescriptor> entries) {
|
||||
if (entries.size() > 1) {
|
||||
throw new NonUniqueCacheException(cache,
|
||||
entries.stream().map(CacheEntryDescriptor::getCacheManager).distinct().toList());
|
||||
}
|
||||
return (!entries.isEmpty() ? entries.get(0) : null);
|
||||
}
|
||||
|
||||
private boolean clearCache(CacheEntryDescriptor entry) {
|
||||
String cacheName = entry.getName();
|
||||
String cacheManager = entry.getCacheManager();
|
||||
Cache cache = this.cacheManagers.get(cacheManager).getCache(cacheName);
|
||||
if (cache != null) {
|
||||
cache.clear();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Predicate<String> isNameMatch(String name) {
|
||||
return (name != null) ? ((requested) -> requested.equals(name)) : matchAll();
|
||||
}
|
||||
|
||||
private Predicate<String> matchAll() {
|
||||
return (name) -> true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description of the caches.
|
||||
*/
|
||||
public static final class CachesDescriptor implements OperationResponseBody {
|
||||
|
||||
private final Map<String, CacheManagerDescriptor> cacheManagers;
|
||||
|
||||
public CachesDescriptor(Map<String, CacheManagerDescriptor> cacheManagers) {
|
||||
this.cacheManagers = cacheManagers;
|
||||
}
|
||||
|
||||
public Map<String, CacheManagerDescriptor> getCacheManagers() {
|
||||
return this.cacheManagers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Description of a {@link CacheManager}.
|
||||
*/
|
||||
public static final class CacheManagerDescriptor {
|
||||
|
||||
private final Map<String, CacheDescriptor> caches;
|
||||
|
||||
public CacheManagerDescriptor(Map<String, CacheDescriptor> caches) {
|
||||
this.caches = caches;
|
||||
}
|
||||
|
||||
public Map<String, CacheDescriptor> getCaches() {
|
||||
return this.caches;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Description of a {@link Cache}.
|
||||
*/
|
||||
public static class CacheDescriptor implements OperationResponseBody {
|
||||
|
||||
private final String target;
|
||||
|
||||
public CacheDescriptor(String target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fully qualified name of the native cache.
|
||||
* @return the fully qualified name of the native cache
|
||||
*/
|
||||
public String getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Description of a {@link Cache} entry.
|
||||
*/
|
||||
public static final class CacheEntryDescriptor extends CacheDescriptor {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String cacheManager;
|
||||
|
||||
public CacheEntryDescriptor(Cache cache, String cacheManager) {
|
||||
super(cache.getNativeCache().getClass().getName());
|
||||
this.name = cache.getName();
|
||||
this.cacheManager = cacheManager;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getCacheManager() {
|
||||
return this.cacheManager;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.endpoint;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.OptionalParameter;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||
import org.springframework.boot.cache.actuate.endpoint.CachesEndpoint.CacheEntryDescriptor;
|
||||
|
||||
/**
|
||||
* {@link EndpointWebExtension @EndpointWebExtension} for the {@link CachesEndpoint}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@EndpointWebExtension(endpoint = CachesEndpoint.class)
|
||||
public class CachesEndpointWebExtension {
|
||||
|
||||
private final CachesEndpoint delegate;
|
||||
|
||||
public CachesEndpointWebExtension(CachesEndpoint delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public WebEndpointResponse<CacheEntryDescriptor> cache(@Selector String cache,
|
||||
@OptionalParameter String cacheManager) {
|
||||
try {
|
||||
CacheEntryDescriptor entry = this.delegate.cache(cache, cacheManager);
|
||||
int status = (entry != null) ? WebEndpointResponse.STATUS_OK : WebEndpointResponse.STATUS_NOT_FOUND;
|
||||
return new WebEndpointResponse<>(entry, status);
|
||||
}
|
||||
catch (NonUniqueCacheException ex) {
|
||||
return new WebEndpointResponse<>(WebEndpointResponse.STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteOperation
|
||||
public WebEndpointResponse<Void> clearCache(@Selector String cache, @OptionalParameter String cacheManager) {
|
||||
try {
|
||||
boolean cleared = this.delegate.clearCache(cache, cacheManager);
|
||||
int status = (cleared ? WebEndpointResponse.STATUS_NO_CONTENT : WebEndpointResponse.STATUS_NOT_FOUND);
|
||||
return new WebEndpointResponse<>(status);
|
||||
}
|
||||
catch (NonUniqueCacheException ex) {
|
||||
return new WebEndpointResponse<>(WebEndpointResponse.STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.endpoint;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Exception thrown when multiple caches exist with the same name.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class NonUniqueCacheException extends RuntimeException {
|
||||
|
||||
private final String cacheName;
|
||||
|
||||
private final Collection<String> cacheManagerNames;
|
||||
|
||||
public NonUniqueCacheException(String cacheName, Collection<String> cacheManagerNames) {
|
||||
super(String.format("Multiple caches named %s found, specify the 'cacheManager' to use: %s", cacheName,
|
||||
cacheManagerNames));
|
||||
this.cacheName = cacheName;
|
||||
this.cacheManagerNames = Collections.unmodifiableCollection(cacheManagerNames);
|
||||
}
|
||||
|
||||
public String getCacheName() {
|
||||
return this.cacheName;
|
||||
}
|
||||
|
||||
public Collection<String> getCacheManagerNames() {
|
||||
return this.cacheManagerNames;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Actuator endpoint for caches.
|
||||
*/
|
||||
package org.springframework.boot.cache.actuate.endpoint;
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.metrics;
|
||||
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
import org.cache2k.extra.micrometer.Cache2kCacheMetrics;
|
||||
import org.cache2k.extra.spring.SpringCache2kCache;
|
||||
|
||||
/**
|
||||
* {@link CacheMeterBinderProvider} implementation for cache2k.
|
||||
*
|
||||
* @author Jens Wilke
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class Cache2kCacheMeterBinderProvider implements CacheMeterBinderProvider<SpringCache2kCache> {
|
||||
|
||||
@Override
|
||||
public MeterBinder getMeterBinder(SpringCache2kCache cache, Iterable<Tag> tags) {
|
||||
return new Cache2kCacheMetrics(cache.getNativeCache(), tags);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.metrics;
|
||||
|
||||
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}.
|
||||
*
|
||||
* @param <C> the cache type
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CacheMeterBinderProvider<C extends Cache> {
|
||||
|
||||
/**
|
||||
* 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 tags tags to apply to all recorded metrics
|
||||
* @return a {@link MeterBinder} handling the specified {@link Cache} or {@code null}
|
||||
*/
|
||||
MeterBinder getMeterBinder(C cache, Iterable<Tag> tags);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.metrics;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
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.util.LambdaSafe;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Register supported {@link Cache} to a {@link MeterRegistry}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class CacheMetricsRegistrar {
|
||||
|
||||
private final MeterRegistry registry;
|
||||
|
||||
private final Collection<CacheMeterBinderProvider<?>> binderProviders;
|
||||
|
||||
/**
|
||||
* Creates a new registrar.
|
||||
* @param registry the {@link MeterRegistry} to use
|
||||
* @param binderProviders the {@link CacheMeterBinderProvider} instances that should
|
||||
* be used to detect compatible caches
|
||||
*/
|
||||
public CacheMetricsRegistrar(MeterRegistry registry, Collection<CacheMeterBinderProvider<?>> binderProviders) {
|
||||
this.registry = registry;
|
||||
this.binderProviders = 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) {
|
||||
MeterBinder meterBinder = getMeterBinder(unwrapIfNecessary(cache), Tags.of(tags));
|
||||
if (meterBinder != null) {
|
||||
meterBinder.bindTo(this.registry);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
private MeterBinder getMeterBinder(Cache cache, Tags tags) {
|
||||
Tags cacheTags = tags.and(getAdditionalTags(cache));
|
||||
return LambdaSafe.callbacks(CacheMeterBinderProvider.class, this.binderProviders, cache)
|
||||
.withLogger(CacheMetricsRegistrar.class)
|
||||
.invokeAnd((binderProvider) -> binderProvider.getMeterBinder(cache, cacheTags))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(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 Iterable<Tag> getAdditionalTags(Cache cache) {
|
||||
return Tags.of("name", cache.getName());
|
||||
}
|
||||
|
||||
private Cache unwrapIfNecessary(Cache cache) {
|
||||
if (ClassUtils.isPresent("org.springframework.cache.transaction.TransactionAwareCacheDecorator",
|
||||
getClass().getClassLoader())) {
|
||||
return TransactionAwareCacheDecoratorHandler.unwrapIfNecessary(cache);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
private static final class TransactionAwareCacheDecoratorHandler {
|
||||
|
||||
private static Cache unwrapIfNecessary(Cache cache) {
|
||||
try {
|
||||
if (cache instanceof TransactionAwareCacheDecorator decorator) {
|
||||
return decorator.getTargetCache();
|
||||
}
|
||||
}
|
||||
catch (NoClassDefFoundError ex) {
|
||||
// Ignore
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.metrics;
|
||||
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
import io.micrometer.core.instrument.binder.cache.CaffeineCacheMetrics;
|
||||
|
||||
import org.springframework.cache.caffeine.CaffeineCache;
|
||||
|
||||
/**
|
||||
* {@link CacheMeterBinderProvider} implementation for Caffeine.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class CaffeineCacheMeterBinderProvider implements CacheMeterBinderProvider<CaffeineCache> {
|
||||
|
||||
@Override
|
||||
public MeterBinder getMeterBinder(CaffeineCache cache, Iterable<Tag> tags) {
|
||||
return new CaffeineCacheMetrics<>(cache.getNativeCache(), cache.getName(), tags);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.metrics;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
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.HazelcastCacheMetrics;
|
||||
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
import org.springframework.boot.cache.actuate.metrics.HazelcastCacheMeterBinderProvider.HazelcastCacheMeterBinderProviderRuntimeHints;
|
||||
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* {@link CacheMeterBinderProvider} implementation for Hazelcast.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@ImportRuntimeHints(HazelcastCacheMeterBinderProviderRuntimeHints.class)
|
||||
public class HazelcastCacheMeterBinderProvider implements CacheMeterBinderProvider<HazelcastCache> {
|
||||
|
||||
@Override
|
||||
public MeterBinder getMeterBinder(HazelcastCache cache, Iterable<Tag> tags) {
|
||||
try {
|
||||
return new HazelcastCacheMetrics(cache.getNativeCache(), tags);
|
||||
}
|
||||
catch (NoSuchMethodError ex) {
|
||||
// Hazelcast 4
|
||||
return createHazelcast4CacheMetrics(cache, tags);
|
||||
}
|
||||
}
|
||||
|
||||
private MeterBinder createHazelcast4CacheMetrics(HazelcastCache cache, Iterable<Tag> tags) {
|
||||
try {
|
||||
Method nativeCacheAccessor = ReflectionUtils.findMethod(HazelcastCache.class, "getNativeCache");
|
||||
Object nativeCache = ReflectionUtils.invokeMethod(nativeCacheAccessor, cache);
|
||||
return HazelcastCacheMetrics.class.getConstructor(Object.class, Iterable.class)
|
||||
.newInstance(nativeCache, tags);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Failed to create MeterBinder for Hazelcast", ex);
|
||||
}
|
||||
}
|
||||
|
||||
static class HazelcastCacheMeterBinderProviderRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
try {
|
||||
Method getNativeCacheMethod = ReflectionUtils.findMethod(HazelcastCache.class, "getNativeCache");
|
||||
Assert.state(getNativeCacheMethod != null, "Unable to find 'getNativeCache' method");
|
||||
Constructor<?> constructor = HazelcastCacheMetrics.class.getConstructor(Object.class, Iterable.class);
|
||||
hints.reflection()
|
||||
.registerMethod(getNativeCacheMethod, ExecutableMode.INVOKE)
|
||||
.registerConstructor(constructor, ExecutableMode.INVOKE);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.metrics;
|
||||
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
import io.micrometer.core.instrument.binder.cache.JCacheMetrics;
|
||||
|
||||
import org.springframework.cache.jcache.JCacheCache;
|
||||
|
||||
/**
|
||||
* {@link CacheMeterBinderProvider} implementation for JCache.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class JCacheCacheMeterBinderProvider implements CacheMeterBinderProvider<JCacheCache> {
|
||||
|
||||
@Override
|
||||
public MeterBinder getMeterBinder(JCacheCache cache, Iterable<Tag> tags) {
|
||||
return new JCacheMetrics<>(cache.getNativeCache(), tags);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.metrics;
|
||||
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
|
||||
import org.springframework.data.redis.cache.RedisCache;
|
||||
|
||||
/**
|
||||
* {@link CacheMeterBinderProvider} implementation for Redis.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class RedisCacheMeterBinderProvider implements CacheMeterBinderProvider<RedisCache> {
|
||||
|
||||
@Override
|
||||
public MeterBinder getMeterBinder(RedisCache cache, Iterable<Tag> tags) {
|
||||
return new RedisCacheMetrics(cache, tags);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.metrics;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.micrometer.core.instrument.FunctionCounter;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.TimeGauge;
|
||||
import io.micrometer.core.instrument.binder.cache.CacheMeterBinder;
|
||||
|
||||
import org.springframework.data.redis.cache.RedisCache;
|
||||
|
||||
/**
|
||||
* {@link CacheMeterBinder} for {@link RedisCache}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.0.0
|
||||
*/
|
||||
public class RedisCacheMetrics extends CacheMeterBinder<RedisCache> {
|
||||
|
||||
private final RedisCache cache;
|
||||
|
||||
public RedisCacheMetrics(RedisCache cache, Iterable<Tag> tags) {
|
||||
super(cache, cache.getName(), tags);
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Long size() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long hitCount() {
|
||||
return this.cache.getStatistics().getHits();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Long missCount() {
|
||||
return this.cache.getStatistics().getMisses();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Long evictionCount() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long putCount() {
|
||||
return this.cache.getStatistics().getPuts();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void bindImplementationSpecificMetrics(MeterRegistry registry) {
|
||||
FunctionCounter.builder("cache.removals", this.cache, (cache) -> cache.getStatistics().getDeletes())
|
||||
.tags(getTagsWithCacheName())
|
||||
.description("Cache removals")
|
||||
.register(registry);
|
||||
FunctionCounter.builder("cache.gets", this.cache, (cache) -> cache.getStatistics().getPending())
|
||||
.tags(getTagsWithCacheName())
|
||||
.tag("result", "pending")
|
||||
.description("The number of pending requests")
|
||||
.register(registry);
|
||||
TimeGauge
|
||||
.builder("cache.lock.duration", this.cache, TimeUnit.NANOSECONDS,
|
||||
(cache) -> cache.getStatistics().getLockWaitDuration(TimeUnit.NANOSECONDS))
|
||||
.tags(getTagsWithCacheName())
|
||||
.description("The time the cache has spent waiting on a lock")
|
||||
.register(registry);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Metrics for caches.
|
||||
*/
|
||||
package org.springframework.boot.cache.actuate.metrics;
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.endpoint;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.cache.actuate.endpoint.CachesEndpoint.CacheEntryDescriptor;
|
||||
import org.springframework.boot.cache.actuate.endpoint.CachesEndpoint.CacheManagerDescriptor;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
|
||||
import org.springframework.cache.support.SimpleCacheManager;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
|
||||
/**
|
||||
* Tests for {@link CachesEndpoint}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class CachesEndpointTests {
|
||||
|
||||
@Test
|
||||
void allCachesWithSingleCacheManager() {
|
||||
CachesEndpoint endpoint = new CachesEndpoint(
|
||||
Collections.singletonMap("test", new ConcurrentMapCacheManager("a", "b")));
|
||||
Map<String, CacheManagerDescriptor> allDescriptors = endpoint.caches().getCacheManagers();
|
||||
assertThat(allDescriptors).containsOnlyKeys("test");
|
||||
CacheManagerDescriptor descriptors = allDescriptors.get("test");
|
||||
assertThat(descriptors.getCaches()).containsOnlyKeys("a", "b");
|
||||
assertThat(descriptors.getCaches().get("a").getTarget()).isEqualTo(ConcurrentHashMap.class.getName());
|
||||
assertThat(descriptors.getCaches().get("b").getTarget()).isEqualTo(ConcurrentHashMap.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void allCachesWithSeveralCacheManagers() {
|
||||
Map<String, CacheManager> cacheManagers = new LinkedHashMap<>();
|
||||
cacheManagers.put("test", new ConcurrentMapCacheManager("a", "b"));
|
||||
cacheManagers.put("another", new ConcurrentMapCacheManager("a", "c"));
|
||||
CachesEndpoint endpoint = new CachesEndpoint(cacheManagers);
|
||||
Map<String, CacheManagerDescriptor> allDescriptors = endpoint.caches().getCacheManagers();
|
||||
assertThat(allDescriptors).containsOnlyKeys("test", "another");
|
||||
assertThat(allDescriptors.get("test").getCaches()).containsOnlyKeys("a", "b");
|
||||
assertThat(allDescriptors.get("another").getCaches()).containsOnlyKeys("a", "c");
|
||||
}
|
||||
|
||||
@Test
|
||||
void namedCacheWithSingleCacheManager() {
|
||||
CachesEndpoint endpoint = new CachesEndpoint(
|
||||
Collections.singletonMap("test", new ConcurrentMapCacheManager("b", "a")));
|
||||
CacheEntryDescriptor entry = endpoint.cache("a", null);
|
||||
assertThat(entry).isNotNull();
|
||||
assertThat(entry.getCacheManager()).isEqualTo("test");
|
||||
assertThat(entry.getName()).isEqualTo("a");
|
||||
assertThat(entry.getTarget()).isEqualTo(ConcurrentHashMap.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void namedCacheWithSeveralCacheManagers() {
|
||||
Map<String, CacheManager> cacheManagers = new LinkedHashMap<>();
|
||||
cacheManagers.put("test", new ConcurrentMapCacheManager("b", "dupe-cache"));
|
||||
cacheManagers.put("another", new ConcurrentMapCacheManager("c", "dupe-cache"));
|
||||
CachesEndpoint endpoint = new CachesEndpoint(cacheManagers);
|
||||
assertThatExceptionOfType(NonUniqueCacheException.class).isThrownBy(() -> endpoint.cache("dupe-cache", null))
|
||||
.withMessageContaining("dupe-cache")
|
||||
.withMessageContaining("test")
|
||||
.withMessageContaining("another");
|
||||
}
|
||||
|
||||
@Test
|
||||
void namedCacheWithUnknownCache() {
|
||||
CachesEndpoint endpoint = new CachesEndpoint(
|
||||
Collections.singletonMap("test", new ConcurrentMapCacheManager("b", "a")));
|
||||
CacheEntryDescriptor entry = endpoint.cache("unknown", null);
|
||||
assertThat(entry).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void namedCacheWithWrongCacheManager() {
|
||||
Map<String, CacheManager> cacheManagers = new LinkedHashMap<>();
|
||||
cacheManagers.put("test", new ConcurrentMapCacheManager("b", "a"));
|
||||
cacheManagers.put("another", new ConcurrentMapCacheManager("c", "a"));
|
||||
CachesEndpoint endpoint = new CachesEndpoint(cacheManagers);
|
||||
CacheEntryDescriptor entry = endpoint.cache("c", "test");
|
||||
assertThat(entry).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void namedCacheWithSeveralCacheManagersWithCacheManagerFilter() {
|
||||
Map<String, CacheManager> cacheManagers = new LinkedHashMap<>();
|
||||
cacheManagers.put("test", new ConcurrentMapCacheManager("b", "a"));
|
||||
cacheManagers.put("another", new ConcurrentMapCacheManager("c", "a"));
|
||||
CachesEndpoint endpoint = new CachesEndpoint(cacheManagers);
|
||||
CacheEntryDescriptor entry = endpoint.cache("a", "test");
|
||||
assertThat(entry).isNotNull();
|
||||
assertThat(entry.getCacheManager()).isEqualTo("test");
|
||||
assertThat(entry.getName()).isEqualTo("a");
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearAllCaches() {
|
||||
Cache a = mockCache("a");
|
||||
Cache b = mockCache("b");
|
||||
CachesEndpoint endpoint = new CachesEndpoint(Collections.singletonMap("test", cacheManager(a, b)));
|
||||
endpoint.clearCaches();
|
||||
then(a).should().clear();
|
||||
then(b).should().clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearCache() {
|
||||
Cache a = mockCache("a");
|
||||
Cache b = mockCache("b");
|
||||
CachesEndpoint endpoint = new CachesEndpoint(Collections.singletonMap("test", cacheManager(a, b)));
|
||||
assertThat(endpoint.clearCache("a", null)).isTrue();
|
||||
then(a).should().clear();
|
||||
then(b).should(never()).clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearCacheWithSeveralCacheManagers() {
|
||||
Map<String, CacheManager> cacheManagers = new LinkedHashMap<>();
|
||||
cacheManagers.put("test", cacheManager(mockCache("dupe-cache"), mockCache("b")));
|
||||
cacheManagers.put("another", cacheManager(mockCache("dupe-cache")));
|
||||
CachesEndpoint endpoint = new CachesEndpoint(cacheManagers);
|
||||
assertThatExceptionOfType(NonUniqueCacheException.class)
|
||||
.isThrownBy(() -> endpoint.clearCache("dupe-cache", null))
|
||||
.withMessageContaining("dupe-cache")
|
||||
.withMessageContaining("test")
|
||||
.withMessageContaining("another");
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearCacheWithSeveralCacheManagersWithCacheManagerFilter() {
|
||||
Map<String, CacheManager> cacheManagers = new LinkedHashMap<>();
|
||||
Cache a = mockCache("a");
|
||||
Cache b = mockCache("b");
|
||||
cacheManagers.put("test", cacheManager(a, b));
|
||||
Cache anotherA = mockCache("a");
|
||||
cacheManagers.put("another", cacheManager(anotherA));
|
||||
CachesEndpoint endpoint = new CachesEndpoint(cacheManagers);
|
||||
assertThat(endpoint.clearCache("a", "another")).isTrue();
|
||||
then(a).should(never()).clear();
|
||||
then(anotherA).should().clear();
|
||||
then(b).should(never()).clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearCacheWithUnknownCache() {
|
||||
Cache a = mockCache("a");
|
||||
CachesEndpoint endpoint = new CachesEndpoint(Collections.singletonMap("test", cacheManager(a)));
|
||||
assertThat(endpoint.clearCache("unknown", null)).isFalse();
|
||||
then(a).should(never()).clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearCacheWithUnknownCacheManager() {
|
||||
Cache a = mockCache("a");
|
||||
CachesEndpoint endpoint = new CachesEndpoint(Collections.singletonMap("test", cacheManager(a)));
|
||||
assertThat(endpoint.clearCache("a", "unknown")).isFalse();
|
||||
then(a).should(never()).clear();
|
||||
}
|
||||
|
||||
private CacheManager cacheManager(Cache... caches) {
|
||||
SimpleCacheManager cacheManager = new SimpleCacheManager();
|
||||
cacheManager.setCaches(Arrays.asList(caches));
|
||||
cacheManager.afterPropertiesSet();
|
||||
return cacheManager;
|
||||
}
|
||||
|
||||
private Cache mockCache(String name) {
|
||||
Cache cache = mock(Cache.class);
|
||||
given(cache.getName()).willReturn(name);
|
||||
given(cache.getNativeCache()).willReturn(new Object());
|
||||
return cache;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.endpoint;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.test.WebEndpointTest;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link CachesEndpoint} exposed by Jersey, Spring MVC, and
|
||||
* WebFlux.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class CachesEndpointWebIntegrationTests {
|
||||
|
||||
@WebEndpointTest
|
||||
void allCaches(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/actuator/caches")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.jsonPath("cacheManagers.one.caches.a.target")
|
||||
.isEqualTo(ConcurrentHashMap.class.getName())
|
||||
.jsonPath("cacheManagers.one.caches.b.target")
|
||||
.isEqualTo(ConcurrentHashMap.class.getName())
|
||||
.jsonPath("cacheManagers.two.caches.a.target")
|
||||
.isEqualTo(ConcurrentHashMap.class.getName())
|
||||
.jsonPath("cacheManagers.two.caches.c.target")
|
||||
.isEqualTo(ConcurrentHashMap.class.getName());
|
||||
}
|
||||
|
||||
@WebEndpointTest
|
||||
void namedCache(WebTestClient client) {
|
||||
client.get()
|
||||
.uri("/actuator/caches/b")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody()
|
||||
.jsonPath("name")
|
||||
.isEqualTo("b")
|
||||
.jsonPath("cacheManager")
|
||||
.isEqualTo("one")
|
||||
.jsonPath("target")
|
||||
.isEqualTo(ConcurrentHashMap.class.getName());
|
||||
}
|
||||
|
||||
@WebEndpointTest
|
||||
void namedCacheWithUnknownName(WebTestClient client) {
|
||||
client.get().uri("/actuator/caches/does-not-exist").exchange().expectStatus().isNotFound();
|
||||
}
|
||||
|
||||
@WebEndpointTest
|
||||
void namedCacheWithNonUniqueName(WebTestClient client) {
|
||||
client.get().uri("/actuator/caches/a").exchange().expectStatus().isBadRequest();
|
||||
}
|
||||
|
||||
@WebEndpointTest
|
||||
void clearNamedCache(WebTestClient client, ApplicationContext context) {
|
||||
Cache b = context.getBean("one", CacheManager.class).getCache("b");
|
||||
b.put("test", "value");
|
||||
client.delete().uri("/actuator/caches/b").exchange().expectStatus().isNoContent();
|
||||
assertThat(b.get("test")).isNull();
|
||||
}
|
||||
|
||||
@WebEndpointTest
|
||||
void cleanNamedCacheWithUnknownName(WebTestClient client) {
|
||||
client.delete().uri("/actuator/caches/does-not-exist").exchange().expectStatus().isNotFound();
|
||||
}
|
||||
|
||||
@WebEndpointTest
|
||||
void clearNamedCacheWithNonUniqueName(WebTestClient client) {
|
||||
client.get().uri("/actuator/caches/a").exchange().expectStatus().isBadRequest();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
CacheManager one() {
|
||||
return new ConcurrentMapCacheManager("a", "b");
|
||||
}
|
||||
|
||||
@Bean
|
||||
CacheManager two() {
|
||||
return new ConcurrentMapCacheManager("a", "c");
|
||||
}
|
||||
|
||||
@Bean
|
||||
CachesEndpoint endpoint(Map<String, CacheManager> cacheManagers) {
|
||||
return new CachesEndpoint(cacheManagers);
|
||||
}
|
||||
|
||||
@Bean
|
||||
CachesEndpointWebExtension cachesEndpointWebExtension(CachesEndpoint endpoint) {
|
||||
return new CachesEndpointWebExtension(endpoint);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.metrics;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
import org.cache2k.extra.micrometer.Cache2kCacheMetrics;
|
||||
import org.cache2k.extra.spring.SpringCache2kCacheManager;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link Cache2kCacheMeterBinderProvider}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class Cache2kCacheMeterBinderProviderTests {
|
||||
|
||||
@Test
|
||||
void cache2kCacheProvider() {
|
||||
SpringCache2kCacheManager cacheManager = new SpringCache2kCacheManager()
|
||||
.addCaches((builder) -> builder.name("test"));
|
||||
MeterBinder meterBinder = new Cache2kCacheMeterBinderProvider().getMeterBinder(cacheManager.getCache("test"),
|
||||
Collections.emptyList());
|
||||
assertThat(meterBinder).isInstanceOf(Cache2kCacheMetrics.class);
|
||||
cacheManager.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.metrics;
|
||||
|
||||
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.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cache.caffeine.CaffeineCache;
|
||||
import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link CacheMetricsRegistrar}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class CacheMetricsRegistrarTests {
|
||||
|
||||
private final MeterRegistry meterRegistry = new SimpleMeterRegistry();
|
||||
|
||||
@Test
|
||||
void bindToSupportedCache() {
|
||||
CacheMetricsRegistrar registrar = new CacheMetricsRegistrar(this.meterRegistry,
|
||||
Collections.singleton(new CaffeineCacheMeterBinderProvider()));
|
||||
assertThat(
|
||||
registrar.bindCacheToRegistry(new CaffeineCache("test", Caffeine.newBuilder().recordStats().build())))
|
||||
.isTrue();
|
||||
assertThat(this.meterRegistry.get("cache.gets").tags("name", "test").meter()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindToSupportedCacheWrappedInTransactionProxy() {
|
||||
CacheMetricsRegistrar registrar = new CacheMetricsRegistrar(this.meterRegistry,
|
||||
Collections.singleton(new CaffeineCacheMeterBinderProvider()));
|
||||
assertThat(registrar.bindCacheToRegistry(new TransactionAwareCacheDecorator(
|
||||
new CaffeineCache("test", Caffeine.newBuilder().recordStats().build()))))
|
||||
.isTrue();
|
||||
assertThat(this.meterRegistry.get("cache.gets").tags("name", "test").meter()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindToUnsupportedCache() {
|
||||
CacheMetricsRegistrar registrar = new CacheMetricsRegistrar(this.meterRegistry, Collections.emptyList());
|
||||
assertThat(
|
||||
registrar.bindCacheToRegistry(new CaffeineCache("test", Caffeine.newBuilder().recordStats().build())))
|
||||
.isFalse();
|
||||
assertThat(this.meterRegistry.find("cache.gets").tags("name", "test").meter()).isNull();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.metrics;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
import io.micrometer.core.instrument.binder.cache.CaffeineCacheMetrics;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cache.caffeine.CaffeineCache;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link CaffeineCacheMeterBinderProvider}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class CaffeineCacheMeterBinderProviderTests {
|
||||
|
||||
@Test
|
||||
void caffeineCacheProvider() {
|
||||
CaffeineCache cache = new CaffeineCache("test", Caffeine.newBuilder().build());
|
||||
MeterBinder meterBinder = new CaffeineCacheMeterBinderProvider().getMeterBinder(cache, Collections.emptyList());
|
||||
assertThat(meterBinder).isInstanceOf(CaffeineCacheMetrics.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.metrics;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import com.hazelcast.map.IMap;
|
||||
import com.hazelcast.spring.cache.HazelcastCache;
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
import io.micrometer.core.instrument.binder.cache.HazelcastCacheMetrics;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
import org.springframework.boot.cache.actuate.metrics.HazelcastCacheMeterBinderProvider.HazelcastCacheMeterBinderProviderRuntimeHints;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link HazelcastCacheMeterBinderProvider}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class HazelcastCacheMeterBinderProviderTests {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
void hazelcastCacheProvider() {
|
||||
IMap<Object, Object> nativeCache = mock(IMap.class);
|
||||
given(nativeCache.getName()).willReturn("test");
|
||||
HazelcastCache cache = new HazelcastCache(nativeCache);
|
||||
MeterBinder meterBinder = new HazelcastCacheMeterBinderProvider().getMeterBinder(cache,
|
||||
Collections.emptyList());
|
||||
assertThat(meterBinder).isInstanceOf(HazelcastCacheMetrics.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterHints() {
|
||||
RuntimeHints runtimeHints = new RuntimeHints();
|
||||
new HazelcastCacheMeterBinderProviderRuntimeHints().registerHints(runtimeHints, getClass().getClassLoader());
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethodInvocation(HazelcastCache.class, "getNativeCache"))
|
||||
.accepts(runtimeHints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(HazelcastCacheMetrics.class)).accepts(runtimeHints);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.metrics;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collections;
|
||||
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
import io.micrometer.core.instrument.binder.cache.JCacheMetrics;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
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 JCacheCacheMeterBinderProvider}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class JCacheCacheMeterBinderProviderTests {
|
||||
|
||||
@Mock
|
||||
private javax.cache.Cache<Object, Object> nativeCache;
|
||||
|
||||
@Test
|
||||
void jCacheCacheProvider() throws URISyntaxException {
|
||||
javax.cache.CacheManager cacheManager = mock(javax.cache.CacheManager.class);
|
||||
given(cacheManager.getURI()).willReturn(new URI("/test"));
|
||||
given(this.nativeCache.getCacheManager()).willReturn(cacheManager);
|
||||
given(this.nativeCache.getName()).willReturn("test");
|
||||
JCacheCache cache = new JCacheCache(this.nativeCache);
|
||||
MeterBinder meterBinder = new JCacheCacheMeterBinderProvider().getMeterBinder(cache, Collections.emptyList());
|
||||
assertThat(meterBinder).isInstanceOf(JCacheMetrics.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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
|
||||
*
|
||||
* https://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.cache.actuate.metrics;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.data.redis.cache.RedisCache;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link RedisCacheMeterBinderProvider}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class RedisCacheMeterBinderProviderTests {
|
||||
|
||||
@Test
|
||||
void redisCacheProvider() {
|
||||
RedisCache cache = mock(RedisCache.class);
|
||||
given(cache.getName()).willReturn("test");
|
||||
MeterBinder meterBinder = new RedisCacheMeterBinderProvider().getMeterBinder(cache, Collections.emptyList());
|
||||
assertThat(meterBinder).isInstanceOf(RedisCacheMetrics.class);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user