DATAREDIS-1082 - Expose cache metrics for RedisCache.

We now expose CacheStatistics for RedisCache. Each cache tracks its statistics (retrievals, hits, misses, stores, removals, lock wait) locally. CacheStatistics is exposed through RedisCacheWriter to allow for statistics customization depending on the cache writer strategy. The statistic object is a singleton per cache instance that gets updated with as the cache operates.

RedisCache cache = …;
CacheStatistics statistics = cache.getStatistics();
statistics.getRetrievals();
statistics.getHits();

Original Pull Request: #545
This commit is contained in:
Mark Paluch
2020-07-07 09:40:39 +02:00
committed by Christoph Strobl
parent 488e658f5d
commit 6daeb1a04c
8 changed files with 363 additions and 23 deletions

View File

@@ -0,0 +1,80 @@
/*
* Copyright 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.data.redis.cache;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.Test;
/**
* Unit tests for {@link DefaultCacheStatistics}.
*
* @author Mark Paluch
*/
class DefaultCacheStatisticsUnitTests {
DefaultCacheStatistics statistics = new DefaultCacheStatistics();
@Test // DATAREDIS-1082
void shouldReportRetrievals() {
assertThat(statistics.getRetrievals()).isZero();
statistics.incrementRetrievals();
assertThat(statistics.getRetrievals()).isOne();
}
@Test // DATAREDIS-1082
void shouldReportHits() {
assertThat(statistics.getHits()).isZero();
statistics.incrementHits();
assertThat(statistics.getHits()).isOne();
}
@Test // DATAREDIS-1082
void shouldReportMisses() {
assertThat(statistics.getMisses()).isZero();
statistics.incrementMisses();
assertThat(statistics.getMisses()).isOne();
}
@Test // DATAREDIS-1082
void shouldReportPuts() {
assertThat(statistics.getStores()).isZero();
statistics.incrementStores();
assertThat(statistics.getStores()).isOne();
}
@Test // DATAREDIS-1082
void shouldReportRemovals() {
assertThat(statistics.getRemovals()).isZero();
statistics.incrementRemovals();
assertThat(statistics.getRemovals()).isOne();
}
}

View File

@@ -41,6 +41,8 @@ import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.types.Expiration;
/**
* Integration tests for {@link DefaultRedisCacheWriter}.
*
* @author Christoph Strobl
* @author Mark Paluch
*/
@@ -85,15 +87,19 @@ public class DefaultRedisCacheWriterTests {
doWithConnection(RedisConnection::flushAll);
}
@Test // DATAREDIS-481
@Test // DATAREDIS-481, DATAREDIS-1082
public void putShouldAddEternalEntry() {
nonLockingRedisCacheWriter(connectionFactory).put(CACHE_NAME, binaryCacheKey, binaryCacheValue, Duration.ZERO);
RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory);
writer.put(CACHE_NAME, binaryCacheKey, binaryCacheValue, Duration.ZERO);
doWithConnection(connection -> {
assertThat(connection.get(binaryCacheKey)).isEqualTo(binaryCacheValue);
assertThat(connection.ttl(binaryCacheKey)).isEqualTo(-1);
});
assertThat(writer.getStatistics().getStores()).isOne();
assertThat(writer.getStatistics().getLockWaitDuration(TimeUnit.NANOSECONDS)).isZero();
}
@Test // DATAREDIS-481
@@ -136,13 +142,18 @@ public class DefaultRedisCacheWriterTests {
});
}
@Test // DATAREDIS-481
@Test // DATAREDIS-481, DATAREDIS-1082
public void getShouldReturnValue() {
doWithConnection(connection -> connection.set(binaryCacheKey, binaryCacheValue));
assertThat(nonLockingRedisCacheWriter(connectionFactory).get(CACHE_NAME, binaryCacheKey))
RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory);
assertThat(writer.get(CACHE_NAME, binaryCacheKey))
.isEqualTo(binaryCacheValue);
assertThat(writer.getStatistics().getRetrievals()).isOne();
assertThat(writer.getStatistics().getHits()).isOne();
assertThat(writer.getStatistics().getMisses()).isZero();
}
@Test // DATAREDIS-481
@@ -150,52 +161,61 @@ public class DefaultRedisCacheWriterTests {
assertThat(nonLockingRedisCacheWriter(connectionFactory).get(CACHE_NAME, binaryCacheKey)).isNull();
}
@Test // DATAREDIS-481
@Test // DATAREDIS-481, DATAREDIS-1082
public void putIfAbsentShouldAddEternalEntryWhenKeyDoesNotExist() {
assertThat(nonLockingRedisCacheWriter(connectionFactory).putIfAbsent(CACHE_NAME, binaryCacheKey, binaryCacheValue,
RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory);
assertThat(writer.putIfAbsent(CACHE_NAME, binaryCacheKey, binaryCacheValue,
Duration.ZERO)).isNull();
doWithConnection(connection -> {
assertThat(connection.get(binaryCacheKey)).isEqualTo(binaryCacheValue);
});
assertThat(writer.getStatistics().getStores()).isOne();
}
@Test // DATAREDIS-481
@Test // DATAREDIS-481, DATAREDIS-1082
public void putIfAbsentShouldNotAddEternalEntryWhenKeyAlreadyExist() {
doWithConnection(connection -> connection.set(binaryCacheKey, binaryCacheValue));
assertThat(nonLockingRedisCacheWriter(connectionFactory).putIfAbsent(CACHE_NAME, binaryCacheKey, "foo".getBytes(),
Duration.ZERO)).isEqualTo(binaryCacheValue);
RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory);
assertThat(writer.putIfAbsent(CACHE_NAME, binaryCacheKey, "foo".getBytes(), Duration.ZERO))
.isEqualTo(binaryCacheValue);
doWithConnection(connection -> {
assertThat(connection.get(binaryCacheKey)).isEqualTo(binaryCacheValue);
});
assertThat(writer.getStatistics().getStores()).isZero();
}
@Test // DATAREDIS-481
@Test // DATAREDIS-481, DATAREDIS-1082
public void putIfAbsentShouldAddExpiringEntryWhenKeyDoesNotExist() {
assertThat(nonLockingRedisCacheWriter(connectionFactory).putIfAbsent(CACHE_NAME, binaryCacheKey, binaryCacheValue,
Duration.ofSeconds(5))).isNull();
RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory);
assertThat(writer.putIfAbsent(CACHE_NAME, binaryCacheKey, binaryCacheValue, Duration.ofSeconds(5))).isNull();
doWithConnection(connection -> {
assertThat(connection.ttl(binaryCacheKey)).isGreaterThan(3).isLessThan(6);
});
assertThat(writer.getStatistics().getStores()).isOne();
}
@Test // DATAREDIS-481
@Test // DATAREDIS-481, DATAREDIS-1082
public void removeShouldDeleteEntry() {
doWithConnection(connection -> connection.set(binaryCacheKey, binaryCacheValue));
nonLockingRedisCacheWriter(connectionFactory).remove(CACHE_NAME, binaryCacheKey);
RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory);
writer.remove(CACHE_NAME, binaryCacheKey);
doWithConnection(connection -> assertThat(connection.exists(binaryCacheKey)).isFalse());
assertThat(writer.getStatistics().getRemovals()).isOne();
}
@Test // DATAREDIS-418
@Test // DATAREDIS-418, DATAREDIS-1082
public void cleanShouldRemoveAllKeysByPattern() {
doWithConnection(connection -> {
@@ -203,13 +223,14 @@ public class DefaultRedisCacheWriterTests {
connection.set("foo".getBytes(), "bar".getBytes());
});
nonLockingRedisCacheWriter(connectionFactory).clean(CACHE_NAME,
(CACHE_NAME + "::*").getBytes(Charset.forName("UTF-8")));
RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory);
writer.clean(CACHE_NAME, (CACHE_NAME + "::*").getBytes(Charset.forName("UTF-8")));
doWithConnection(connection -> {
assertThat(connection.exists(binaryCacheKey)).isFalse();
assertThat(connection.exists("foo".getBytes())).isTrue();
});
assertThat(writer.getStatistics().getRemovals()).isOne();
}
@Test // DATAREDIS-481
@@ -237,18 +258,17 @@ public class DefaultRedisCacheWriterTests {
});
}
@Test // DATAREDIS-481
@Test // DATAREDIS-481, DATAREDIS-1082
public void lockingCacheWriterShouldWaitForLockRelease() throws InterruptedException {
DefaultRedisCacheWriter cw = (DefaultRedisCacheWriter) lockingRedisCacheWriter(connectionFactory);
cw.lock(CACHE_NAME);
DefaultRedisCacheWriter writer = (DefaultRedisCacheWriter) lockingRedisCacheWriter(connectionFactory);
writer.lock(CACHE_NAME);
CountDownLatch beforeWrite = new CountDownLatch(1);
CountDownLatch afterWrite = new CountDownLatch(1);
Thread th = new Thread(() -> {
RedisCacheWriter writer = lockingRedisCacheWriter(connectionFactory);
beforeWrite.countDown();
writer.put(CACHE_NAME, binaryCacheKey, binaryCacheValue, Duration.ZERO);
afterWrite.countDown();
@@ -265,12 +285,13 @@ public class DefaultRedisCacheWriterTests {
assertThat(connection.exists(binaryCacheKey)).isFalse();
});
cw.unlock(CACHE_NAME);
writer.unlock(CACHE_NAME);
afterWrite.await();
doWithConnection(connection -> {
assertThat(connection.exists(binaryCacheKey)).isTrue();
});
assertThat(writer.getStatistics().getLockWaitDuration(TimeUnit.NANOSECONDS)).isGreaterThan(0);
} finally {
th.interrupt();
}