/* * Copyright Terracotta, Inc. * * 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 * * http://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.ehcache.integration.statistics; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.TimeUnit; import org.assertj.core.data.MapEntry; import org.ehcache.Cache; import org.ehcache.CacheManager; import org.ehcache.config.CacheConfiguration; import org.ehcache.config.builders.CacheConfigurationBuilder; import org.ehcache.config.builders.CacheManagerBuilder; import org.ehcache.config.builders.ResourcePoolsBuilder; import org.ehcache.core.spi.service.StatisticsService; import org.ehcache.expiry.Duration; import org.ehcache.expiry.Expirations; import org.ehcache.impl.config.persistence.DefaultPersistenceConfiguration; import org.ehcache.impl.internal.TimeSourceConfiguration; import org.ehcache.impl.internal.statistics.DefaultStatisticsService; import org.ehcache.integration.TestTimeSource; import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assume.assumeFalse; /** * Check that calculations are accurate according to specification. Each cache method have a different impact on the statistics * so each method should be tested */ public class TierCalculationTest extends AbstractTierCalculationTest { private static final int TIME_TO_EXPIRATION = 100; private CacheManager cacheManager; private Cache<Integer, String> cache; private TestTimeSource timeSource = new TestTimeSource(System.currentTimeMillis()); public TierCalculationTest(String tierName, ResourcePoolsBuilder poolBuilder) { super(tierName, poolBuilder); } @Before public void before() throws Exception { CacheConfiguration<Integer, String> cacheConfiguration = CacheConfigurationBuilder .newCacheConfigurationBuilder(Integer.class, String.class, resources) .withExpiry(Expirations.timeToLiveExpiration(Duration.of(TIME_TO_EXPIRATION, TimeUnit.MILLISECONDS))) .build(); StatisticsService statisticsService = new DefaultStatisticsService(); cacheManager = CacheManagerBuilder.newCacheManagerBuilder() .withCache("cache", cacheConfiguration) .using(new DefaultPersistenceConfiguration(diskPath.newFolder())) .using(statisticsService) .using(new TimeSourceConfiguration(timeSource)) .build(true); cache = cacheManager.getCache("cache", Integer.class, String.class); // Get the tier statistic. tierStatistics = statisticsService.getCacheStatistics("cache") .getTierStatistics().get(tierName); } @After public void after() { if(cacheManager != null) { cacheManager.close(); } } // WARNING: forEach and spliterator can't be tested because they are Java 8 @Test public void clear() { cache.put(1, "a"); cache.put(2, "b"); changesOf(0, 0, 2, 0, 0); cache.clear(); changesOf(0, 0, 0, 0, 0); } @Test public void containsKey() { expect(cache.containsKey(1)).isFalse(); changesOf(0, 0, 0, 0, 0); cache.put(1, "a"); changesOf(0, 0, 1, 0, 0); expect(cache.containsKey(1)).isTrue(); changesOf(0, 0, 0, 0, 0); } @Test public void get() { expect(cache.get(1)).isNull(); changesOf(0, 1, 0, 0, 0); cache.put(1, "a"); changesOf(0, 0, 1, 0, 0); expect(cache.get(1)).isEqualTo("a"); changesOf(1, 0, 0, 0, 0); } @Test public void getAll() { expect(cache.getAll(asSet(1))).containsExactly(MapEntry.entry(1, null)); changesOf(0, 1, 0, 0, 0); cache.put(1, "a"); cache.put(2, "b"); changesOf(0, 0, 2, 0, 0); expect(cache.getAll(asSet(1, 2, 3))).containsKeys(1, 2); changesOf(2, 1, 0, 0, 0); } @Test public void iterator() { cache.put(1, "a"); cache.put(2, "b"); changesOf(0, 0, 2, 0, 0); Iterator<Cache.Entry<Integer, String>> iterator = cache.iterator(); changesOf(1, 0, 0, 0, 0); // FIXME Why one?!? iterator.next().getKey(); changesOf(1, 0, 0, 0, 0); // FIXME One hit and on the cache we have two expect(iterator.hasNext()).isTrue(); changesOf(0, 0, 0, 0, 0); iterator.next().getKey(); changesOf(0, 0, 0, 0, 0); // FIXME No hit on a next expect(iterator.hasNext()).isFalse(); changesOf(0, 0, 0, 0, 0); iterator.remove(); changesOf(1, 0, 0, 1, 0); // FIXME remove does hit } @Test public void put() { cache.put(1, "a"); changesOf(0, 0, 1, 0, 0); cache.put(1, "b"); changesOf(0, 0, 1, 0, 1); } @Test public void putAll() { Map<Integer, String> vals = new HashMap<Integer, String>(); vals.put(1, "a"); vals.put(2, "b"); cache.putAll(vals); changesOf(0, 0, 2, 0, 0); vals.put(1, "c"); vals.put(2, "d"); vals.put(3, "e"); cache.putAll(vals); changesOf(0, 0, 3, 0, 0); // FIXME: No way to track update correctly in OnHeapStore.compute } @Test public void putIfAbsent() { expect(cache.putIfAbsent(1, "a")).isNull(); changesOf(0, 1, 1, 0, 0); expect(cache.putIfAbsent(1, "b")).isEqualTo("a"); changesOf(1, 0, 0, 0, 0); } @Test public void remove() { cache.remove(1); changesOf(0, 0, 0, 0, 0); cache.put(1, "a"); changesOf(0, 0, 1, 0, 0); cache.remove(1); changesOf(0, 0, 0, 1, 0); } @Test public void removeKV() { expect(cache.remove(1, "a")).isFalse(); changesOf(0, 1, 0, 0, 0); cache.put(1, "a"); changesOf(0, 0, 1, 0, 0); expect(cache.remove(1, "xxx")).isFalse(); changesOf(0, 1, 0, 0, 0); // FIXME The cache counts a hit here expect(cache.remove(1, "a")).isTrue(); changesOf(1, 0, 0, 1, 0); } @Test public void removeAllKeys() { cache.put(1, "a"); cache.put(2, "b"); changesOf(0, 0, 2, 0, 0); cache.removeAll(asSet(1, 2, 3)); changesOf(0, 0, 0, 2, 0); } @Test public void replaceKV() { expect(cache.replace(1, "a")).isNull(); changesOf(0, 1, 0, 0, 0); cache.put(1, "a"); changesOf(0, 0, 1, 0, 0); expect(cache.replace(1, "b")).isEqualTo("a"); changesOf(1, 0, 1, 0, 1); } @Test public void replaceKON() { expect(cache.replace(1, "a", "b")).isFalse(); changesOf(0, 1, 0, 0, 0); cache.put(1, "a"); changesOf(0, 0, 1, 0, 0); expect(cache.replace(1, "xxx", "b")).isFalse(); changesOf(0, 1, 0, 0, 0); // FIXME: We have a hit on the cache but a miss on the store. Why? expect(cache.replace(1, "a", "b")).isTrue(); changesOf(1, 0, 1, 0, 1); } @Test public void testClearingStats() { // We do it twice because the second time we already have compensating counters, so the result might fail innerClear(); innerClear(); } private void innerClear() { cache.get(1); // one miss cache.getAll(asSet(1, 2, 3)); // 3 misses cache.put(1, "a"); // one put cache.put(1, "b"); // one put and update cache.putAll(Collections.singletonMap(2, "b")); // 1 put cache.get(1); // one hit cache.remove(1); // one remove cache.removeAll(asSet(2)); // one remove changesOf(1, 4, 3, 2, 1); tierStatistics.clear(); changesOf(-1, -4, -3, -2, -1); } @Test public void testMappingCount() { assertThat(tierStatistics.getMappings()).isEqualTo(0); cache.put(1, "a"); assertThat(tierStatistics.getMappings()).isEqualTo(1); } @Test public void testMaxMappingCount() { assertThat(tierStatistics.getMaxMappings()).isEqualTo(-1); // FIXME Shouldn't it be 0? cache.put(1, "a"); cache.put(2, "b"); cache.remove(1); assertThat(tierStatistics.getMappings()).isEqualTo(1); // FIXME: I was expecting 2 } @Test public void testAllocatedByteSize() { assumeFalse(tierName.equals("OnHeap")); // FIXME: Not calculated for OnHeap when a size is allocated long size = tierStatistics.getAllocatedByteSize(); cache.put(1, "a"); assertThat(tierStatistics.getAllocatedByteSize()).isGreaterThan(size); // FIXME: Why is allocated growing? } @Test public void testOccupiedByteSize() { assertThat(tierStatistics.getOccupiedByteSize()).isEqualTo(0); cache.put(1, "a"); assertThat(tierStatistics.getOccupiedByteSize()).isGreaterThan(0); } @Test public void testEviction() { String payload = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; // Wait until we reach the maximum that we can fit in to make sure that are indeed evictions calculated int i = 0; long evictions; do { cache.put(i++, payload); evictions = tierStatistics.getEvictions(); } while(evictions == 0 && i < 100_000); // The 100 000 threshold is to prevent an infinite loop in case of a bug assertThat(evictions).isGreaterThan(0); } @Test public void testExpiration() throws InterruptedException { cache.put(1, "a"); timeSource.advanceTime(TIME_TO_EXPIRATION); // push the current time after expiration assertThat(cache.get(1)).isNull(); assertThat(tierStatistics.getExpirations()).isEqualTo(1); } }