package rocks.inspectit.shared.cs.indexing.storage.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
import org.apache.commons.lang.builder.ToStringBuilder;
import rocks.inspectit.shared.all.cmr.cache.IObjectSizes;
import rocks.inspectit.shared.all.communication.DefaultData;
import rocks.inspectit.shared.all.indexing.IIndexQuery;
import rocks.inspectit.shared.cs.indexing.LeafTask;
import rocks.inspectit.shared.cs.indexing.impl.IndexingException;
import rocks.inspectit.shared.cs.indexing.storage.AbstractStorageDescriptor;
import rocks.inspectit.shared.cs.indexing.storage.IStorageDescriptor;
import rocks.inspectit.shared.cs.indexing.storage.IStorageTreeComponent;
import rocks.inspectit.shared.cs.storage.util.StorageUtil;
/**
* This leaf for the storage is not keeping the {@link SimpleStorageDescriptor} for each element.
* This leaf just keeps track of the total size of elements saved. It is expected that this leafs
* are used when the elements does not have to be retrieved singularly.
* <P>
* The clear advantage with this leaf is size of the leaf in memory/disk. However, the price for
* this is not being able to find one concrete element in the leaf. When this is necessary, the
* other leaf type must be used.
* <P>
* <b>Important:</b><br>
* Changing this class can cause the break of the backward/forward compatibility of the storage in
* the way that we will not be able to read any data from the storage. Thus, please be careful with
* performing any changes until there is a proper mechanism to protect against this problem.
*
*
* @author Ivan Senic
*
* @param <E>
* Type of data that can be indexed.
*/
public class LeafWithNoDescriptors<E extends DefaultData> implements IStorageTreeComponent<E> {
/**
* Max size of one range is 8MB. If more that is going to be written in the same leaf then we
* need to split data in several ranges. The problem can occur if we write too much in one leaf
* (like > 100MB) loading of this data on the UI has to be in one request, making this request a
* high impact on the memory that can make OOME in the UI.
*/
private static final long MAX_RANGE_SIZE = 8388608;
/**
* Leaf id.
*/
private int id;
/**
* Bounded descriptor.
*/
private transient BoundedDecriptor boundedDecriptor = new BoundedDecriptor();
/**
* This is a not thread-safe list of the descriptors that need to be synchronized on our own.
*/
private List<SimpleStorageDescriptor> descriptors = new ArrayList<>();
/**
* Default constructor. Generates leaf ID.
*/
public LeafWithNoDescriptors() {
this(StorageUtil.getRandomInt());
}
/**
* Secondary constructor. Assigns the leaf with the ID.
*
* @param id
* ID to be assigned to the leaf.
*/
public LeafWithNoDescriptors(int id) {
this.id = id;
}
/**
* {@inheritDoc}
*/
@Override
public IStorageDescriptor put(E element) throws IndexingException {
return boundedDecriptor;
}
/**
* {@inheritDoc}
* <p>
* Calling this method with this implementation of the storage leaf is highly discouraged,
* because we simply can not find one element, and have to return descriptor for all elements.
*/
@Override
public IStorageDescriptor get(E element) {
throw new UnsupportedOperationException("LeafWithNoDescriptors can not answer on the single element query.");
}
/**
* {@inheritDoc}
*/
@Override
public List<IStorageDescriptor> query(IIndexQuery query) {
List<IStorageDescriptor> list = new ArrayList<>();
for (SimpleStorageDescriptor simpleStorageDescriptor : descriptors) {
list.add(new StorageDescriptor(id, simpleStorageDescriptor));
}
return list;
}
/**
* {@inheritDoc}
*/
@Override
public List<IStorageDescriptor> query(IIndexQuery query, ForkJoinPool forkJoinPool) {
return forkJoinPool.invoke(getTaskForForkJoinQuery(query));
}
/**
* {@inheritDoc}
*/
@Override
public IStorageDescriptor getAndRemove(E template) {
// FIXME Not good, when write fails, we need to remove somehow the peace that is missing..
return null;
};
/**
* {@inheritDoc}
*/
@Override
public synchronized void preWriteFinalization() {
optimiseDescriptors(descriptors);
}
/**
* {@inheritDoc}
*/
@Override
public long getComponentSize(IObjectSizes objectSizes) {
long size = objectSizes.getSizeOfObjectHeader();
size += objectSizes.getPrimitiveTypesSize(2, 0, 1, 0, 0, 0);
size += objectSizes.getSizeOf(descriptors);
// manually calculate the descriptor size
long descriptorSize = objectSizes.alignTo8Bytes(objectSizes.getSizeOfObjectObject() + objectSizes.getPrimitiveTypesSize(0, 0, 1, 0, 1, 0));
size += descriptors.size() * descriptorSize;
return objectSizes.alignTo8Bytes(size);
}
/**
* Adds the written position and size by updating the existing {@link #descriptors} list.
*
* @param position
* Position that was written.
* @param size
* Size.
*/
private synchronized void addPositionAndSize(long position, long size) {
for (SimpleStorageDescriptor storageDescriptor : descriptors) {
if (((storageDescriptor.getSize() + size) < MAX_RANGE_SIZE) && storageDescriptor.join(position, size)) {
return;
}
}
SimpleStorageDescriptor storageDescriptor = new SimpleStorageDescriptor(position, (int) size);
descriptors.add(storageDescriptor);
}
/**
* Optimizes the list of the descriptors so that necessary joining is done. This method will
* also assure that no descriptor has bigger size than {@value #MAX_RANGE_SIZE} bytes, as
* defined in {@link #MAX_RANGE_SIZE}.
*
* @param descriptorList
* List of {@link StorageDescriptor}s to optimize.
*/
private void optimiseDescriptors(List<SimpleStorageDescriptor> descriptorList) {
for (int i = 0; i < (descriptorList.size() - 1); i++) {
for (int j = i + 1; j < descriptorList.size(); j++) {
SimpleStorageDescriptor descriptor = descriptors.get(i);
SimpleStorageDescriptor other = descriptors.get(j);
if (((descriptor.getSize() + other.getSize()) < MAX_RANGE_SIZE) && descriptor.join(other)) {
descriptorList.remove(j);
j--;
}
}
}
}
/**
* Gets {@link #id}.
*
* @return {@link #id}
*/
int getId() {
return id;
}
/**
* {@inheritDoc}
*/
@Override
public RecursiveTask<List<IStorageDescriptor>> getTaskForForkJoinQuery(IIndexQuery query) {
return new LeafTask<>(this, query);
}
/**
* This is the private implementation of {@link IStorageDescriptor} that reflects operations
* directly to the leaf. The usage of descriptor outside of this class should not be changed,
* but calling some methods won't create any actions.
*
* @author Ivan Senic
*
*/
private class BoundedDecriptor extends AbstractStorageDescriptor {
/**
* {@inheritDoc}
*/
@Override
public int getChannelId() {
return id;
}
/**
* {@inheritDoc}
*/
@Override
public void setChannelId(int channelId) {
}
/**
* {@inheritDoc}
*/
@Override
public long getPosition() {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public long getSize() {
return 0;
}
/**
* {@inheritDoc}
*/
@Override
public void setPositionAndSize(long position, long size) {
addPositionAndSize(position, size);
}
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
ToStringBuilder toStringBuilder = new ToStringBuilder(this);
toStringBuilder.append("descriptors", descriptors);
return toStringBuilder.toString();
}
}