/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* 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 com.hazelcast.internal.eviction;
import com.hazelcast.cache.impl.record.CacheObjectRecord;
import com.hazelcast.config.EvictionPolicy;
import com.hazelcast.internal.eviction.impl.evaluator.EvictionPolicyEvaluator;
import com.hazelcast.test.HazelcastSerialClassRunner;
import com.hazelcast.test.HazelcastTestSupport;
import com.hazelcast.test.annotation.QuickTest;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@RunWith(HazelcastSerialClassRunner.class)
@Category(QuickTest.class)
public class EvictionPolicyEvaluatorTest extends HazelcastTestSupport {
private class SimpleEvictionCandidate<K, V extends Evictable> implements EvictionCandidate<K, V> {
private K key;
private V value;
private SimpleEvictionCandidate(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getAccessor() {
return key;
}
@Override
public V getEvictable() {
return value;
}
@Override
public Object getKey() {
throw new UnsupportedOperationException();
}
@Override
public Object getValue() {
throw new UnsupportedOperationException();
}
@Override
public long getCreationTime() {
return getEvictable().getCreationTime();
}
@Override
public long getLastAccessTime() {
return getEvictable().getLastAccessTime();
}
@Override
public long getAccessHit() {
return getEvictable().getAccessHit();
}
}
@Test
public void test_leastRecentlyAccessedEntry_isSelected_when_evictionPolicy_is_LRU() {
test_evictionPolicyLRU(false);
}
@Test
public void test_expiredEntry_hasMorePriority_than_leastRecentlyAccessedEntry_toBeEvicted_when_evictionPolicy_is_LRU() {
test_evictionPolicyLRU(true);
}
private void test_evictionPolicyLRU(boolean useExpiredEntry) {
final int RECORD_COUNT = 100;
final int EXPECTED_EVICTED_RECORD_VALUE = RECORD_COUNT / 2;
final int EXPECTED_EXPIRED_RECORD_VALUE = useExpiredEntry ? RECORD_COUNT / 4 : -1;
EvictionConfiguration evictionConfig = new EvictionConfiguration() {
@Override
public EvictionStrategyType getEvictionStrategyType() {
return null;
}
@Override
public EvictionPolicy getEvictionPolicy() {
return EvictionPolicy.LRU;
}
@Override
public EvictionPolicyType getEvictionPolicyType() {
return EvictionPolicyType.LRU;
}
@Override
public String getComparatorClassName() {
return null;
}
@Override
public EvictionPolicyComparator getComparator() {
return null;
}
};
EvictionPolicyEvaluator evictionPolicyEvaluator =
EvictionPolicyEvaluatorProvider.getEvictionPolicyEvaluator(evictionConfig, null);
List<EvictionCandidate<Integer, CacheObjectRecord>> records =
new ArrayList<EvictionCandidate<Integer, CacheObjectRecord>>();
long baseTime = System.currentTimeMillis();
for (int i = 0; i < RECORD_COUNT; i++) {
long creationTime = baseTime + (i * 100);
CacheObjectRecord record = new CacheObjectRecord(i, creationTime, Long.MAX_VALUE);
if (i == EXPECTED_EVICTED_RECORD_VALUE) {
// The record in the middle will be minimum access time.
// So, it will be selected for eviction
record.setAccessTime(baseTime - 1000);
} else if (i == EXPECTED_EXPIRED_RECORD_VALUE) {
record.setExpirationTime(System.currentTimeMillis());
} else {
record.setAccessTime(creationTime + 1000);
}
records.add(new SimpleEvictionCandidate<Integer, CacheObjectRecord>(i, record));
}
sleepAtLeastMillis(1);
Iterable<EvictionCandidate<Integer, CacheObjectRecord>> evictedRecords =
evictionPolicyEvaluator.evaluate(records);
assertNotNull(evictedRecords);
Iterator<EvictionCandidate<Integer, CacheObjectRecord>> evictedRecordsIterator = evictedRecords.iterator();
assertTrue(evictedRecordsIterator.hasNext());
EvictionCandidate<Integer, CacheObjectRecord> candidateEvictedRecord = evictedRecordsIterator.next();
assertNotNull(candidateEvictedRecord);
assertFalse(evictedRecordsIterator.hasNext());
CacheObjectRecord evictedRecord = candidateEvictedRecord.getEvictable();
assertNotNull(evictedRecord);
if (useExpiredEntry) {
assertEquals(EXPECTED_EXPIRED_RECORD_VALUE, evictedRecord.getValue());
} else {
assertEquals(EXPECTED_EVICTED_RECORD_VALUE, evictedRecord.getValue());
}
}
@Test
public void test_leastFrequentlyUsedEntry_isSelected_when_evictionPolicy_is_LFU() {
test_evictionPolicyLFU(false);
}
@Test
public void test_expiredEntry_hasMorePriority_than_leastFrequentlyUsedEntry_toBeEvicted_when_evictionPolicy_is_LFU() {
test_evictionPolicyLFU(true);
}
private void test_evictionPolicyLFU(boolean useExpiredEntry) {
final int RECORD_COUNT = 100;
final int EXPECTED_EVICTED_RECORD_VALUE = RECORD_COUNT / 2;
final int EXPECTED_EXPIRED_RECORD_VALUE = useExpiredEntry ? RECORD_COUNT / 4 : -1;
EvictionConfiguration evictionConfig = new EvictionConfiguration() {
@Override
public EvictionStrategyType getEvictionStrategyType() {
return null;
}
@Override
public EvictionPolicy getEvictionPolicy() {
return EvictionPolicy.LFU;
}
@Override
public EvictionPolicyType getEvictionPolicyType() {
return EvictionPolicyType.LFU;
}
@Override
public String getComparatorClassName() {
return null;
}
@Override
public EvictionPolicyComparator getComparator() {
return null;
}
};
EvictionPolicyEvaluator evictionPolicyEvaluator =
EvictionPolicyEvaluatorProvider.getEvictionPolicyEvaluator(evictionConfig, null);
List<EvictionCandidate<Integer, CacheObjectRecord>> records =
new ArrayList<EvictionCandidate<Integer, CacheObjectRecord>>();
for (int i = 0; i < RECORD_COUNT; i++) {
CacheObjectRecord record = new CacheObjectRecord(i, System.currentTimeMillis(), Long.MAX_VALUE);
if (i == EXPECTED_EVICTED_RECORD_VALUE) {
// The record in the middle will be minimum access hit.
// So, it will be selected for eviction
record.setAccessHit(0);
} else if (i == EXPECTED_EXPIRED_RECORD_VALUE) {
record.setExpirationTime(System.currentTimeMillis());
} else {
record.setAccessHit(i + 1);
}
records.add(new SimpleEvictionCandidate<Integer, CacheObjectRecord>(i, record));
}
Iterable<EvictionCandidate<Integer, CacheObjectRecord>> evictedRecords =
evictionPolicyEvaluator.evaluate(records);
assertNotNull(evictedRecords);
Iterator<EvictionCandidate<Integer, CacheObjectRecord>> evictedRecordsIterator = evictedRecords.iterator();
assertTrue(evictedRecordsIterator.hasNext());
EvictionCandidate<Integer, CacheObjectRecord> candidateEvictedRecord = evictedRecordsIterator.next();
assertNotNull(candidateEvictedRecord);
assertFalse(evictedRecordsIterator.hasNext());
CacheObjectRecord evictedRecord = candidateEvictedRecord.getEvictable();
assertNotNull(evictedRecord);
if (useExpiredEntry) {
assertEquals(EXPECTED_EXPIRED_RECORD_VALUE, evictedRecord.getValue());
} else {
assertEquals(EXPECTED_EVICTED_RECORD_VALUE, evictedRecord.getValue());
}
}
}