blob: 460c62ecefd323db3a140099226976bacddb2f2d [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.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.apache.geode.cache.AttributesFactory;
import org.apache.geode.cache.CacheException;
import org.apache.geode.cache.CacheStatistics;
import org.apache.geode.cache.EntryDestroyedException;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.Scope;
import org.apache.geode.cache.StatisticsDisabledException;
import org.apache.geode.test.dunit.Host;
import org.apache.geode.test.dunit.SerializableRunnable;
import org.apache.geode.test.dunit.VM;
import org.apache.geode.test.dunit.Wait;
import org.apache.geode.test.dunit.cache.internal.JUnit4CacheTestCase;
/**
* Tests the {@link CacheStatistics} that are maintained by a {@link Region} and a
* {@link org.apache.geode.cache.Region.Entry}.
*
*
* @since GemFire 3.0
*/
public class CacheStatisticsDUnitTest extends JUnit4CacheTestCase {
public CacheStatisticsDUnitTest() {
super();
}
//////// Helper Methods
/**
* Asserts that two <code>long</code>s are equal concerning a delta.
*/
// public static void assertIndexDetailsEquals(long expected, long actual,
// long delta) {
// long difference = Math.abs(expected - actual);
// assertTrue("Expected: " + expected
// + " Actual: " + actual,
// difference <= delta);
// }
public static void assertInRange(long start, long end, long actual) {
assertTrue("Expected: " + actual + " >= " + start, actual >= start);
assertTrue("Expected: " + actual + " <= " + end, actual <= end);
}
//////// Test methods
/**
* Tests that the {@link CacheStatistics#getHitCount hit count} and
* {@link CacheStatistics#getMissCount miss count} are updated properly for a local region and its
* entries.
*/
@Test
public void testHitMissCount() throws CacheException {
String name = getUniqueName();
Object key = "KEY"; // value exists
Object key2 = "KEY2"; // no entry
Object key3 = "KEY3"; // entry, invalid
Object value = "VALUE";
AttributesFactory factory = new AttributesFactory();
factory.setScope(Scope.LOCAL);
factory.setStatisticsEnabled(true);
Region region = createRegion(name, factory.create());
CacheStatistics rStats = region.getStatistics();
assertEquals(0, rStats.getHitCount());
assertEquals(0, rStats.getMissCount());
assertEquals(0.0f, rStats.getHitRatio(), 0.0f);
assertNull(region.get(key));
assertEquals(0, rStats.getHitCount());
assertEquals(1, rStats.getMissCount());
assertEquals(0.0f, rStats.getHitRatio(), 0.0f);
assertNull(region.get(key));
assertEquals(0, rStats.getHitCount());
assertEquals(2, rStats.getMissCount());
assertEquals(0.0f, rStats.getHitRatio(), 0.0f);
rStats.resetCounts();
assertEquals(0, rStats.getHitCount());
assertEquals(0, rStats.getMissCount());
assertEquals(0.0f, rStats.getHitRatio(), 0.0f);
region.put(key, value);
assertEquals(0, rStats.getHitCount());
assertEquals(0, rStats.getMissCount());
CacheStatistics eStats = region.getEntry(key).getStatistics();
assertEquals(0, eStats.getHitCount());
assertEquals(0, eStats.getMissCount());
assertEquals(0.0f, eStats.getHitRatio(), 0.0f);
region.get(key);
assertEquals(1, eStats.getHitCount());
assertEquals(0, eStats.getMissCount());
assertEquals(1.0f, eStats.getHitRatio(), 0.0f);
assertEquals(1, rStats.getHitCount());
assertEquals(0, rStats.getMissCount());
assertEquals(1.0f, rStats.getHitRatio(), 0.0f);
region.get(key2);
// assert independent from eStats
assertEquals(1, eStats.getHitCount());
assertEquals(0, eStats.getMissCount());
assertEquals(1.0f, eStats.getHitRatio(), 0.0f);
assertEquals(1, rStats.getHitCount());
assertEquals(1, rStats.getMissCount());
assertEquals(0.5f, rStats.getHitRatio(), 0.0f);
region.create(key3, null);
CacheStatistics e3Stats = region.getEntry(key3).getStatistics();
assertEquals(0, e3Stats.getHitCount());
assertEquals(0, e3Stats.getMissCount());
assertEquals(0.0f, e3Stats.getHitRatio(), 0.0f);
region.get(key3); // miss on existing entry
assertEquals(0, e3Stats.getHitCount());
assertEquals(1, e3Stats.getMissCount());
assertEquals(0.0f, e3Stats.getHitRatio(), 0.0f);
assertEquals(1, rStats.getHitCount());
assertEquals(2, rStats.getMissCount());
assertEquals(0.33f, rStats.getHitRatio(), 0.01f);
eStats.resetCounts();
assertEquals(0, eStats.getHitCount());
assertEquals(0, eStats.getMissCount());
assertEquals(0.0f, eStats.getHitRatio(), 0.0f);
rStats.resetCounts();
region.invalidate(key);
assertEquals(0, eStats.getHitCount());
assertEquals(0, eStats.getMissCount());
assertEquals(0.0f, eStats.getHitRatio(), 0.0f);
region.get(key);
assertEquals(0, eStats.getHitCount());
assertEquals(1, eStats.getMissCount());
assertEquals(0, rStats.getHitCount());
assertEquals(1, rStats.getMissCount());
region.destroy(key);
try {
eStats.getMissCount();
fail("Should have thrown an EntryDestroyedException");
} catch (EntryDestroyedException ex) {
// pass...
}
}
/**
* Tests that the {@linkplain CacheStatistics#getLastAccessedTime last access time} and
* {@link CacheStatistics#getLastModifiedTime last modified time} are update appropriately for a
* local region and its entries. It also validates that the last modification and last access
* times are propagated to parent regions.
*/
@Test
public void testTimeStats() throws CacheException, InterruptedException {
final long ESTAT_RES = 100; // the resolution, in ms, of entry stats
String name = getUniqueName();
Object key = "KEY";
Object key2 = "KEY2";
Object value = "VALUE";
long before;
long after;
long oldBefore;
long oldAfter;
AttributesFactory factory = new AttributesFactory();
factory.setScope(Scope.LOCAL);
factory.setStatisticsEnabled(true);
before = getCache().cacheTimeMillis();
Region region = createRegion(name, factory.create());
CacheStatistics rStats = region.getStatistics();
CacheStatistics rootStats = getRootRegion().getStatistics();
after = getCache().cacheTimeMillis();
assertInRange(before, after, rStats.getLastAccessedTime());
assertInRange(before, after, rStats.getLastModifiedTime());
assertInRange(before, after, rootStats.getLastAccessedTime());
assertInRange(before, after, rootStats.getLastModifiedTime());
oldBefore = before;
oldAfter = after;
Wait.pause(150);
before = getCache().cacheTimeMillis();
region.get(key);
after = getCache().cacheTimeMillis();
assertInRange(before, after, rStats.getLastAccessedTime());
assertInRange(oldBefore, oldAfter, rStats.getLastModifiedTime());
assertInRange(before, after, rootStats.getLastAccessedTime());
assertInRange(oldBefore, oldAfter, rootStats.getLastModifiedTime());
Wait.pause(150);
before = getCache().cacheTimeMillis();
region.put(key, value);
CacheStatistics eStats = region.getEntry(key).getStatistics();
after = getCache().cacheTimeMillis();
assertInRange(before, after, rStats.getLastAccessedTime());
assertInRange(before, after, rStats.getLastModifiedTime());
assertInRange(before, after, rootStats.getLastAccessedTime());
assertInRange(before, after, rootStats.getLastModifiedTime());
assertInRange(before - ESTAT_RES, after + ESTAT_RES, eStats.getLastAccessedTime());
assertInRange(before - ESTAT_RES, after + ESTAT_RES, eStats.getLastModifiedTime());
oldBefore = before;
oldAfter = after;
Wait.pause(150);
before = getCache().cacheTimeMillis();
region.get(key);
after = getCache().cacheTimeMillis();
assertInRange(before, after, rStats.getLastAccessedTime());
assertInRange(oldBefore, oldAfter, rStats.getLastModifiedTime());
assertInRange(before, after, rootStats.getLastAccessedTime());
assertInRange(oldBefore, oldAfter, rootStats.getLastModifiedTime());
assertInRange(before - ESTAT_RES, after + ESTAT_RES, eStats.getLastAccessedTime());
assertInRange(oldBefore - ESTAT_RES, oldAfter + ESTAT_RES, eStats.getLastModifiedTime());
long oldOldBefore = oldBefore;
long oldOldAfter = oldAfter;
oldBefore = before;
oldAfter = after;
Wait.pause(150);
before = getCache().cacheTimeMillis();
region.create(key2, null);
CacheStatistics eStats2 = region.getEntry(key2).getStatistics();
after = getCache().cacheTimeMillis();
assertInRange(before, after, rStats.getLastAccessedTime());
assertInRange(before, after, rStats.getLastModifiedTime());
assertInRange(before, after, rootStats.getLastAccessedTime());
assertInRange(before, after, rootStats.getLastModifiedTime());
assertInRange(oldBefore - ESTAT_RES, oldAfter + ESTAT_RES, eStats.getLastAccessedTime());
assertInRange(oldOldBefore - ESTAT_RES, oldOldAfter + ESTAT_RES, eStats.getLastModifiedTime());
assertInRange(before - ESTAT_RES, after + ESTAT_RES, eStats2.getLastAccessedTime());
assertInRange(before - ESTAT_RES, after + ESTAT_RES, eStats2.getLastModifiedTime());
// Invalidation and destruction do not update the modification/access
// times
oldBefore = before;
oldAfter = after;
Wait.pause(150);
before = getCache().cacheTimeMillis();
region.invalidate(key2);
after = getCache().cacheTimeMillis();
assertInRange(oldBefore - ESTAT_RES, oldAfter + ESTAT_RES, eStats2.getLastAccessedTime());
assertInRange(oldBefore - ESTAT_RES, oldAfter + ESTAT_RES, eStats2.getLastModifiedTime());
assertInRange(oldBefore, oldAfter, rStats.getLastAccessedTime());
assertInRange(oldBefore, oldAfter, rStats.getLastModifiedTime());
assertInRange(oldBefore, oldAfter, rootStats.getLastAccessedTime());
assertInRange(oldBefore, oldAfter, rootStats.getLastModifiedTime());
Wait.pause(150);
before = getCache().cacheTimeMillis();
region.destroy(key2);
after = getCache().cacheTimeMillis();
assertInRange(oldBefore, oldAfter, rStats.getLastAccessedTime());
assertInRange(oldBefore, oldAfter, rStats.getLastModifiedTime());
assertInRange(oldBefore, oldAfter, rootStats.getLastAccessedTime());
assertInRange(oldBefore, oldAfter, rootStats.getLastModifiedTime());
before = getCache().cacheTimeMillis();
Region sub = region.createSubregion("sub", region.getAttributes());
after = getCache().cacheTimeMillis();
assertInRange(before, after, rStats.getLastAccessedTime());
assertInRange(before, after, rStats.getLastModifiedTime());
assertInRange(before, after, rootStats.getLastAccessedTime());
assertInRange(before, after, rootStats.getLastModifiedTime());
oldBefore = before;
oldAfter = after;
before = getCache().cacheTimeMillis();
sub.destroyRegion();
after = getCache().cacheTimeMillis();
assertInRange(oldBefore, oldAfter, rStats.getLastAccessedTime());
assertInRange(oldBefore, oldAfter, rStats.getLastModifiedTime());
assertInRange(oldBefore, oldAfter, rootStats.getLastAccessedTime());
assertInRange(oldBefore, oldAfter, rootStats.getLastModifiedTime());
}
/** The last time an entry was accessed */
protected static volatile long lastAccessed;
protected static volatile long lastModified;
protected static volatile long lastModifiedLocal;
protected static volatile long lastModifiedRemote;
/**
* Tests that distributed operations update the {@link CacheStatistics#getLastModifiedTime last
* modified time}, but not the {@link CacheStatistics#getLastAccessedTime last accessed time}. It
* also validates that distributed operations do not affect the hit and miss counts in remote
* caches.
*/
@Test
public void testDistributedStats() {
final String name = getUniqueName();
final Object key = "KEY";
final Object value = "VALUE";
SerializableRunnable create = new CacheSerializableRunnable("Create Region") {
@Override
public void run2() throws CacheException {
AttributesFactory factory = new AttributesFactory();
factory.setScope(Scope.DISTRIBUTED_ACK);
factory.setEarlyAck(false);
factory.setStatisticsEnabled(true);
createRegion(name, factory.create());
}
};
Host host = Host.getHost(0);
VM vm0 = host.getVM(0);
VM vm1 = host.getVM(1);
vm0.invoke(create);
vm1.invoke(create);
vm0.invoke(new CacheSerializableRunnable("Define entry") {
@Override
public void run2() throws CacheException {
Region region = getRootRegion().getSubregion(name);
// region.create(key, null);
region.put(key, value);
CacheStatistics stats = region.getEntry(key).getStatistics();
lastAccessed = stats.getLastAccessedTime();
lastModified = stats.getLastModifiedTime();
getCache().getLogger()
.fine("DEFINE: lastAccessed: " + lastAccessed + ", lastModified: " + lastModified);
assertEquals(0, stats.getHitCount());
assertEquals(0, stats.getMissCount());
assertTrue(lastModified > 0);
lastModifiedLocal = lastModified;
}
});
vm1.invoke(new CacheSerializableRunnable("Net search") {
@Override
public void run2() throws CacheException {
Region region = getRootRegion().getSubregion(name);
Object result = region.get(key);
assertEquals(value, result);
CacheStatistics stats = region.getEntry(key).getStatistics();
lastModifiedRemote = stats.getLastModifiedTime();
getCache().getLogger().fine("NETSEARCH: lastAccessed: " + stats.getLastAccessedTime()
+ ", lastModified: " + stats.getLastModifiedTime());
}
});
vm0.invoke(new CacheSerializableRunnable("Verify stats") {
@Override
public void run2() throws CacheException {
Region region = getRootRegion().getSubregion(name);
CacheStatistics stats = region.getEntry(key).getStatistics();
assertEquals(lastAccessed, stats.getLastAccessedTime());
assertEquals(lastModified, stats.getLastModifiedTime());
assertEquals(0, stats.getHitCount());
assertEquals(0, stats.getMissCount());
}
});
assertEquals(lastModifiedLocal, lastModifiedRemote);
// make sure at least 100ms have passed; otherwise, the update
// may not actually bump the statistics
Wait.pause(100);
vm1.invoke(new CacheSerializableRunnable("Update") {
@Override
public void run2() throws CacheException {
Region region = getRootRegion().getSubregion(name);
// assertNull(region.getEntry(key));
region.put(key, value);
assertEquals(value, region.getEntry(key).getValue());
CacheStatistics stats = region.getEntry(key).getStatistics();
getCache().getLogger().fine("UPDATE: lastAccessed: " + stats.getLastAccessedTime()
+ ", lastModified: " + stats.getLastModifiedTime());
}
});
final long errorMargin = 50;
vm0.invoke(new CacheSerializableRunnable("Verify stats") {
@Override
public void run2() throws CacheException {
Region region = getRootRegion().getSubregion(name);
CacheStatistics stats = region.getEntry(key).getStatistics();
long ta = stats.getLastAccessedTime();
long tm = stats.getLastModifiedTime();
long hc = stats.getHitCount();
long mc = stats.getMissCount();
getCache().getLogger().fine("VERIFY: lastAccessed: " + ta + ", lastModified: " + tm);
assertTrue("lastAccessedTime was " + ta + " but was expected to be > " + lastAccessed,
lastAccessed < (ta + errorMargin));
assertTrue("lastAccessed=" + lastAccessed + " should be < stats.getLastModifiedTime=" + tm,
lastAccessed < (tm + errorMargin));
assertEquals(0, hc);
assertEquals(0, mc);
lastAccessed = stats.getLastAccessedTime();
lastModified = stats.getLastModifiedTime();
}
});
vm1.invoke(new CacheSerializableRunnable("Invalidate") {
@Override
public void run2() throws CacheException {
Region region = getRootRegion().getSubregion(name);
region.invalidate(key);
}
});
vm0.invoke(new CacheSerializableRunnable("Verify stats") {
@Override
public void run2() throws CacheException {
Region region = getRootRegion().getSubregion(name);
CacheStatistics stats = region.getEntry(key).getStatistics();
assertEquals(lastAccessed, stats.getLastAccessedTime());
assertEquals(lastModified, stats.getLastModifiedTime());
assertEquals(0, stats.getHitCount());
assertEquals(0, stats.getMissCount());
}
});
vm1.invoke(new CacheSerializableRunnable("Destroy Entry") {
@Override
public void run2() throws CacheException {
Region region = getRootRegion().getSubregion(name);
region.destroy(key);
}
});
vm0.invoke(new CacheSerializableRunnable("Verify region stats") {
@Override
public void run2() throws CacheException {
Region region = getRootRegion().getSubregion(name);
CacheStatistics stats = region.getStatistics();
// lastAccessed var contains stat from an Entry, which may be
// up to 100 ms off from stat in Region because Entry has
// less precision
// assertIndexDetailsEquals(lastAccessed, stats.getLastAccessedTime(), 100);
assertEquals(0, stats.getHitCount());
assertEquals(0, stats.getMissCount());
}
});
}
/**
* Tests that an attempt to get statistics when they are disabled results in a
* {@link StatisticsDisabledException}.
*/
@Test
public void testDisabledStatistics() throws CacheException {
String name = getUniqueName();
Object key = "KEY";
Object value = "VALUE";
AttributesFactory factory = new AttributesFactory();
factory.setStatisticsEnabled(false);
Region region = createRegion(name, factory.create());
try {
region.getStatistics();
fail("Should have thrown a StatisticsDisabledException");
} catch (StatisticsDisabledException ex) {
// pass...
}
region.put(key, value);
Region.Entry entry = region.getEntry(key);
try {
entry.getStatistics();
} catch (StatisticsDisabledException ex) {
// pass...
}
}
}