/* * 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; } }