mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-09-03 04:26:12 +08:00
Expose cache metrics for Redis
This commit adds support for Redis cache metrics. Users can opt-in for statistics using the "spring.cache.redis.enable-statistics" property. Closes gh-22701
This commit is contained in:
parent
a099cd9420
commit
34c4c3f235
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 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.
|
||||
@ -26,12 +26,14 @@ import org.springframework.boot.actuate.metrics.cache.CaffeineCacheMeterBinderPr
|
||||
import org.springframework.boot.actuate.metrics.cache.EhCache2CacheMeterBinderProvider;
|
||||
import org.springframework.boot.actuate.metrics.cache.HazelcastCacheMeterBinderProvider;
|
||||
import org.springframework.boot.actuate.metrics.cache.JCacheCacheMeterBinderProvider;
|
||||
import org.springframework.boot.actuate.metrics.cache.RedisCacheMeterBinderProvider;
|
||||
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;
|
||||
import org.springframework.data.redis.cache.RedisCache;
|
||||
|
||||
/**
|
||||
* Configure {@link CacheMeterBinderProvider} beans.
|
||||
@ -86,4 +88,15 @@ class CacheMeterBinderProvidersConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(RedisCache.class)
|
||||
static class RedisCacheMeterBinderProviderConfiguration {
|
||||
|
||||
@Bean
|
||||
RedisCacheMeterBinderProvider redisCacheMeterBinderProvider() {
|
||||
return new RedisCacheMeterBinderProvider();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -83,6 +83,7 @@ dependencies {
|
||||
testImplementation("org.skyscreamer:jsonassert")
|
||||
testImplementation("org.springframework:spring-test")
|
||||
testImplementation("com.squareup.okhttp3:mockwebserver")
|
||||
testImplementation("org.testcontainers:junit-jupiter")
|
||||
|
||||
testRuntimeOnly("io.projectreactor.netty:reactor-netty-http")
|
||||
testRuntimeOnly("javax.xml.bind:jaxb-api")
|
||||
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.actuate.metrics.cache;
|
||||
|
||||
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 2.4.0
|
||||
*/
|
||||
public class RedisCacheMeterBinderProvider implements CacheMeterBinderProvider<RedisCache> {
|
||||
|
||||
@Override
|
||||
public MeterBinder getMeterBinder(RedisCache cache, Iterable<Tag> tags) {
|
||||
return new RedisCacheMetrics(cache, tags);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.actuate.metrics.cache;
|
||||
|
||||
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 2.4.0
|
||||
*/
|
||||
public class RedisCacheMetrics extends CacheMeterBinder {
|
||||
|
||||
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,45 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.actuate.metrics.cache;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.actuate.metrics.cache;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
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.autoconfigure.cache.CacheAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.data.redis.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.testcontainers.RedisContainer;
|
||||
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 = new RedisContainer();
|
||||
|
||||
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.redis.host=" + redis.getHost(), "spring.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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -257,6 +257,11 @@ public class CacheProperties {
|
||||
*/
|
||||
private boolean useKeyPrefix = true;
|
||||
|
||||
/**
|
||||
* Whether to enable cache statistics.
|
||||
*/
|
||||
private boolean enableStatistics;
|
||||
|
||||
public Duration getTimeToLive() {
|
||||
return this.timeToLive;
|
||||
}
|
||||
@ -289,6 +294,13 @@ public class CacheProperties {
|
||||
this.useKeyPrefix = useKeyPrefix;
|
||||
}
|
||||
|
||||
public boolean isEnableStatistics() {
|
||||
return this.enableStatistics;
|
||||
}
|
||||
|
||||
public void setEnableStatistics(boolean enableStatistics) {
|
||||
this.enableStatistics = enableStatistics;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -63,6 +63,9 @@ class RedisCacheConfiguration {
|
||||
if (!cacheNames.isEmpty()) {
|
||||
builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
|
||||
}
|
||||
if (cacheProperties.getRedis().isEnableStatistics()) {
|
||||
builder.enableStatistics();
|
||||
}
|
||||
redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
|
||||
return cacheManagerCustomizers.customize(builder.build());
|
||||
}
|
||||
|
@ -2303,6 +2303,7 @@ The following cache libraries are supported:
|
||||
* EhCache 2
|
||||
* Hazelcast
|
||||
* Any compliant JCache (JSR-107) implementation
|
||||
* Redis
|
||||
|
||||
Metrics are tagged by the name of the cache and by the name of the `CacheManager` that is derived from the bean name.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user