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:
Stephane Nicoll
2020-10-07 17:20:53 +02:00
parent a099cd9420
commit 34c4c3f235
9 changed files with 335 additions and 1 deletions

View File

@@ -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);
}
}

View File

@@ -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 {
}
}