package rocks.inspectit.server.cache.impl;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.slf4j.LoggerFactory;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import rocks.inspectit.server.cache.IBufferElement;
import rocks.inspectit.server.test.AbstractTestNGLogSupport;
import rocks.inspectit.shared.all.cmr.cache.IObjectSizes;
import rocks.inspectit.shared.all.communication.DefaultData;
import rocks.inspectit.shared.cs.indexing.buffer.IBufferTreeComponent;
/**
* Testing of the functionality of the {@link AtomicBuffer}.
*
* @author Ivan Senic
*
*/
@SuppressWarnings("PMD")
public class AtomicBufferTest extends AbstractTestNGLogSupport {
/**
* Class under test.
*/
private AtomicBuffer<DefaultData> buffer;
@Mock
private BufferProperties bufferProperties;
@Mock
private IObjectSizes objectSizes;
@Mock
private IBufferTreeComponent<DefaultData> indexingTree;
/**
* Init.
*
* @throws Exception
*/
@BeforeMethod
public void init() throws Exception {
MockitoAnnotations.initMocks(this);
buffer = new AtomicBuffer<>();
buffer.bufferProperties = bufferProperties;
buffer.objectSizes = objectSizes;
buffer.indexingTree = indexingTree;
buffer.log = LoggerFactory.getLogger(AtomicBuffer.class);
when(bufferProperties.getIndexingTreeCleaningThreads()).thenReturn(1);
buffer.postConstruct();
}
/**
* Test that insertion will be in order.
*/
@Test
public void insertElements() {
DefaultData defaultData = mock(DefaultData.class);
IBufferElement<DefaultData> element1 = new BufferElement<>(defaultData);
IBufferElement<DefaultData> element2 = new BufferElement<>(defaultData);
buffer.put(element1);
buffer.put(element2);
assertThat(buffer.getInsertedElemenets(), is(2L));
assertThat(element1.getNextElement(), is(equalTo(element2)));
}
/**
* Tests that eviction will remove right amount of elements.
*
* @throws Exception
*/
@Test(invocationCount = 5)
public void eviction() throws Exception {
Random random = new Random();
long elements = 1 + random.nextInt(10000);
elements += elements % 2;
int analyzers = 1 + random.nextInt(3);
// evict half of the buffer
when(bufferProperties.getInitialBufferSize()).thenReturn(elements);
when(bufferProperties.getEvictionOccupancyPercentage()).thenReturn(0.1f);
when(bufferProperties.getEvictionFragmentSizePercentage()).thenReturn(0.5f);
buffer.postConstruct();
DefaultData defaultData = mock(DefaultData.class);
when(defaultData.getObjectSize(objectSizes)).thenReturn(1L);
BufferAnalyzer[] analyzerArray = new BufferAnalyzer[analyzers];
for (int i = 0; i < analyzers; i++) {
BufferAnalyzer bufferAnalyzer = new BufferAnalyzer(buffer);
bufferAnalyzer.start();
analyzerArray[i] = bufferAnalyzer;
}
for (int i = 0; i < elements; i++) {
IBufferElement<DefaultData> bufferElement = new BufferElement<>(defaultData);
buffer.put(bufferElement);
}
// wait to be analyzed
while (buffer.getAnalyzedElements() < elements) {
Thread.sleep(50);
}
buffer.evict();
for (BufferAnalyzer bufferAnalyzer : analyzerArray) {
bufferAnalyzer.interrupt();
}
assertThat(buffer.getCurrentSize(), is(elements / 2));
assertThat(buffer.getInsertedElemenets(), is(elements));
assertThat(buffer.getEvictedElemenets(), is(elements / 2));
}
/**
* Tests that size of the elements is correctly analyzed and added to the buffer size.
*
* @throws Exception
*/
@Test(invocationCount = 5)
public void analysisAndSize() throws Exception {
Random random = new Random();
// tests needs at least three elements
long elements = 3 + random.nextInt(10000);
elements += elements % 2;
int analyzers = 1 + random.nextInt(3);
// eviction needed when 99% of the buffer is full
when(bufferProperties.getInitialBufferSize()).thenReturn(elements);
when(bufferProperties.getEvictionOccupancyPercentage()).thenReturn(0.99f);
buffer.postConstruct();
DefaultData defaultData = mock(DefaultData.class);
when(defaultData.getObjectSize(objectSizes)).thenReturn(1L);
// start analyzers
BufferAnalyzer[] analyzerArray = new BufferAnalyzer[analyzers];
for (int i = 0; i < analyzers; i++) {
BufferAnalyzer bufferAnalyzer = new BufferAnalyzer(buffer);
bufferAnalyzer.start();
analyzerArray[i] = bufferAnalyzer;
}
IBufferElement<DefaultData> first = null;
long firstRunElements = (long) (elements * 0.99f) - 1;
for (int i = 0; i < firstRunElements; i++) {
IBufferElement<DefaultData> bufferElement = new BufferElement<>(defaultData);
if (0 == i) {
first = bufferElement;
}
buffer.put(bufferElement);
}
// wait to be analyzed
while (buffer.getAnalyzedElements() < firstRunElements) {
Thread.sleep(50);
}
assertThat(buffer.getCurrentSize(), is(firstRunElements));
assertThat(buffer.getOccupancyPercentage(), is((float) firstRunElements / elements));
assertThat(buffer.shouldEvict(), is(false));
// add rest for activating eviction
for (int i = 0; i < (elements - firstRunElements); i++) {
IBufferElement<DefaultData> bufferElement = new BufferElement<>(defaultData);
buffer.put(bufferElement);
}
// wait to be analyzed
while (buffer.getAnalyzedElements() < elements) {
Thread.sleep(50);
}
// interrupt analyzers
for (BufferAnalyzer bufferAnalyzer : analyzerArray) {
bufferAnalyzer.interrupt();
}
assertThat(buffer.getCurrentSize(), is(elements));
assertThat(buffer.getOccupancyPercentage(), is(1f));
assertThat(buffer.shouldEvict(), is(true));
assertThat(buffer.getAnalyzedElements(), is(elements));
for (int i = 0; i < elements; i++) {
assertThat(first.isAnalyzed(), is(true));
first = first.getNextElement();
}
}
/**
* Tests that expansion rate will be used on elements size.
*
* @throws Exception
*/
@Test(invocationCount = 5)
public void analysisAndSizeWithExpansionRate() throws Exception {
Random random = new Random();
long elements = 1 + random.nextInt(10000);
elements += elements % 2;
int analyzers = 1 + random.nextInt(3);
float expansionRate = 0.1f;
long elementSize = 10;
when(objectSizes.getObjectSecurityExpansionRate()).thenReturn(expansionRate);
buffer.postConstruct();
DefaultData defaultData = mock(DefaultData.class);
when(defaultData.getObjectSize(objectSizes)).thenReturn(elementSize);
// start analyzers
BufferAnalyzer[] analyzerArray = new BufferAnalyzer[analyzers];
for (int i = 0; i < analyzers; i++) {
BufferAnalyzer bufferAnalyzer = new BufferAnalyzer(buffer);
bufferAnalyzer.start();
analyzerArray[i] = bufferAnalyzer;
}
for (int i = 0; i < elements; i++) {
IBufferElement<DefaultData> bufferElement = new BufferElement<>(defaultData);
buffer.put(bufferElement);
}
// wait to be analyzed
while (buffer.getAnalyzedElements() < elements) {
Thread.sleep(50);
}
// interrupt analyzers
for (BufferAnalyzer bufferAnalyzer : analyzerArray) {
bufferAnalyzer.interrupt();
}
assertThat(buffer.getCurrentSize(), is((long) (elements * elementSize * (1 + expansionRate))));
}
/**
* Test that elements are correctly indexed.
*
* @throws Exception
*/
@Test(invocationCount = 5)
public void indexing() throws Exception {
Random random = new Random();
long elements = 1 + random.nextInt(10000);
elements += elements % 2;
int indexers = 1 + random.nextInt(3);
when(bufferProperties.getIndexingWaitTime()).thenReturn(10L);
DefaultData defaultData = mock(DefaultData.class);
when(defaultData.getObjectSize(objectSizes)).thenReturn(1L);
// start analyzer
BufferAnalyzer bufferAnalyzer = new BufferAnalyzer(buffer);
bufferAnalyzer.start();
// start indexers
BufferIndexer[] indexerArray = new BufferIndexer[indexers];
for (int i = 0; i < indexers; i++) {
BufferIndexer bufferIndexer = new BufferIndexer(buffer);
bufferIndexer.start();
indexerArray[i] = bufferIndexer;
}
IBufferElement<DefaultData> first = null;
for (int i = 0; i < elements; i++) {
IBufferElement<DefaultData> bufferElement = new BufferElement<>(defaultData);
if (0 == i) {
first = bufferElement;
}
buffer.put(bufferElement);
}
// wait for the elements to be analyzed and indexed
while ((buffer.getAnalyzedElements() < elements) || (buffer.getIndexedElements() < elements)) {
Thread.sleep(50);
}
// interrupt workers
bufferAnalyzer.interrupt();
for (BufferIndexer bufferIndexer : indexerArray) {
bufferIndexer.interrupt();
}
for (int i = 0; i < elements; i++) {
assertThat(first.isIndexed(), is(true));
first = first.getNextElement();
}
assertThat(buffer.getIndexedElements(), is(elements));
verify(indexingTree, times((int) elements)).put(defaultData);
}
/**
* Tests that the tree size calculations and maintenance is done.
*
* @throws Exception
*/
@Test(invocationCount = 5)
public void indexingTreeMaintenance() throws Exception {
Random random = new Random();
long flagsSetOnBytes = 30L;
long elements = 1 + random.nextInt(10000);
// when adding 30 bytes, maintenance should be done
// indexing tree always reports 10 bytes size
when(bufferProperties.getInitialBufferSize()).thenReturn(elements);
when(bufferProperties.getEvictionOccupancyPercentage()).thenReturn(0.5f);
when(bufferProperties.getEvictionFragmentSizePercentage()).thenReturn(0.35f);
when(bufferProperties.getFlagsSetOnBytes(anyLong())).thenReturn(flagsSetOnBytes);
when(bufferProperties.getIndexingWaitTime()).thenReturn(10L);
when(indexingTree.getComponentSize(objectSizes)).thenReturn(10L);
buffer.postConstruct();
DefaultData defaultData = mock(DefaultData.class);
when(defaultData.getObjectSize(objectSizes)).thenReturn(1L);
BufferAnalyzer bufferAnalyzer = new BufferAnalyzer(buffer);
bufferAnalyzer.start();
BufferIndexer bufferIndexer = new BufferIndexer(buffer);
bufferIndexer.start();
for (int i = 0; i < elements; i++) {
IBufferElement<DefaultData> bufferElement = new BufferElement<>(defaultData);
buffer.put(bufferElement);
}
// wait for the elements to be analyzed and indexed
while ((buffer.getAnalyzedElements() < elements) || (buffer.getIndexedElements() < elements)) {
Thread.sleep(50);
}
if (elements > flagsSetOnBytes) {
assertThat(buffer.getCurrentSize(), is(elements + 10L));
verify(indexingTree, atLeast(1)).getComponentSize(objectSizes);
} else {
assertThat(buffer.getCurrentSize(), is(elements));
verify(indexingTree, times(0)).getComponentSize(objectSizes);
}
// evict
assertThat(buffer.shouldEvict(), is(true));
buffer.evict();
long evicted = buffer.getEvictedElemenets();
// now add two more element to active the cleaning of the indexing tree
// the cleaning should be done after adding the first one, so we can execute the verify
// afterwards
buffer.put(new BufferElement<>(defaultData));
buffer.put(new BufferElement<>(defaultData));
// wait for the element to be analyzed and indexed
while ((buffer.getAnalyzedElements() < (elements + 2)) || (buffer.getIndexedElements() < (elements + 2))) {
Thread.sleep(50);
}
bufferAnalyzer.interrupt();
bufferIndexer.interrupt();
// only if we evicted enough we can expect the clean
if (evicted > flagsSetOnBytes) {
verify(indexingTree, times(1)).cleanWithRunnable(Matchers.<ExecutorService> anyObject());
} else {
verify(indexingTree, times(0)).cleanWithRunnable(Matchers.<ExecutorService> anyObject());
}
}
/**
* Tests that the clean works properly.
*
* @throws Exception
*/
@Test(invocationCount = 5)
public void clean() throws Exception {
Random random = new Random();
long elements = 1 + random.nextInt(10000);
when(bufferProperties.getInitialBufferSize()).thenReturn(elements);
when(bufferProperties.getIndexingWaitTime()).thenReturn(5L);
DefaultData defaultData = mock(DefaultData.class);
when(defaultData.getObjectSize(objectSizes)).thenReturn(1L);
BufferAnalyzer bufferAnalyzer = new BufferAnalyzer(buffer);
bufferAnalyzer.start();
BufferIndexer bufferIndexer = new BufferIndexer(buffer);
bufferIndexer.start();
for (int i = 0; i < (elements / 2); i++) {
IBufferElement<DefaultData> bufferElement = new BufferElement<>(defaultData);
buffer.put(bufferElement);
buffer.put(bufferElement);
// execute clear all the time, let s push this all threads
buffer.clearAll();
}
for (int i = 0; i < elements; i++) {
IBufferElement<DefaultData> bufferElement = new BufferElement<>(defaultData);
buffer.put(bufferElement);
}
// wait for the elements to be analyzed and indexed
while ((buffer.getAnalyzedElements() < elements) || (buffer.getIndexedElements() < elements)) {
Thread.sleep(500);
}
bufferAnalyzer.interrupt();
bufferIndexer.interrupt();
assertThat(buffer.getCurrentSize(), is(elements));
assertThat(buffer.getAnalyzedElements(), is(elements));
assertThat(buffer.getIndexedElements(), is(elements));
assertThat(buffer.getEvictedElemenets(), is(0L));
}
}