/*
* Copyright Terracotta, Inc.
*
* 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 org.ehcache.impl.internal.store.heap.bytesized;
import org.ehcache.config.Eviction;
import org.ehcache.config.EvictionAdvisor;
import org.ehcache.config.ResourcePools;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.core.spi.function.NullaryFunction;
import org.ehcache.event.EventType;
import org.ehcache.core.events.StoreEventDispatcher;
import org.ehcache.core.spi.store.StoreAccessException;
import org.ehcache.core.spi.store.heap.LimitExceededException;
import org.ehcache.expiry.Duration;
import org.ehcache.expiry.Expirations;
import org.ehcache.expiry.Expiry;
import org.ehcache.core.spi.function.BiFunction;
import org.ehcache.core.spi.function.Function;
import org.ehcache.impl.copy.IdentityCopier;
import org.ehcache.impl.internal.events.TestStoreEventDispatcher;
import org.ehcache.impl.internal.sizeof.DefaultSizeOfEngine;
import org.ehcache.impl.internal.store.heap.OnHeapStore;
import org.ehcache.impl.internal.store.heap.holders.CopiedOnHeapValueHolder;
import org.ehcache.core.spi.time.SystemTimeSource;
import org.ehcache.internal.TestTimeSource;
import org.ehcache.core.spi.time.TimeSource;
import org.ehcache.sizeof.SizeOf;
import org.ehcache.sizeof.SizeOfFilterSource;
import org.ehcache.core.spi.store.Store;
import org.ehcache.core.spi.store.events.StoreEvent;
import org.ehcache.core.spi.store.events.StoreEventListener;
import org.ehcache.spi.copy.Copier;
import org.ehcache.spi.serialization.Serializer;
import org.ehcache.core.spi.store.heap.SizeOfEngine;
import org.hamcrest.Matcher;
import org.junit.Test;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import static org.ehcache.config.builders.ResourcePoolsBuilder.newResourcePoolsBuilder;
import static org.ehcache.internal.store.StoreCreationEventListenerTest.eventType;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* @author Abhilash
*
*/
public class ByteAccountingTest {
private static final Copier DEFAULT_COPIER = new IdentityCopier();
private static final SizeOfEngine SIZE_OF_ENGINE = new DefaultSizeOfEngine(Long.MAX_VALUE, Long.MAX_VALUE);
private static final String KEY = "key";
private static final String VALUE = "value";
private static final long SIZE_OF_KEY_VALUE_PAIR = getSize(KEY, VALUE);
private static final SizeOfFilterSource FILTERSOURCE = new SizeOfFilterSource(true);
private static final SizeOf SIZEOF = SizeOf.newInstance(FILTERSOURCE.getFilters());
<K, V> OnHeapStoreForTests<K, V> newStore() {
return newStore(SystemTimeSource.INSTANCE, Expirations.noExpiration(), Eviction.noAdvice());
}
<K, V> OnHeapStoreForTests<K, V> newStore(int capacity) {
return newStore(SystemTimeSource.INSTANCE, Expirations.noExpiration(), Eviction.noAdvice(), capacity);
}
<K, V> OnHeapStoreForTests<K, V> newStore(TimeSource timeSource, Expiry<? super K, ? super V> expiry) {
return newStore(timeSource, expiry, Eviction.noAdvice());
}
<K, V> OnHeapStoreForTests<K, V> newStore(TimeSource timeSource, Expiry<? super K, ? super V> expiry, EvictionAdvisor<? super K, ? super V> evictionAdvisor) {
return newStore(timeSource, expiry, evictionAdvisor, 100);
}
private <K, V> OnHeapStoreForTests<K, V> newStore(final TimeSource timeSource, final Expiry<? super K, ? super V> expiry, final EvictionAdvisor<? super K, ? super V> evictionAdvisor,
final int capacity) {
return new OnHeapStoreForTests<K, V>(new Store.Configuration<K, V>() {
@SuppressWarnings("unchecked")
@Override
public Class<K> getKeyType() {
return (Class<K>) String.class;
}
@SuppressWarnings("unchecked")
@Override
public Class<V> getValueType() {
return (Class<V>) String.class;
}
@Override
public EvictionAdvisor<? super K, ? super V> getEvictionAdvisor() {
return evictionAdvisor;
}
@Override
public ClassLoader getClassLoader() {
return getClass().getClassLoader();
}
@Override
public Expiry<? super K, ? super V> getExpiry() {
return expiry;
}
@Override
public ResourcePools getResourcePools() {
return newResourcePoolsBuilder().heap(capacity, MemoryUnit.KB).build();
}
@Override
public Serializer<K> getKeySerializer() {
throw new AssertionError("By-ref heap store using serializers!");
}
@Override
public Serializer<V> getValueSerializer() {
throw new AssertionError("By-ref heap store using serializers!");
}
@Override
public int getDispatcherConcurrency() {
return 0;
}
}, timeSource, new DefaultSizeOfEngine(Long.MAX_VALUE, Long.MAX_VALUE), new TestStoreEventDispatcher<K, V>());
}
@Test
public void testPut() throws StoreAccessException {
OnHeapStoreForTests<String, String> store = newStore();
store.put(KEY, VALUE);
assertThat(store.getCurrentUsageInBytes(), is(SIZE_OF_KEY_VALUE_PAIR));
}
@Test
public void testPutUpdate() throws StoreAccessException {
OnHeapStoreForTests<String, String> store = newStore();
store.put(KEY, VALUE);
String otherValue = "otherValue";
store.put(KEY, otherValue);
long delta = SIZEOF.deepSizeOf(otherValue) - SIZEOF.deepSizeOf(VALUE);
assertThat(store.getCurrentUsageInBytes(), is(SIZE_OF_KEY_VALUE_PAIR + delta));
}
@Test
public void testPutExpiryOnCreate() throws StoreAccessException {
TestTimeSource timeSource = new TestTimeSource(1000L);
OnHeapStoreForTests<String, String> store = newStore(timeSource, Expirations.builder().setCreate(Duration.ZERO).build());
store.put(KEY, VALUE);
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testPutExpiryOnUpdate() throws StoreAccessException {
TestTimeSource timeSource = new TestTimeSource(1000L);
OnHeapStoreForTests<String, String> store = newStore(timeSource, Expirations.builder().setUpdate(Duration.ZERO).build());
store.put(KEY, VALUE);
store.put(KEY, "otherValue");
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testRemove() throws StoreAccessException {
OnHeapStoreForTests<String, String> store = newStore();
store.put(KEY, VALUE);
long beforeRemove = store.getCurrentUsageInBytes();
store.remove("Another Key");
assertThat(store.getCurrentUsageInBytes(), is(beforeRemove));
store.remove(KEY);
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testRemoveExpired() throws StoreAccessException {
TestTimeSource timeSource = new TestTimeSource(1000L);
OnHeapStoreForTests<String, String> store = newStore(timeSource, ttlCreation600ms());
store.put(KEY, VALUE);
timeSource.advanceTime(1000L);
store.remove(KEY);
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testRemoveTwoArg() throws StoreAccessException {
OnHeapStoreForTests<String, String> store = newStore();
store.put(KEY, VALUE);
long beforeRemove = store.getCurrentUsageInBytes();
store.remove(KEY, "Another value");
assertThat(store.getCurrentUsageInBytes(), is(beforeRemove));
store.remove(KEY, VALUE);
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testRemoveTwoArgExpired() throws StoreAccessException {
TestTimeSource timeSource = new TestTimeSource(1000L);
OnHeapStoreForTests<String, String> store = newStore(timeSource, ttlCreation600ms());
store.put(KEY, VALUE);
timeSource.advanceTime(1000L);
store.remove(KEY, "whatever value, it is expired anyway");
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testRemoveTwoArgExpiresOnAccess() throws StoreAccessException {
TestTimeSource timeSource = new TestTimeSource(1000L);
OnHeapStoreForTests<String, String> store = newStore(timeSource, Expirations.builder().setAccess(Duration.ZERO).build());
store.put(KEY, VALUE);
store.remove(KEY, "whatever value, it expires on access");
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testReplace() throws StoreAccessException {
OnHeapStoreForTests<String, String> store = newStore();
store.put(KEY, VALUE);
long beforeReplace = store.getCurrentUsageInBytes();
store.replace("Another Key", "Another Value");
assertThat(store.getCurrentUsageInBytes(), is(beforeReplace));
String toReplace = "Replaced Value";
store.replace(KEY, toReplace);
long delta = SIZEOF.deepSizeOf(toReplace) - SIZEOF.deepSizeOf(VALUE);
long afterReplace = store.getCurrentUsageInBytes();
assertThat(afterReplace - beforeReplace, is(delta));
//when delta is negative
store.replace(KEY, VALUE);
assertThat(afterReplace - store.getCurrentUsageInBytes(), is(delta));
}
@Test
public void testReplaceTwoArgExpired() throws StoreAccessException {
TestTimeSource timeSource = new TestTimeSource(1000L);
OnHeapStoreForTests<String, String> store = newStore(timeSource, ttlCreation600ms());
store.put(KEY, VALUE);
timeSource.advanceTime(1000L);
store.replace(KEY, "whatever value, it is expired anyway");
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testReplaceTwoArgExpiresOnUpdate() throws StoreAccessException {
TestTimeSource timeSource = new TestTimeSource(1000L);
OnHeapStoreForTests<String, String> store = newStore(timeSource, Expirations.builder().setUpdate(Duration.ZERO).build());
store.put(KEY, VALUE);
store.replace(KEY, "whatever value, it expires on update");
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testReplaceThreeArg() throws StoreAccessException {
OnHeapStoreForTests<String, String> store = newStore();
store.put(KEY, VALUE);
long beforeReplace = store.getCurrentUsageInBytes();
store.replace(KEY, "Another Value", VALUE);
assertThat(store.getCurrentUsageInBytes(), is(beforeReplace));
String toReplace = "Replaced Value";
store.replace(KEY, VALUE, toReplace);
long delta = SIZEOF.deepSizeOf(toReplace) - SIZEOF.deepSizeOf(VALUE);
long afterReplace = store.getCurrentUsageInBytes();
assertThat(afterReplace - beforeReplace, is(delta));
//when delta is negative
store.replace(KEY, toReplace, VALUE);
assertThat(afterReplace - store.getCurrentUsageInBytes(), is(delta));
}
@Test
public void testReplaceThreeArgExpired() throws StoreAccessException {
TestTimeSource timeSource = new TestTimeSource(1000L);
OnHeapStoreForTests<String, String> store = newStore(timeSource, ttlCreation600ms());
store.put(KEY, VALUE);
timeSource.advanceTime(1000L);
store.replace(KEY, VALUE, "whatever value, it is expired anyway");
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testReplaceThreeArgExpiresOnUpdate() throws StoreAccessException {
TestTimeSource timeSource = new TestTimeSource(1000L);
OnHeapStoreForTests<String, String> store = newStore(timeSource, Expirations.builder().setUpdate(Duration.ZERO).build());
store.put(KEY, VALUE);
store.replace(KEY, VALUE, "whatever value, it expires on update");
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testPutIfAbsent() throws StoreAccessException {
OnHeapStoreForTests<String, String> store = newStore();
store.putIfAbsent(KEY, VALUE);
long current = store.getCurrentUsageInBytes();
assertThat(current, is(SIZE_OF_KEY_VALUE_PAIR));
store.putIfAbsent(KEY, "New Value to Put");
assertThat(store.getCurrentUsageInBytes(), is(current));
}
@Test
public void testPutIfAbsentOverExpired() throws StoreAccessException {
TestTimeSource timeSource = new TestTimeSource(1000L);
OnHeapStoreForTests<String, String> store = newStore(timeSource, ttlCreation600ms());
store.put(KEY, "an expired value");
timeSource.advanceTime(1000L);
store.putIfAbsent(KEY, VALUE);
assertThat(store.getCurrentUsageInBytes(), is(SIZE_OF_KEY_VALUE_PAIR));
}
@Test
public void testPutIfAbsentExpiresOnAccess() throws StoreAccessException {
TestTimeSource timeSource = new TestTimeSource(1000L);
OnHeapStoreForTests<String, String> store = newStore(timeSource, Expirations.builder().setAccess(Duration.ZERO).build());
store.put(KEY, VALUE);
store.putIfAbsent(KEY, "another value ... whatever");
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testInvalidate() throws StoreAccessException {
OnHeapStoreForTests<Object, Object> store = newStore();
store.put(KEY, VALUE);
store.invalidate(KEY);
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testSilentInvalidate() throws StoreAccessException {
OnHeapStoreForTests<Object, Object> store = newStore();
store.put(KEY, VALUE);
store.silentInvalidate(KEY, new Function<Store.ValueHolder<Object>, Void>() {
@Override
public Void apply(Store.ValueHolder<Object> objectValueHolder) {
// Nothing to do
return null;
}
});
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testComputeRemove() throws StoreAccessException {
OnHeapStoreForTests<String, String> store = newStore();
store.put(KEY, VALUE);
assertThat(store.getCurrentUsageInBytes(), is(SIZE_OF_KEY_VALUE_PAIR));
store.compute("another", new BiFunction<String, String, String>() {
@Override
public String apply(String a, String b) {
return null;
}
});
assertThat(store.getCurrentUsageInBytes(), is(SIZE_OF_KEY_VALUE_PAIR));
store.compute(KEY, new BiFunction<String, String, String>() {
@Override
public String apply(String a, String b) {
return null;
}
});
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testCompute() throws StoreAccessException {
OnHeapStoreForTests<String, String> store = newStore();
store.compute(KEY, new BiFunction<String, String, String>() {
@Override
public String apply(String a, String b) {
return VALUE;
}
});
assertThat(store.getCurrentUsageInBytes(), is(SIZE_OF_KEY_VALUE_PAIR));
final String replace = "Replace the original value";
long delta = SIZEOF.deepSizeOf(replace) - SIZEOF.deepSizeOf(VALUE);
store.compute(KEY, new BiFunction<String, String, String>() {
@Override
public String apply(String a, String b) {
return replace;
}
});
assertThat(store.getCurrentUsageInBytes(), is(SIZE_OF_KEY_VALUE_PAIR + delta));
}
@Test
public void testComputeExpiryOnAccess() throws StoreAccessException {
TestTimeSource timeSource = new TestTimeSource(100L);
OnHeapStoreForTests<String, String> store = newStore(timeSource, Expirations.builder().setAccess(Duration.ZERO).build());
store.put(KEY, VALUE);
store.compute(KEY, new BiFunction<String, String, String>() {
@Override
public String apply(String s, String s2) {
return s2;
}
}, new NullaryFunction<Boolean>() {
@Override
public Boolean apply() {
return false;
}
});
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testComputeExpiryOnUpdate() throws StoreAccessException {
TestTimeSource timeSource = new TestTimeSource(100L);
OnHeapStoreForTests<String, String> store = newStore(timeSource, Expirations.builder().setUpdate(Duration.ZERO).build());
store.put(KEY, VALUE);
store.compute(KEY, new BiFunction<String, String, String>() {
@Override
public String apply(String s, String s2) {
return s2;
}
});
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testComputeIfAbsent() throws StoreAccessException {
OnHeapStoreForTests<String, String> store = newStore();
store.computeIfAbsent(KEY, new Function<String, String>() {
@Override
public String apply(String a) {
return VALUE;
}
});
assertThat(store.getCurrentUsageInBytes(), is(SIZE_OF_KEY_VALUE_PAIR));
store.computeIfAbsent(KEY, new Function<String, String>() {
@Override
public String apply(String a) {
return "Should not be replaced";
}
});
assertThat(store.getCurrentUsageInBytes(), is(SIZE_OF_KEY_VALUE_PAIR));
}
@Test
public void testComputeIfAbsentExpireOnCreate() throws StoreAccessException {
TestTimeSource timeSource = new TestTimeSource(100L);
OnHeapStoreForTests<String, String> store = newStore(timeSource, Expirations.builder().setCreate(Duration.ZERO).build());
store.computeIfAbsent(KEY, new Function<String, String>() {
@Override
public String apply(String s) {
return VALUE;
}
});
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testComputeIfAbsentExpiryOnAccess() throws StoreAccessException {
TestTimeSource timeSource = new TestTimeSource(100L);
OnHeapStoreForTests<String, String> store = newStore(timeSource, Expirations.builder().setAccess(Duration.ZERO).build());
store.put(KEY, VALUE);
store.computeIfAbsent(KEY, new Function<String, String>() {
@Override
public String apply(String s) {
fail("should not be called");
return s;
}
});
assertThat(store.getCurrentUsageInBytes(), is(0L));
}
@Test
public void testExpiry() throws StoreAccessException {
TestTimeSource timeSource = new TestTimeSource();
OnHeapStoreForTests<String, String> store = newStore(timeSource, Expirations.timeToLiveExpiration(new Duration(1, TimeUnit.MILLISECONDS)));
store.put(KEY, VALUE);
assertThat(store.getCurrentUsageInBytes(), is(SIZE_OF_KEY_VALUE_PAIR));
timeSource.advanceTime(1);
assertThat(store.getCurrentUsageInBytes(), is(SIZE_OF_KEY_VALUE_PAIR));
assertThat(store.get(KEY), nullValue());
assertThat(store.getCurrentUsageInBytes(), is(0l));
}
@Test
public void testEviction() throws StoreAccessException {
OnHeapStoreForTests<String, String> store = newStore(1);
@SuppressWarnings("unchecked")
StoreEventListener<String, String> listener = mock(StoreEventListener.class);
store.getStoreEventSource().addEventListener(listener);
store.put(KEY, VALUE);
assertThat(store.getCurrentUsageInBytes(), is(SIZE_OF_KEY_VALUE_PAIR));
String key1 = "key1";
char[] chars = new char[250];
Arrays.fill(chars, (char) 0xffff);
String value1 = new String(chars);
long requiredSize = getSize(key1, value1);
store.put(key1, value1);
Matcher<StoreEvent<String, String>> matcher = eventType(EventType.EVICTED);
verify(listener, times(1)).onEvent(argThat(matcher));
if (store.get(key1) != null) {
assertThat(store.getCurrentUsageInBytes(), is(requiredSize));
} else {
assertThat(store.getCurrentUsageInBytes(), is(SIZE_OF_KEY_VALUE_PAIR));
}
}
private Expiry<Object, Object> ttlCreation600ms() {
return Expirations.builder().setCreate(new Duration(600L, TimeUnit.MILLISECONDS)).build();
}
static long getSize(String key, String value) {
@SuppressWarnings("unchecked")
CopiedOnHeapValueHolder<String> valueHolder = new CopiedOnHeapValueHolder<String>(value, 0L, 0L, true, DEFAULT_COPIER);
long size = 0L;
try {
size = SIZE_OF_ENGINE.sizeof(key, valueHolder);
} catch (LimitExceededException e) {
fail();
}
return size;
}
static class OnHeapStoreForTests<K, V> extends OnHeapStore<K, V> {
private static final Copier DEFAULT_COPIER = new IdentityCopier();
@SuppressWarnings("unchecked")
OnHeapStoreForTests(final Configuration<K, V> config, final TimeSource timeSource,
final SizeOfEngine engine, StoreEventDispatcher<K, V> eventDispatcher) {
super(config, timeSource, DEFAULT_COPIER, DEFAULT_COPIER, engine, eventDispatcher);
}
long getCurrentUsageInBytes() {
return super.byteSized();
}
}
}