blob: 976b8d16b1abf430ceba73a074c568bb5bcdb696 [file] [log] [blame]
/*
* 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.cache30;
import static org.apache.geode.internal.JvmSizeUtils.getObjectHeaderSize;
import static org.apache.geode.internal.JvmSizeUtils.roundUpSize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.Properties;
import java.util.Random;
import org.junit.Test;
import org.apache.geode.cache.AttributesFactory;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheException;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.EvictionAttributes;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.Scope;
import org.apache.geode.cache.util.ObjectSizer;
import org.apache.geode.distributed.DistributedSystem;
import org.apache.geode.internal.JvmSizeUtils;
import org.apache.geode.internal.cache.LocalRegion;
import org.apache.geode.internal.cache.eviction.EvictionCounters;
import org.apache.geode.internal.cache.eviction.MemoryLRUController;
import org.apache.geode.internal.size.ReflectionSingleObjectSizer;
import org.apache.geode.internal.size.WellKnownClassSizer;
import org.apache.geode.test.dunit.cache.internal.JUnit4CacheTestCase;
/**
* Tests the basic functionality of the memory lru eviction controller and its statistics.
*
*
* @since GemFire 3.2
*/
public class MemLRUEvictionControllerDUnitTest extends JUnit4CacheTestCase {
private static boolean usingMain = false;
/**
* Creates a new <code>MemLRUEvictionControllerDUnitTest</code>
*/
public MemLRUEvictionControllerDUnitTest() {
super();
}
/**
* Returns the <code>EvictionStatistics</code> for the given region
*/
private EvictionCounters getLRUStats(Region region) {
final LocalRegion l = (LocalRegion) region;
return l.getEvictionController().getCounters();
}
private int getEntryOverhead(Region region) {
LocalRegion lRegion = (LocalRegion) region;
return ((MemoryLRUController) lRegion.getEvictionController()).getPerEntryOverhead();
}
// ////// Test Methods
/**
* Carefully verifies that region operations effect the {@link EvictionCounters} as expected.
*/
@Test
public void testRegionOperations() throws CacheException {
int threshold = 4;
final String name = getUniqueName();
AttributesFactory factory = new AttributesFactory();
factory.setScope(Scope.LOCAL);
factory.setEvictionAttributes(EvictionAttributes.createLRUMemoryAttributes(threshold));
Region region;
if (usingMain) {
DistributedSystem system = DistributedSystem.connect(new Properties());
Cache cache = CacheFactory.create(system);
region = cache.createRegion("Test", factory.create());
} else {
region = createRegion(name, factory.create());
}
EvictionCounters lruStats = getLRUStats(region);
assertNotNull(lruStats);
String sampleKey = "10000";
int stringSize = getObjectHeaderSize() // String object
+ (2 * 4) + JvmSizeUtils.getReferenceSize(); // 2 ints and a reference on a string
stringSize = roundUpSize(stringSize);
int charArraySize = sampleKey.length() * 2 + getObjectHeaderSize() // char array
// object
+ 4; // length of char array
charArraySize = roundUpSize(charArraySize);
assertEquals(stringSize, ReflectionSingleObjectSizer.sizeof(String.class));
assertEquals(roundUpSize(getObjectHeaderSize() + 4),
(new ReflectionSingleObjectSizer()).sizeof(new char[0]));
assertEquals(charArraySize, (new ReflectionSingleObjectSizer()).sizeof(new char[5]));
int keySize = stringSize + charArraySize;
assertEquals(keySize, WellKnownClassSizer.sizeof(sampleKey));
assertEquals(keySize, ObjectSizer.DEFAULT.sizeof(sampleKey));
// now that keys are inlined the keySize is 0
keySize = 0;
byte[] sampleValue = new byte[1000];
int valueSize = sampleValue.length + getObjectHeaderSize() // byte array object;
+ 4; // length of byte array
valueSize = roundUpSize(valueSize);
int entrySize = keySize + valueSize + getEntryOverhead(region);
assertEquals(valueSize, ObjectSizer.DEFAULT.sizeof(sampleValue));
for (int i = 1; i <= 100; i++) {
Object key = String.valueOf(10000 + i);
Object value = new byte[1000];
region.put(key, value);
assertEquals(i * entrySize, lruStats.getCounter());
assertEquals(0, lruStats.getEvictions());
}
for (int i = 100; i >= 1; i--) {
Object key = String.valueOf(10000 + i);
region.destroy(key);
assertEquals((i - 1) * entrySize, lruStats.getCounter());
assertEquals(0, lruStats.getEvictions());
}
}
/**
* Make sure that we only size a class the first time we see the class instance.
*
*/
@Test
public void testSizeClassesOnce() throws CacheException {
int threshold = 4;
final String name = getUniqueName();
AttributesFactory factory = new AttributesFactory();
factory.setScope(Scope.LOCAL);
factory.setEvictionAttributes(EvictionAttributes.createLRUMemoryAttributes(threshold));
Region region = createRegion(name, factory.create());
EvictionCounters lruStats = getLRUStats(region);
assertNotNull(lruStats);
TestObject object = new TestObject(50);
// keySize is 0 because it is inlined
int keySize = 0; // ObjectSizer.DEFAULT.sizeof(new String("10000"));
int valueSize = ObjectSizer.DEFAULT.sizeof(object);
int entrySize = keySize + valueSize + getEntryOverhead(region);
Random ran = new Random();
for (int i = 1; i <= 100; i++) {
Object key = String.valueOf(10000 + i);
// Use a randomly sized object.
Object value = new TestObject(ran.nextInt(100));
region.put(key, value);
assertEquals(i * entrySize, lruStats.getCounter());
assertEquals(0, lruStats.getEvictions());
}
for (int i = 100; i >= 1; i--) {
Object key = String.valueOf(10000 + i);
region.destroy(key);
assertEquals((i - 1) * entrySize, lruStats.getCounter());
assertEquals(0, lruStats.getEvictions());
}
}
/**
* Prints out the number of bytes that a region entry occupies in the VM.
*/
@Test
public void testEntryOverHead() throws Exception {
final String name = getUniqueName();
AttributesFactory factory = new AttributesFactory();
factory.setScope(Scope.LOCAL);
factory.setEvictionAttributes(EvictionAttributes.createLRUMemoryAttributes(50));
Region region;
region = createRegion(name, factory.create());
String s = "Each entry occupies " + getEntryOverhead(region) + " bytes";
region.getCache().getDistributedSystem().getLogWriter().info(s);
}
/**
* Class used in testCustomObjectSizer
*
* @since GemFire 5.0
*/
class CustomObjectSizer implements ObjectSizer {
private final int dataSize;
private boolean wasCalled = false;
public CustomObjectSizer(int dataSize) {
this.dataSize = dataSize;
}
@Override
public int sizeof(Object o) {
wasCalled = true;
return dataSize;
}
public boolean wasCalled() {
return wasCalled;
}
}
/**
* Validate that a custom {@link ObjectSizer} is called, configured propertly, and actually limits
* the size of the <code>Region</code>.
*
*/
@Test
public void testCustomObjectSizer() throws Exception {
final String name = getUniqueName();
final int entrySize = 1024 * 1024;
final int numEntries = 3;
AttributesFactory factory = new AttributesFactory();
CustomObjectSizer cs = new CustomObjectSizer(entrySize);
factory.setScope(Scope.LOCAL);
factory.setEvictionAttributes(EvictionAttributes.createLRUMemoryAttributes(1, cs));
Region r = createRegion(name, factory.create());
for (int size = 0; size < numEntries; size++) {
// changed to a boolean[] because byte[] does not cause a call to ObjectSizer.sizeof
// What was calling it before was the key object. But now that keys are inlined we
// no longer call ObjectSizer.sizeof for the key.
r.put(size, new boolean[entrySize]);
}
assertTrue("ObjectSizer was not triggered", cs.wasCalled());
long actualSize = getLRUStats(r).getCounter();
int bytesPut = (entrySize * numEntries);
assertTrue("Expected bytes put: " + bytesPut + " is not < " + actualSize,
actualSize < bytesPut);
}
public static void main(String[] args) throws Exception {
usingMain = true;
(new MemLRUEvictionControllerDUnitTest()).testRegionOperations();
}
private static class TestObject {
private final byte[] field;
public TestObject(int size) {
field = new byte[size];
}
}
}