diff --git a/src/main/asciidoc/reference/redis.adoc b/src/main/asciidoc/reference/redis.adoc index 2823e4206..1383e5101 100644 --- a/src/main/asciidoc/reference/redis.adoc +++ b/src/main/asciidoc/reference/redis.adoc @@ -809,3 +809,8 @@ The following table lists the default settings for `RedisCacheConfiguration`: |Conversion Service |`DefaultFormattingConversionService` with default cache key converters |==== + +[NOTE] +==== +By default `RedisCache` does not expose statistics. Use `RedisCacheManagerBuilder.enableStatistics()` to collect local _hits_ and _misses_ exposed via `RedisCache#getStatistics()`, that returns a snapshot of the collected data. +==== diff --git a/src/main/java/org/springframework/data/redis/cache/CacheStatistics.java b/src/main/java/org/springframework/data/redis/cache/CacheStatistics.java index 3edd24553..b831238b8 100644 --- a/src/main/java/org/springframework/data/redis/cache/CacheStatistics.java +++ b/src/main/java/org/springframework/data/redis/cache/CacheStatistics.java @@ -15,28 +15,37 @@ */ package org.springframework.data.redis.cache; +import java.time.Instant; import java.util.concurrent.TimeUnit; /** - * Cache statistics for a Redis Cache. + * Cache statistics for a {@link RedisCache}.
+ * NOTE: {@link CacheStatistics} only serve local (in memory) data and do not collect any server + * statistics. * * @author Mark Paluch + * @author Christoph Strobl * @since 2.4 */ public interface CacheStatistics { + /** + * @return the name of the {@link RedisCache}. + */ + String getCacheName(); + /** * @return number of put operations on the cache. */ - long getStores(); + long getPuts(); /** - * @return number of get operations. + * @return the total number of get operations including both {@link #getHits() hits} and {@link #getMisses() misses}. */ - long getRetrievals(); + long getGets(); /** - * @return number of cache get hits. + * @return the number of cache get hits. */ long getHits(); @@ -45,14 +54,39 @@ public interface CacheStatistics { */ long getMisses(); + /** + * @return the number of {@link #getGets() gets} that have not yet been answered (neither {@link #getHits() hit} nor + * {@link #getMisses() miss}). + */ + default long getPending() { + return getGets() - (getHits() + getMisses()); + } + /** * @return number of cache removals. */ - long getRemovals(); + long getDeletes(); /** * @param unit the time unit to report the lock wait duration. * @return lock duration using the given {@link TimeUnit} if the cache is configured to use locking. */ long getLockWaitDuration(TimeUnit unit); + + /** + * @return initial point in time when started statistics capturing. + */ + Instant getSince(); + + /** + * @return instantaneous point in time of last statistics counter reset. Equals {@link #getSince()} if never resetted. + */ + Instant getLastReset(); + + /** + * @return the statistics time. + */ + default Instant getTime() { + return Instant.now(); + } } diff --git a/src/main/java/org/springframework/data/redis/cache/CacheStatisticsCollector.java b/src/main/java/org/springframework/data/redis/cache/CacheStatisticsCollector.java new file mode 100644 index 000000000..6d18cbd83 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/cache/CacheStatisticsCollector.java @@ -0,0 +1,98 @@ +/* + * 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; + +/** + * The statistics collector supports capturing of relevant {@link RedisCache} operations such as + * {@literal hits & misses}. + * + * @author Christoph Strobl + * @since 2.4 + */ +public interface CacheStatisticsCollector extends CacheStatisticsProvider { + + /** + * Increase the counter for {@literal put operations} of the given cache. + * + * @param cacheName must not be {@literal null}. + */ + void incPuts(String cacheName); + + /** + * Increase the counter for {@literal get operations} of the given cache. + * + * @param cacheName must not be {@literal null}. + */ + void incGets(String cacheName); + + /** + * Increase the counter for {@literal get operations with result} of the given cache. + * + * @param cacheName must not be {@literal null}. + */ + void incHits(String cacheName); + + /** + * Increase the counter for {@literal get operations without result} of the given cache. + * + * @param cacheName must not be {@literal null}. + */ + void incMisses(String cacheName); + + /** + * Increase the counter for {@literal delete operations} of the given cache. + * + * @param cacheName must not be {@literal null}. + */ + default void incDeletes(String cacheName) { + incDeletesBy(cacheName, 1); + } + + /** + * Increase the counter for {@literal delete operations} of the given cache by the given value. + * + * @param cacheName must not be {@literal null}. + */ + void incDeletesBy(String cacheName, int value); + + /** + * Increase the gauge for {@literal sync lock duration} of the cache by the given nanoseconds. + * + * @param cacheName must not be {@literal null}. + */ + void incLockTime(String cacheName, long durationNS); + + /** + * Reset the all counters and gauges of for the given cache. + * + * @param cacheName must not be {@literal null}. + */ + void reset(String cacheName); + + /** + * @return a {@link CacheStatisticsCollector} that performs no action. + */ + static CacheStatisticsCollector none() { + return NoOpCacheStatisticsCollector.INSTANCE; + } + + /** + * @return a default {@link CacheStatisticsCollector} implementation. + */ + static CacheStatisticsCollector instance/*clearlyNeedsBetterNaming*/() { + return new DefaultCacheStatisticsCollector(); + } +} diff --git a/src/main/java/org/springframework/data/redis/cache/CacheStatisticsProvider.java b/src/main/java/org/springframework/data/redis/cache/CacheStatisticsProvider.java new file mode 100644 index 000000000..bd59f454a --- /dev/null +++ b/src/main/java/org/springframework/data/redis/cache/CacheStatisticsProvider.java @@ -0,0 +1,31 @@ +/* + * 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; + +/** + * @author Christoph Strobl + * @since 2.4 + */ +public interface CacheStatisticsProvider { + + /** + * Obtain snapshot of the captured statistics. + * + * @param cacheName must not be {@literal null}. + * @return never {@literal null}. + */ + CacheStatistics getCacheStatistics(String cacheName); +} diff --git a/src/main/java/org/springframework/data/redis/cache/DefaultCacheStatistics.java b/src/main/java/org/springframework/data/redis/cache/DefaultCacheStatistics.java deleted file mode 100644 index e2b9d90e2..000000000 --- a/src/main/java/org/springframework/data/redis/cache/DefaultCacheStatistics.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * 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 java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.LongAdder; - -import org.springframework.util.Assert; - -/** - * Default mutable {@link CacheStatistics} implementation. - * - * @author Mark Paluch - * @since 2.4 - */ -class DefaultCacheStatistics implements CacheStatistics { - - private final LongAdder stores = new LongAdder(); - private final LongAdder retrievals = new LongAdder(); - private final LongAdder hits = new LongAdder(); - private final LongAdder misses = new LongAdder(); - private final LongAdder removals = new LongAdder(); - private final LongAdder lockWaitTimeNs = new LongAdder(); - - /* - * (non-Javadoc) - * @see org.springframework.data.redis.cache.CacheStatistics#getStores() - */ - @Override - public long getStores() { - return stores.sum(); - } - - void incrementStores() { - stores.increment(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.redis.cache.CacheStatistics#getRetrievals() - */ - @Override - public long getRetrievals() { - return retrievals.sum(); - } - - void incrementRetrievals() { - retrievals.increment(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.redis.cache.CacheStatistics#getHits() - */ - @Override - public long getHits() { - return hits.sum(); - } - - void incrementHits() { - hits.increment(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.redis.cache.CacheStatistics#getMisses() - */ - @Override - public long getMisses() { - return misses.sum(); - } - - void incrementMisses() { - misses.increment(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.redis.cache.CacheStatistics#getRemovals() - */ - @Override - public long getRemovals() { - return removals.sum(); - } - - void incrementRemovals() { - incrementRemovals(1); - } - - /** - * @param x number of removals to add. - */ - void incrementRemovals(int x) { - removals.add(x); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.redis.cache.CacheStatistics#getLockWaitDuration(java.util.concurrent.TimeUnit) - */ - @Override - public long getLockWaitDuration(TimeUnit unit) { - - Assert.notNull(unit, "TimeUnit must not be null"); - - return unit.convert(lockWaitTimeNs.sum(), TimeUnit.NANOSECONDS); - } - - void incrementLockWaitTime(long waitTimeNs) { - lockWaitTimeNs.add(waitTimeNs); - } -} diff --git a/src/main/java/org/springframework/data/redis/cache/DefaultCacheStatisticsCollector.java b/src/main/java/org/springframework/data/redis/cache/DefaultCacheStatisticsCollector.java new file mode 100644 index 000000000..4788d71cc --- /dev/null +++ b/src/main/java/org/springframework/data/redis/cache/DefaultCacheStatisticsCollector.java @@ -0,0 +1,107 @@ +/* + * 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 java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Default {@link CacheStatisticsCollector} implementation holding synchronized per cache + * {@link MutableCacheStatistics}. + * + * @author Christoph Strobl + * @since 2.4 + */ +class DefaultCacheStatisticsCollector implements CacheStatisticsCollector { + + private final Map stats = new ConcurrentHashMap<>(); + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatisticsCollector.incPuts(java.lang.String) + */ + @Override + public void incPuts(String cacheName) { + statsFor(cacheName).incPuts(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatisticsCollector.incGets(java.lang.String) + */ + @Override + public void incGets(String cacheName) { + statsFor(cacheName).incGets(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatisticsCollector.incHits(java.lang.String) + */ + @Override + public void incHits(String cacheName) { + statsFor(cacheName).incHits(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatisticsCollector.incMisses(java.lang.String) + */ + @Override + public void incMisses(String cacheName) { + statsFor(cacheName).incMisses(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatisticsCollector.incDeletesBy(java.lang.String, int) + */ + @Override + public void incDeletesBy(String cacheName, int value) { + statsFor(cacheName).incDeletes(value); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatisticsCollector.incLockTime(java.lang.String) + */ + @Override + public void incLockTime(String name, long durationNS) { + statsFor(name).incLockWaitTime(durationNS); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatisticsCollector.reset(java.lang.String) + */ + @Override + public void reset(String cacheName) { + statsFor(cacheName).reset(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatisticsCollector.getCacheStatistics(java.lang.String) + */ + @Override + public CacheStatistics getCacheStatistics(String cacheName) { + return statsFor(cacheName).captureSnapshot(); + } + + private MutableCacheStatistics statsFor(String cacheName) { + return stats.computeIfAbsent(cacheName, MutableCacheStatistics::new); + } +} diff --git a/src/main/java/org/springframework/data/redis/cache/DefaultRedisCacheWriter.java b/src/main/java/org/springframework/data/redis/cache/DefaultRedisCacheWriter.java index ca9f2ff65..ec0f4fcc1 100644 --- a/src/main/java/org/springframework/data/redis/cache/DefaultRedisCacheWriter.java +++ b/src/main/java/org/springframework/data/redis/cache/DefaultRedisCacheWriter.java @@ -51,7 +51,7 @@ class DefaultRedisCacheWriter implements RedisCacheWriter { private final RedisConnectionFactory connectionFactory; private final Duration sleepTime; - private final DefaultCacheStatistics statistics; + private final CacheStatisticsCollector statistics; /** * @param connectionFactory must not be {@literal null}. @@ -66,13 +66,25 @@ class DefaultRedisCacheWriter implements RedisCacheWriter { * to disable locking. */ DefaultRedisCacheWriter(RedisConnectionFactory connectionFactory, Duration sleepTime) { + this(connectionFactory, sleepTime, CacheStatisticsCollector.none()); + } + + /** + * @param connectionFactory must not be {@literal null}. + * @param sleepTime sleep time between lock request attempts. Must not be {@literal null}. Use {@link Duration#ZERO} + * to disable locking. + * @param cacheStatisticsCollector must not be {@literal null}. + */ + DefaultRedisCacheWriter(RedisConnectionFactory connectionFactory, Duration sleepTime, + CacheStatisticsCollector cacheStatisticsCollector) { Assert.notNull(connectionFactory, "ConnectionFactory must not be null!"); Assert.notNull(sleepTime, "SleepTime must not be null!"); + Assert.notNull(cacheStatisticsCollector, "CacheStatisticsCollector must not be null!"); this.connectionFactory = connectionFactory; this.sleepTime = sleepTime; - this.statistics = new DefaultCacheStatistics(); + this.statistics = cacheStatisticsCollector; } /* @@ -97,7 +109,7 @@ class DefaultRedisCacheWriter implements RedisCacheWriter { return "OK"; }); - statistics.incrementStores(); + statistics.incPuts(name); } /* @@ -112,12 +124,12 @@ class DefaultRedisCacheWriter implements RedisCacheWriter { byte[] result = execute(name, connection -> connection.get(key)); - statistics.incrementRetrievals(); + statistics.incGets(name); if (result != null) { - statistics.incrementHits(); + statistics.incHits(name); } else { - statistics.incrementMisses(); + statistics.incMisses(name); } return result; @@ -147,7 +159,7 @@ class DefaultRedisCacheWriter implements RedisCacheWriter { connection.pExpire(key, ttl.toMillis()); } - statistics.incrementStores(); + statistics.incPuts(name); return null; } @@ -172,7 +184,7 @@ class DefaultRedisCacheWriter implements RedisCacheWriter { Assert.notNull(key, "Key must not be null!"); execute(name, connection -> connection.del(key)); - statistics.incrementRemovals(); + statistics.incDeletes(name); } /* @@ -200,7 +212,7 @@ class DefaultRedisCacheWriter implements RedisCacheWriter { .toArray(new byte[0][]); if (keys.length > 0) { - statistics.incrementRemovals(keys.length); + statistics.incDeletesBy(name, keys.length); connection.del(keys); } } finally { @@ -216,11 +228,25 @@ class DefaultRedisCacheWriter implements RedisCacheWriter { /* * (non-Javadoc) - * @see org.springframework.data.redis.cache.RedisCacheWriter#getStatistics() + * @see org.springframework.data.redis.cache.CacheStatisticsProvider#getCacheStatistics(java.lang.String) */ @Override - public DefaultCacheStatistics getStatistics() { - return statistics; + public CacheStatistics getCacheStatistics(String cacheName) { + return statistics.getCacheStatistics(cacheName); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.RedisCacheWriter#clearStatistics(java.lang.String) + */ + @Override + public void clearStatistics(String name) { + statistics.reset(name); + } + + @Override + public RedisCacheWriter with(CacheStatisticsCollector cacheStatisticsCollector) { + return new DefaultRedisCacheWriter(connectionFactory, sleepTime, cacheStatisticsCollector); } /** @@ -303,7 +329,7 @@ class DefaultRedisCacheWriter implements RedisCacheWriter { throw new PessimisticLockingFailureException(String.format("Interrupted while waiting to unlock cache %s", name), ex); } finally { - statistics.incrementLockWaitTime(System.nanoTime() - lockWaitTimeNs); + statistics.incLockTime(name, System.nanoTime() - lockWaitTimeNs); } } diff --git a/src/main/java/org/springframework/data/redis/cache/MutableCacheStatistics.java b/src/main/java/org/springframework/data/redis/cache/MutableCacheStatistics.java new file mode 100644 index 000000000..09db99d47 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/cache/MutableCacheStatistics.java @@ -0,0 +1,304 @@ +/* + * 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 java.time.Instant; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.LongAdder; + +import org.springframework.util.Assert; + +/** + * Default mutable {@link CacheStatistics} implementation. + * + * @author Mark Paluch + * @since 2.4 + */ +class MutableCacheStatistics implements CacheStatistics { + + private final String cacheName; + + private final Instant aliveSince = Instant.now(); + private Instant lastReset = aliveSince; + + private final LongAdder puts = new LongAdder(); + private final LongAdder gets = new LongAdder(); + private final LongAdder hits = new LongAdder(); + private final LongAdder misses = new LongAdder(); + private final LongAdder deletes = new LongAdder(); + private final LongAdder lockWaitTimeNs = new LongAdder(); + + MutableCacheStatistics(String cacheName) { + this.cacheName = cacheName; + } + + /* + * (non-Javadoc) + * org.springframework.data.redis.cache.CacheStatistics#getCacheName() + */ + @Override + public String getCacheName() { + return cacheName; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatistics#getPuts() + */ + @Override + public long getPuts() { + return puts.sum(); + } + + void incPuts() { + puts.increment(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatistics#getRetrievals() + */ + @Override + public long getGets() { + return gets.sum(); + } + + void incGets() { + gets.increment(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatistics#getHits() + */ + @Override + public long getHits() { + return hits.sum(); + } + + void incHits() { + hits.increment(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatistics#getMisses() + */ + @Override + public long getMisses() { + return misses.sum(); + } + + void incMisses() { + misses.increment(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatistics#getRemovals() + */ + @Override + public long getDeletes() { + return deletes.sum(); + } + + void incDeletes() { + incDeletes(1); + } + + /** + * @param x number of removals to add. + */ + void incDeletes(int x) { + deletes.add(x); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatistics#getLockWaitDuration(java.util.concurrent.TimeUnit) + */ + @Override + public long getLockWaitDuration(TimeUnit unit) { + + Assert.notNull(unit, "TimeUnit must not be null"); + + return unit.convert(lockWaitTimeNs.sum(), TimeUnit.NANOSECONDS); + } + + @Override + public Instant getSince() { + return this.aliveSince; + } + + @Override + public Instant getLastReset() { + return lastReset; + } + + void incLockWaitTime(long waitTimeNs) { + lockWaitTimeNs.add(waitTimeNs); + } + + void reset() { + + lastReset = Instant.now(); + + puts.reset(); + gets.reset(); + hits.reset(); + misses.reset(); + deletes.reset(); + lockWaitTimeNs.reset(); + } + + CacheStatistics captureSnapshot() { + return new Snapshot(this); + } + + /** + * {@link CacheStatistics} value object holding snapshot data. + */ + private static class Snapshot implements CacheStatistics { + + private final String cacheName; + private final long puts; + private final long gets; + private final long hits; + private final long misses; + private final long deletes; + private final long lockWaitTimeNS; + private final long pending; + private final Instant time; + private final Instant since; + private final Instant lastReset; + + Snapshot(CacheStatistics statistics) { + + cacheName = statistics.getCacheName(); + gets = statistics.getGets(); + hits = statistics.getHits(); + misses = statistics.getMisses(); + puts = statistics.getPuts(); + deletes = statistics.getDeletes(); + pending = gets - (hits + misses); + + lockWaitTimeNS = statistics.getLockWaitDuration(TimeUnit.NANOSECONDS); + + time = Instant.now(); + since = Instant.from(statistics.getSince()); + lastReset = Instant.from(statistics.getLastReset()); + } + + /* + * (non-Javadoc) + * org.springframework.data.redis.cache.CacheStatistics#getCacheName() + */ + @Override + public String getCacheName() { + return cacheName; + } + + /* + * (non-Javadoc) + * org.springframework.data.redis.cache.CacheStatistics#getPuts() + */ + @Override + public long getPuts() { + return puts; + } + + /* + * (non-Javadoc) + * org.springframework.data.redis.cache.CacheStatistics#getGets() + */ + @Override + public long getGets() { + return gets; + } + + /* + * (non-Javadoc) + * org.springframework.data.redis.cache.CacheStatistics#getHits() + */ + @Override + public long getHits() { + return hits; + } + + /* + * (non-Javadoc) + * org.springframework.data.redis.cache.CacheStatistics#getMisses() + */ + @Override + public long getMisses() { + return misses; + } + + /* + * (non-Javadoc) + * org.springframework.data.redis.cache.CacheStatistics#getPending() + */ + @Override + public long getPending() { + return pending; + } + + /* + * (non-Javadoc) + * org.springframework.data.redis.cache.CacheStatistics#getDeletes() + */ + @Override + public long getDeletes() { + return deletes; + } + + /* + * (non-Javadoc) + * org.springframework.data.redis.cache.CacheStatistics#getCacheName(java.util.concurrent.TimeUnit) + */ + @Override + public long getLockWaitDuration(TimeUnit unit) { + return unit.convert(lockWaitTimeNS, TimeUnit.NANOSECONDS); + } + + /* + * (non-Javadoc) + * org.springframework.data.redis.cache.CacheStatistics#getSince() + */ + @Override + public Instant getSince() { + return since; + } + + /* + * (non-Javadoc) + * org.springframework.data.redis.cache.CacheStatistics#getLastReset() + */ + @Override + public Instant getLastReset() { + return lastReset; + } + + /* + * (non-Javadoc) + * org.springframework.data.redis.cache.CacheStatistics#getTime() + */ + @Override + public Instant getTime() { + return time; + } + } +} diff --git a/src/main/java/org/springframework/data/redis/cache/NoOpCacheStatisticsCollector.java b/src/main/java/org/springframework/data/redis/cache/NoOpCacheStatisticsCollector.java new file mode 100644 index 000000000..cd41d6deb --- /dev/null +++ b/src/main/java/org/springframework/data/redis/cache/NoOpCacheStatisticsCollector.java @@ -0,0 +1,164 @@ +/* + * 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 java.time.Instant; +import java.util.concurrent.TimeUnit; + +import org.springframework.util.ObjectUtils; + +/** + * {@link CacheStatisticsCollector} implementation that does not capture anything but throws an + * {@link IllegalStateException} when {@link #getCacheStatistics(String) obtaining} {@link CacheStatistics} for a cache. + * + * @author Christoph Strobl + * @since 2.4 + */ +enum NoOpCacheStatisticsCollector implements CacheStatisticsCollector { + + INSTANCE; + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatisticsCollector.incPuts(java.lang.String) + */ + @Override + public void incPuts(String cacheName) {} + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatisticsCollector.incGets(java.lang.String) + */ + @Override + public void incGets(String cacheName) {} + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatisticsCollector.incHits(java.lang.String) + */ + @Override + public void incHits(String cacheName) {} + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatisticsCollector.incMisses(java.lang.String) + */ + @Override + public void incMisses(String cacheName) {} + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatisticsCollector.incDeletesBy(java.lang.String) + */ + @Override + public void incDeletesBy(String cacheName, int value) {} + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatisticsCollector.incLockTime(java.lang.String) + */ + @Override + public void incLockTime(String name, long durationNS) {} + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatisticsCollector.reset(java.lang.String) + */ + @Override + public void reset(String cacheName) {} + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.cache.CacheStatisticsCollector.getCacheStatistics(java.lang.String) + */ + @Override + public CacheStatistics getCacheStatistics(String cacheName) { + return new EmptyStats(cacheName); + } + + private static class EmptyStats implements CacheStatistics { + + private final String cacheName; + + EmptyStats(String cacheName) { + this.cacheName = cacheName; + } + + @Override + public String getCacheName() { + return cacheName; + } + + @Override + public long getPuts() { + return -1; + } + + @Override + public long getGets() { + return -1; + } + + @Override + public long getHits() { + return -1; + } + + @Override + public long getMisses() { + return -1; + } + + @Override + public long getDeletes() { + return -1; + } + + @Override + public long getLockWaitDuration(TimeUnit unit) { + return -1; + } + + @Override + public Instant getSince() { + return Instant.EPOCH; + } + + @Override + public Instant getLastReset() { + return getSince(); + } + + @Override + public boolean equals(Object o) { + + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + EmptyStats that = (EmptyStats) o; + return ObjectUtils.nullSafeEquals(cacheName, that.cacheName); + } + + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(cacheName); + } + } +} diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCache.java b/src/main/java/org/springframework/data/redis/cache/RedisCache.java index 7dce2e81c..88dea0de2 100644 --- a/src/main/java/org/springframework/data/redis/cache/RedisCache.java +++ b/src/main/java/org/springframework/data/redis/cache/RedisCache.java @@ -195,13 +195,23 @@ public class RedisCache extends AbstractValueAdaptingCache { /** * Return the {@link CacheStatistics} for this cache instance. Statistics are accumulated per cache instance and not - * from the backing Redis data store. Cache statistics are accumulated starting from the time a cache is created. + * from the backing Redis data store. * * @return statistics object for this {@link RedisCache}. * @since 2.4 + * @throws IllegalStateException if the {@link RedisCacheWriter} does not support statistics. */ public CacheStatistics getStatistics() { - return cacheWriter.getStatistics(); + return cacheWriter.getCacheStatistics(getName()); + } + + /** + * Reset all statistics counters and gauges for this cache. + * + * @since 2.4 + */ + public void clearStatistics() { + cacheWriter.clearStatistics(getName()); } /** diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCacheManager.java b/src/main/java/org/springframework/data/redis/cache/RedisCacheManager.java index 6777e0fc4..b15cbf269 100644 --- a/src/main/java/org/springframework/data/redis/cache/RedisCacheManager.java +++ b/src/main/java/org/springframework/data/redis/cache/RedisCacheManager.java @@ -289,6 +289,7 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager public static class RedisCacheManagerBuilder { private @Nullable RedisCacheWriter cacheWriter; + private CacheStatisticsCollector statisticsCollector = CacheStatisticsCollector.none(); private RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig(); private final Map initialCaches = new LinkedHashMap<>(); private boolean enableTransactions; @@ -456,6 +457,25 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager return Optional.ofNullable(this.initialCaches.get(cacheName)); } + /** + * @return + * @since 2.4 + */ + public RedisCacheManagerBuilder enableStatistics() { + return statisticsCollector(CacheStatisticsCollector.instance()); + } + + /** + * @return + * @since 2.4 + */ + private RedisCacheManagerBuilder statisticsCollector(CacheStatisticsCollector statisticsCollector) { + + Assert.notNull(statisticsCollector, "StatisticsCollector must not be null!"); + this.statisticsCollector = statisticsCollector; + return this; + } + /** * Create new instance of {@link RedisCacheManager} with configuration options applied. * @@ -463,9 +483,16 @@ public class RedisCacheManager extends AbstractTransactionSupportingCacheManager */ public RedisCacheManager build() { - Assert.state(cacheWriter != null, "CacheWriter must not be null! You can provide one via 'RedisCacheManagerBuilder#cacheWriter(RedisCacheWriter)'."); + Assert.state(cacheWriter != null, + "CacheWriter must not be null! You can provide one via 'RedisCacheManagerBuilder#cacheWriter(RedisCacheWriter)'."); - RedisCacheManager cm = new RedisCacheManager(cacheWriter, defaultCacheConfiguration, initialCaches, + RedisCacheWriter theCacheWriter = cacheWriter; + + if (!statisticsCollector.equals(CacheStatisticsCollector.none())) { + theCacheWriter = cacheWriter.with(statisticsCollector); + } + + RedisCacheManager cm = new RedisCacheManager(theCacheWriter, defaultCacheConfiguration, initialCaches, allowInFlightCacheCreation); cm.setTransactionAware(enableTransactions); diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCacheWriter.java b/src/main/java/org/springframework/data/redis/cache/RedisCacheWriter.java index 9a3ba701f..9c2525790 100644 --- a/src/main/java/org/springframework/data/redis/cache/RedisCacheWriter.java +++ b/src/main/java/org/springframework/data/redis/cache/RedisCacheWriter.java @@ -31,7 +31,7 @@ import org.springframework.util.Assert; * @author Mark Paluch * @since 2.0 */ -public interface RedisCacheWriter { +public interface RedisCacheWriter extends CacheStatisticsProvider { /** * Create new {@link RedisCacheWriter} without locking behavior. @@ -108,11 +108,18 @@ public interface RedisCacheWriter { void clean(String name, byte[] pattern); /** - * Return the {@link CacheStatistics} for this cache instance. Statistics are accumulated per cache instance and not - * from the backing Redis data store. Cache statistics are accumulated starting from the time a cache is created. + * Reset all statistics counters and gauges for this cache. * - * @return statistics object for this {@link RedisCache}. * @since 2.4 */ - CacheStatistics getStatistics(); + void clearStatistics(String name); + + /** + * Obtain a {@link RedisCacheWriter} using the given {@link CacheStatisticsCollector} to collect metrics. + * + * @param cacheStatisticsCollector must not be {@literal null}. + * @return new instance of {@link RedisCacheWriter}. + */ + RedisCacheWriter with(CacheStatisticsCollector cacheStatisticsCollector); + } diff --git a/src/test/java/org/springframework/data/redis/cache/DefaultCacheStatisticsCollectorUnitTests.java b/src/test/java/org/springframework/data/redis/cache/DefaultCacheStatisticsCollectorUnitTests.java new file mode 100644 index 000000000..073f83ecc --- /dev/null +++ b/src/test/java/org/springframework/data/redis/cache/DefaultCacheStatisticsCollectorUnitTests.java @@ -0,0 +1,86 @@ +/* + * 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.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * @author Christoph Strobl + */ +class DefaultCacheStatisticsCollectorUnitTests { + + static final String CACHE_1 = "cache:1"; + static final String CACHE_2 = "cache:2"; + + DefaultCacheStatisticsCollector collector; + + @BeforeEach + void beforeEach() { + collector = new DefaultCacheStatisticsCollector(); + } + + @Test // DATAREDIS-1082 + void collectsStatsPerCache() { + + collector.incGets(CACHE_1); + collector.incPuts(CACHE_2); + + assertThat(collector.getCacheStatistics(CACHE_1).getGets()).isOne(); + assertThat(collector.getCacheStatistics(CACHE_1).getPuts()).isZero(); + + assertThat(collector.getCacheStatistics(CACHE_2).getGets()).isZero(); + assertThat(collector.getCacheStatistics(CACHE_2).getPuts()).isOne(); + } + + @Test // DATAREDIS-1082 + void returnsEmptyStatsForCacheWithoutStatsSet() { + + assertThat(collector.getCacheStatistics(CACHE_1).getGets()).isZero(); + assertThat(collector.getCacheStatistics(CACHE_1).getPuts()).isZero(); + assertThat(collector.getCacheStatistics(CACHE_1).getDeletes()).isZero(); + } + + @Test // DATAREDIS-1082 + void returnsSnapshotOnGet() { + + collector.incGets(CACHE_1); + + CacheStatistics stats = collector.getCacheStatistics(CACHE_1); + assertThat(stats.getGets()).isOne(); + + collector.incGets(CACHE_1); + + assertThat(stats.getGets()).isOne(); + assertThat(collector.getCacheStatistics(CACHE_1).getGets()).isEqualTo(2); + } + + @Test // DATAREDIS-1082 + void resetClearsData() { + + collector.incGets(CACHE_1); + + CacheStatistics stats = collector.getCacheStatistics(CACHE_1); + assertThat(stats.getGets()).isOne(); + + collector.reset(CACHE_1); + + assertThat(stats.getGets()).isOne(); + assertThat(collector.getCacheStatistics(CACHE_1).getGets()).isZero(); + } +} diff --git a/src/test/java/org/springframework/data/redis/cache/DefaultRedisCacheWriterTests.java b/src/test/java/org/springframework/data/redis/cache/DefaultRedisCacheWriterTests.java index 741cd705c..a2228a955 100644 --- a/src/test/java/org/springframework/data/redis/cache/DefaultRedisCacheWriterTests.java +++ b/src/test/java/org/springframework/data/redis/cache/DefaultRedisCacheWriterTests.java @@ -90,7 +90,7 @@ public class DefaultRedisCacheWriterTests { @Test // DATAREDIS-481, DATAREDIS-1082 public void putShouldAddEternalEntry() { - RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory); + RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory).with(CacheStatisticsCollector.instance()); writer.put(CACHE_NAME, binaryCacheKey, binaryCacheValue, Duration.ZERO); doWithConnection(connection -> { @@ -98,8 +98,8 @@ public class DefaultRedisCacheWriterTests { assertThat(connection.ttl(binaryCacheKey)).isEqualTo(-1); }); - assertThat(writer.getStatistics().getStores()).isOne(); - assertThat(writer.getStatistics().getLockWaitDuration(TimeUnit.NANOSECONDS)).isZero(); + assertThat(writer.getCacheStatistics(CACHE_NAME).getPuts()).isOne(); + assertThat(writer.getCacheStatistics(CACHE_NAME).getLockWaitDuration(TimeUnit.NANOSECONDS)).isZero(); } @Test // DATAREDIS-481 @@ -147,13 +147,13 @@ public class DefaultRedisCacheWriterTests { doWithConnection(connection -> connection.set(binaryCacheKey, binaryCacheValue)); - RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory); + RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory).with(CacheStatisticsCollector.instance()); assertThat(writer.get(CACHE_NAME, binaryCacheKey)) .isEqualTo(binaryCacheValue); - assertThat(writer.getStatistics().getRetrievals()).isOne(); - assertThat(writer.getStatistics().getHits()).isOne(); - assertThat(writer.getStatistics().getMisses()).isZero(); + assertThat(writer.getCacheStatistics(CACHE_NAME).getGets()).isOne(); + assertThat(writer.getCacheStatistics(CACHE_NAME).getHits()).isOne(); + assertThat(writer.getCacheStatistics(CACHE_NAME).getMisses()).isZero(); } @Test // DATAREDIS-481 @@ -164,7 +164,7 @@ public class DefaultRedisCacheWriterTests { @Test // DATAREDIS-481, DATAREDIS-1082 public void putIfAbsentShouldAddEternalEntryWhenKeyDoesNotExist() { - RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory); + RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory).with(CacheStatisticsCollector.instance()); assertThat(writer.putIfAbsent(CACHE_NAME, binaryCacheKey, binaryCacheValue, Duration.ZERO)).isNull(); @@ -172,7 +172,7 @@ public class DefaultRedisCacheWriterTests { assertThat(connection.get(binaryCacheKey)).isEqualTo(binaryCacheValue); }); - assertThat(writer.getStatistics().getStores()).isOne(); + assertThat(writer.getCacheStatistics(CACHE_NAME).getPuts()).isOne(); } @Test // DATAREDIS-481, DATAREDIS-1082 @@ -180,7 +180,7 @@ public class DefaultRedisCacheWriterTests { doWithConnection(connection -> connection.set(binaryCacheKey, binaryCacheValue)); - RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory); + RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory).with(CacheStatisticsCollector.instance()); assertThat(writer.putIfAbsent(CACHE_NAME, binaryCacheKey, "foo".getBytes(), Duration.ZERO)) .isEqualTo(binaryCacheValue); @@ -188,19 +188,19 @@ public class DefaultRedisCacheWriterTests { assertThat(connection.get(binaryCacheKey)).isEqualTo(binaryCacheValue); }); - assertThat(writer.getStatistics().getStores()).isZero(); + assertThat(writer.getCacheStatistics(CACHE_NAME).getPuts()).isZero(); } @Test // DATAREDIS-481, DATAREDIS-1082 public void putIfAbsentShouldAddExpiringEntryWhenKeyDoesNotExist() { - RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory); + RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory).with(CacheStatisticsCollector.instance()); 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(); + assertThat(writer.getCacheStatistics(CACHE_NAME).getPuts()).isOne(); } @Test // DATAREDIS-481, DATAREDIS-1082 @@ -208,11 +208,11 @@ public class DefaultRedisCacheWriterTests { doWithConnection(connection -> connection.set(binaryCacheKey, binaryCacheValue)); - RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory); + RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory).with(CacheStatisticsCollector.instance()); writer.remove(CACHE_NAME, binaryCacheKey); doWithConnection(connection -> assertThat(connection.exists(binaryCacheKey)).isFalse()); - assertThat(writer.getStatistics().getRemovals()).isOne(); + assertThat(writer.getCacheStatistics(CACHE_NAME).getDeletes()).isOne(); } @Test // DATAREDIS-418, DATAREDIS-1082 @@ -223,14 +223,14 @@ public class DefaultRedisCacheWriterTests { connection.set("foo".getBytes(), "bar".getBytes()); }); - RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory); + RedisCacheWriter writer = nonLockingRedisCacheWriter(connectionFactory).with(CacheStatisticsCollector.instance()); 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(); + assertThat(writer.getCacheStatistics(CACHE_NAME).getDeletes()).isOne(); } @Test // DATAREDIS-481 @@ -261,7 +261,7 @@ public class DefaultRedisCacheWriterTests { @Test // DATAREDIS-481, DATAREDIS-1082 public void lockingCacheWriterShouldWaitForLockRelease() throws InterruptedException { - DefaultRedisCacheWriter writer = (DefaultRedisCacheWriter) lockingRedisCacheWriter(connectionFactory); + DefaultRedisCacheWriter writer = (DefaultRedisCacheWriter) lockingRedisCacheWriter(connectionFactory).with(CacheStatisticsCollector.instance()); writer.lock(CACHE_NAME); CountDownLatch beforeWrite = new CountDownLatch(1); @@ -291,7 +291,7 @@ public class DefaultRedisCacheWriterTests { doWithConnection(connection -> { assertThat(connection.exists(binaryCacheKey)).isTrue(); }); - assertThat(writer.getStatistics().getLockWaitDuration(TimeUnit.NANOSECONDS)).isGreaterThan(0); + assertThat(writer.getCacheStatistics(CACHE_NAME).getLockWaitDuration(TimeUnit.NANOSECONDS)).isGreaterThan(0); } finally { th.interrupt(); } @@ -338,6 +338,18 @@ public class DefaultRedisCacheWriterTests { .hasCauseInstanceOf(InterruptedException.class); } + @Test // DATAREDIS-1082 + public void noOpSatisticsCollectorReturnsEmptyStatsInstance() { + + DefaultRedisCacheWriter cw = (DefaultRedisCacheWriter) lockingRedisCacheWriter(connectionFactory); + CacheStatistics stats = cw.getCacheStatistics(CACHE_NAME); + + cw.putIfAbsent(CACHE_NAME, binaryCacheKey, binaryCacheValue, Duration.ofSeconds(5)); + + assertThat(stats).isNotNull(); + assertThat(stats.getPuts()).isNegative(); + } + private void doWithConnection(Consumer callback) { RedisConnection connection = connectionFactory.getConnection(); diff --git a/src/test/java/org/springframework/data/redis/cache/DefaultCacheStatisticsUnitTests.java b/src/test/java/org/springframework/data/redis/cache/MutableCacheStatisticsUnitTests.java similarity index 67% rename from src/test/java/org/springframework/data/redis/cache/DefaultCacheStatisticsUnitTests.java rename to src/test/java/org/springframework/data/redis/cache/MutableCacheStatisticsUnitTests.java index a1436deaf..1dc305a5a 100644 --- a/src/test/java/org/springframework/data/redis/cache/DefaultCacheStatisticsUnitTests.java +++ b/src/test/java/org/springframework/data/redis/cache/MutableCacheStatisticsUnitTests.java @@ -20,22 +20,22 @@ import static org.assertj.core.api.Assertions.*; import org.junit.jupiter.api.Test; /** - * Unit tests for {@link DefaultCacheStatistics}. + * Unit tests for {@link MutableCacheStatistics}. * * @author Mark Paluch */ -class DefaultCacheStatisticsUnitTests { +class MutableCacheStatisticsUnitTests { - DefaultCacheStatistics statistics = new DefaultCacheStatistics(); + MutableCacheStatistics statistics = new MutableCacheStatistics("cache-name"); @Test // DATAREDIS-1082 void shouldReportRetrievals() { - assertThat(statistics.getRetrievals()).isZero(); + assertThat(statistics.getGets()).isZero(); - statistics.incrementRetrievals(); + statistics.incGets(); - assertThat(statistics.getRetrievals()).isOne(); + assertThat(statistics.getGets()).isOne(); } @Test // DATAREDIS-1082 @@ -43,7 +43,7 @@ class DefaultCacheStatisticsUnitTests { assertThat(statistics.getHits()).isZero(); - statistics.incrementHits(); + statistics.incHits(); assertThat(statistics.getHits()).isOne(); } @@ -53,7 +53,7 @@ class DefaultCacheStatisticsUnitTests { assertThat(statistics.getMisses()).isZero(); - statistics.incrementMisses(); + statistics.incMisses(); assertThat(statistics.getMisses()).isOne(); } @@ -61,20 +61,20 @@ class DefaultCacheStatisticsUnitTests { @Test // DATAREDIS-1082 void shouldReportPuts() { - assertThat(statistics.getStores()).isZero(); + assertThat(statistics.getPuts()).isZero(); - statistics.incrementStores(); + statistics.incPuts(); - assertThat(statistics.getStores()).isOne(); + assertThat(statistics.getPuts()).isOne(); } @Test // DATAREDIS-1082 void shouldReportRemovals() { - assertThat(statistics.getRemovals()).isZero(); + assertThat(statistics.getDeletes()).isZero(); - statistics.incrementRemovals(); + statistics.incDeletes(); - assertThat(statistics.getRemovals()).isOne(); + assertThat(statistics.getDeletes()).isOne(); } } diff --git a/src/test/java/org/springframework/data/redis/cache/RedisCacheManagerUnitTests.java b/src/test/java/org/springframework/data/redis/cache/RedisCacheManagerUnitTests.java index 3d2b486a8..aa5c37500 100644 --- a/src/test/java/org/springframework/data/redis/cache/RedisCacheManagerUnitTests.java +++ b/src/test/java/org/springframework/data/redis/cache/RedisCacheManagerUnitTests.java @@ -171,4 +171,21 @@ class RedisCacheManagerUnitTests { void builderShouldRequireCacheWriter() { assertThatIllegalStateException().isThrownBy(() -> RedisCacheManager.builder().build()); } + + @Test // DATAREDIS-1082 + void builderSetsStatisticsCollectorWhenEnabled() { + + when(cacheWriter.with(any())).thenReturn(cacheWriter); + RedisCacheManager.builder(cacheWriter).enableStatistics().build(); + + verify(cacheWriter).with(any(DefaultCacheStatisticsCollector.class)); + } + + @Test // DATAREDIS-1082 + void builderWontSetStatisticsCollectorByDefault() { + + RedisCacheManager.builder(cacheWriter).build(); + + verify(cacheWriter, never()).with(any()); + } }