/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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.apache.geode.internal.cache.lru;
import static org.apache.geode.distributed.ConfigurationProperties.*;
import static org.junit.Assert.*;
import java.util.Properties;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.TestName;
import org.apache.geode.StatisticDescriptor;
import org.apache.geode.StatisticsFactory;
import org.apache.geode.StatisticsType;
import org.apache.geode.StatisticsTypeFactory;
import org.apache.geode.cache.AttributesFactory;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheExistsException;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.EvictionAction;
import org.apache.geode.cache.EvictionAlgorithm;
import org.apache.geode.cache.Region;
import org.apache.geode.distributed.DistributedSystem;
import org.apache.geode.internal.statistics.StatisticsTypeFactoryImpl;
import org.apache.geode.internal.cache.InternalRegionArguments;
import org.apache.geode.internal.cache.PlaceHolderDiskRegion;
import org.apache.geode.test.junit.categories.IntegrationTest;
/**
* This class tests the LRUCapacityController's core clock algorithm.
*/
@Category(IntegrationTest.class)
public class LRUClockJUnitTest {
private String myTestName;
static Properties sysProps = new Properties();
@Rule
public TestName testName = new TestName();
@Before
public void setUp() throws Exception {
sysProps = new Properties();
sysProps.setProperty(MCAST_PORT, "0");
sysProps.setProperty(LOCATORS, "");
}
@Test
public void testAddToClockFace() throws Exception {
NewLRUClockHand clock = getAClockHand(getARegion(), new TestEnableLRU());
// getLRUEntry( maxScan )
LRUTestEntry[] nodes = new LRUTestEntry[10];
int i = 0;
for (i = 0; i < 10; i++) {
nodes[i] = getANode(i);
clock.appendEntry(nodes[i]);
}
// getLRUEntry until empty... verify order of results.
for (i = 0; i < 10; i++) {
LRUTestEntry n = (LRUTestEntry) clock.getLRUEntry();
assertTrue("expected nodes[" + nodes[i].id() + "], found nodes[" + n.id() + "]",
n == nodes[i]);
}
assertTrue("expected null", clock.getLRUEntry() == null);
}
@Test
public void testFIFO() throws Exception {
NewLRUClockHand clock = getAClockHand(getARegion(), new TestEnableLRU());
for (int i = 0; i < 100; i++) {
LRUClockNode entry = getANode(i);
clock.appendEntry(entry);
}
for (int i = 100; i < 2000; i++) {
LRUClockNode entry = getANode(i);
clock.appendEntry(entry);
Object obj = clock.getLRUEntry();
if (obj instanceof LRUTestEntry) {
LRUTestEntry le = (LRUTestEntry) obj;
le.setEvicted();
} else {
assertTrue("found wrong type: " + obj.getClass().getName(), false);
}
}
int counter = 0;
LRUTestEntry le = (LRUTestEntry) clock.getLRUEntry();
while (le != null) {
counter++;
le = (LRUTestEntry) clock.getLRUEntry();
}
assertTrue("expected 100, found " + counter, counter == 100);
}
@Test
public void testEvicted() throws Exception {
NewLRUClockHand clock = getAClockHand(getARegion(), new TestEnableLRU());
// getLRUEntry( maxScan )
LRUTestEntry[] nodes = new LRUTestEntry[10];
int i = 0;
for (i = 0; i < 10; i++) {
nodes[i] = getANode(i);
clock.appendEntry(nodes[i]);
}
for (i = 0; i < 10; i += 2) {
clock.unlinkEntry(nodes[i]);
}
// getLRUEntry until empty... verify order of results.
for (i = 1; i < 10; i += 2) {
LRUTestEntry n = (LRUTestEntry) clock.getLRUEntry();
assertTrue("expected nodes[" + nodes[i].id() + "], found nodes[" + n.id() + "]",
n == nodes[i]);
}
assertTrue("expected null", clock.getLRUEntry() == null);
}
@Test
public void testRecentlyUsed() throws Exception {
NewLRUClockHand clock = getAClockHand(getARegion(), new TestEnableLRU());
// getLRUEntry( maxScan )
LRUTestEntry[] nodes = new LRUTestEntry[10];
int i = 0;
for (i = 0; i < 10; i++) {
nodes[i] = getANode(i);
clock.appendEntry(nodes[i]);
if (i % 2 == 0) {
nodes[i].setRecentlyUsed();
}
}
// getLRUEntry until empty... verify order of results.
// should find 1, 3, etc... as 0, 2, 4 etc... were marked recently used..
for (i = 1; i < 10; i += 2) {
LRUTestEntry n = (LRUTestEntry) clock.getLRUEntry();
assertTrue("expected nodes[" + nodes[i].id() + "], found nodes[" + n.id() + "]",
n == nodes[i]);
}
// now 0, 2, 4 should go...
for (i = 0; i < 10; i += 2) {
LRUTestEntry n = (LRUTestEntry) clock.getLRUEntry();
assertTrue("expected nodes[" + nodes[i].id() + "], found nodes[" + n.id() + "]",
n == nodes[i]);
}
assertTrue("expected null", clock.getLRUEntry() == null);
}
@Test
public void testRemoveHead() throws Exception {
NewLRUClockHand clock = getAClockHand(getARegion(), new TestEnableLRU());
LRUTestEntry[] nodes = new LRUTestEntry[10];
int i = 0;
for (i = 0; i < 10; i++) {
nodes[i] = getANode(i);
clock.appendEntry(nodes[i]);
}
clock.unlinkEntry(nodes[0]);
for (i = 1; i < 10; i++) {
LRUTestEntry n = (LRUTestEntry) clock.getLRUEntry();
assertTrue("expected nodes[" + nodes[i].id() + "], found nodes[" + n.id() + "]",
n == nodes[i]);
}
assertEquals(null, clock.getLRUEntry());
}
@Test
public void testRemoveMiddle() throws Exception {
NewLRUClockHand clock = getAClockHand(getARegion(), new TestEnableLRU());
LRUTestEntry[] nodes = new LRUTestEntry[10];
int i = 0;
for (i = 0; i < 10; i++) {
nodes[i] = getANode(i);
clock.appendEntry(nodes[i]);
}
clock.unlinkEntry(nodes[5]);
for (i = 0; i < 5; i++) {
LRUTestEntry n = (LRUTestEntry) clock.getLRUEntry();
assertTrue("expected nodes[" + nodes[i].id() + "], found nodes[" + n.id() + "]",
n == nodes[i]);
}
for (i = 6; i < 10; i++) {
LRUTestEntry n = (LRUTestEntry) clock.getLRUEntry();
assertTrue("expected nodes[" + nodes[i].id() + "], found nodes[" + n.id() + "]",
n == nodes[i]);
}
assertEquals(null, clock.getLRUEntry());
}
@Test
public void testRemoveTail() throws Exception {
NewLRUClockHand clock = getAClockHand(getARegion(), new TestEnableLRU());
LRUTestEntry[] nodes = new LRUTestEntry[10];
int i = 0;
for (i = 0; i < 10; i++) {
nodes[i] = getANode(i);
clock.appendEntry(nodes[i]);
}
clock.unlinkEntry(nodes[9]);
for (i = 0; i < 9; i++) {
LRUTestEntry n = (LRUTestEntry) clock.getLRUEntry();
assertTrue("expected nodes[" + nodes[i].id() + "], found nodes[" + n.id() + "]",
n == nodes[i]);
}
assertEquals(null, clock.getLRUEntry());
}
/** manufacture a node so that a shared type can be used by SharedLRUClockTest. */
private LRUTestEntry getANode(int id) {
return new LocalLRUTestEntry(id);
}
private interface LRUTestEntry extends LRUClockNode {
public int id();
}
/** test implementation of an LRUClockNode */
private static class LocalLRUTestEntry implements LRUTestEntry {
int id;
LRUClockNode next;
LRUClockNode prev;
int size;
boolean recentlyUsed;
boolean evicted;
public LocalLRUTestEntry(int id) {
this.id = id;
next = null;
prev = null;
size = 0;
recentlyUsed = false;
evicted = false;
}
@Override
public int id() {
return id;
}
public boolean isTombstone() {
return false;
}
@Override
public void setNextLRUNode(LRUClockNode next) {
this.next = next;
}
@Override
public LRUClockNode nextLRUNode() {
return this.next;
}
@Override
public void setPrevLRUNode(LRUClockNode prev) {
this.prev = prev;
}
@Override
public LRUClockNode prevLRUNode() {
return this.prev;
}
@Override
public int updateEntrySize(EnableLRU cc) {
return this.size = 1;
}
@Override
public int updateEntrySize(EnableLRU cc, Object value) {
return this.size = 1;
}
@Override
public int getEntrySize() {
return this.size;
}
/** this should only happen with the LRUClockHand sync'ed */
@Override
public void setEvicted() {
evicted = true;
}
@Override
public void unsetEvicted() {
evicted = false;
}
@Override
public boolean testEvicted() {
return evicted;
}
@Override
public boolean testRecentlyUsed() {
return recentlyUsed;
}
@Override
public void setRecentlyUsed() {
recentlyUsed = true;
}
@Override
public void unsetRecentlyUsed() {
recentlyUsed = false;
}
public LRUClockNode absoluteSelf() {
return this;
}
public LRUClockNode clearClones() {
return this;
}
public int cloneCount() {
return 0;
}
}
private class TestEnableLRU implements EnableLRU {
private final StatisticsType statType;
{
// create the stats type for MemLRU.
StatisticsTypeFactory f = StatisticsTypeFactoryImpl.singleton();
final String bytesAllowedDesc = "Number of total bytes allowed in this region.";
final String byteCountDesc = "Number of bytes in region.";
final String lruEvictionsDesc = "Number of total entry evictions triggered by LRU.";
final String lruEvaluationsDesc = "Number of entries evaluated during LRU operations.";
final String lruGreedyReturnsDesc = "Number of non-LRU entries evicted during LRU operations";
final String lruDestroysDesc = "Number of entry destroys triggered by LRU.";
final String lruDestroysLimitDesc =
"Maximum number of entry destroys triggered by LRU before scan occurs.";
statType = f.createType("TestLRUStatistics",
"Statistics about byte based Least Recently Used region entry disposal",
new StatisticDescriptor[] {f.createLongGauge("bytesAllowed", bytesAllowedDesc, "bytes"),
f.createLongGauge("byteCount", byteCountDesc, "bytes"),
f.createLongCounter("lruEvictions", lruEvictionsDesc, "entries"),
f.createLongCounter("lruEvaluations", lruEvaluationsDesc, "entries"),
f.createLongCounter("lruGreedyReturns", lruGreedyReturnsDesc, "entries"),
f.createLongCounter("lruDestroys", lruDestroysDesc, "entries"),
f.createLongCounter("lruDestroysLimit", lruDestroysLimitDesc, "entries"),});
}
@Override
public int entrySize(Object key, Object value) throws IllegalArgumentException {
return 1;
}
@Override
public long limit() {
return 20;
}
public boolean usesMem() {
return false;
}
@Override
public EvictionAlgorithm getEvictionAlgorithm() {
return EvictionAlgorithm.LRU_ENTRY;
}
@Override
public LRUStatistics getStats() {
return null;
}
@Override
public EvictionAction getEvictionAction() {
return EvictionAction.DEFAULT_EVICTION_ACTION;
}
@Override
public StatisticsType getStatisticsType() {
return statType;
}
@Override
public String getStatisticsName() {
return "TestLRUStatistics";
}
@Override
public int getLimitStatId() {
return statType.nameToId("bytesAllowed");
}
@Override
public int getCountStatId() {
return statType.nameToId("byteCount");
}
@Override
public int getEvictionsStatId() {
return statType.nameToId("lruEvictions");
}
@Override
public int getDestroysStatId() {
return statType.nameToId("lruDestroys");
}
@Override
public int getDestroysLimitStatId() {
return statType.nameToId("lruDestroysLimit");
}
@Override
public int getEvaluationsStatId() {
return statType.nameToId("lruEvaluations");
}
@Override
public int getGreedyReturnsStatId() {
return statType.nameToId("lruGreedyReturns");
}
@Override
public boolean mustEvict(LRUStatistics stats, Region region, int delta) {
throw new UnsupportedOperationException("Not implemented");
}
@Override
public void afterEviction() {
throw new UnsupportedOperationException("Not implemented");
}
@Override
public LRUStatistics initStats(Object region, StatisticsFactory sf) {
String regionName;
if (region instanceof Region) {
regionName = ((Region) region).getName();
} else if (region instanceof PlaceHolderDiskRegion) {
regionName = ((PlaceHolderDiskRegion) region).getName();
// @todo make it shorter (I think it is the fullPath
} else {
throw new IllegalStateException("expected Region or PlaceHolderDiskRegion");
}
final LRUStatistics stats = new LRUStatistics(sf, "TestLRUStatistics" + regionName, this);
stats.setLimit(limit());
return stats;
}
}
/** overridden in SharedLRUClockTest to test SharedLRUClockHand */
private NewLRUClockHand getAClockHand(Region reg, EnableLRU elru) {
return new NewLRUClockHand(reg, elru, new InternalRegionArguments());
}
private Region getARegion() throws Exception {
DistributedSystem ds = DistributedSystem.connect(sysProps);
Cache c = null;
try {
c = CacheFactory.create(ds);
} catch (CacheExistsException cee) {
c = CacheFactory.getInstance(ds);
}
AttributesFactory af = new AttributesFactory();
Region root = c.getRegion("root");
if (root == null) {
root = c.createRegion("root", af.create());
}
Region sub = root.createSubregion(testName.getMethodName(), af.create());
return sub;
}
}