package rocks.inspectit.shared.all.storage.nio;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.slf4j.Logger;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import rocks.inspectit.shared.all.cmr.property.spring.PropertyUpdate;
import rocks.inspectit.shared.all.spring.logger.Log;
import rocks.inspectit.shared.all.storage.nio.bytebuffer.ByteBufferFactory;
import rocks.inspectit.shared.all.util.UnderlyingSystemInfo;
import rocks.inspectit.shared.all.util.UnderlyingSystemInfo.JvmProvider;
/**
* This component enables reusing of the {@link ByteBuffer}s. It creates a set of initial buffers
* that are available for "rent". The amount of buffers created at the beginning is going to be
* enough to satisfies {@link #poolMinCapacity}. The total amount of buffers created will never go
* above {@link #poolMaxCapacity}.
* <P>
* This class does not have protection against not returning a buffer, thus all components using the
* provider have to ensure they will return the buffer to the provider.
*
* @author Ivan Senic
*
*/
@Component
public class ByteBufferProvider extends GenericObjectPool<ByteBuffer> implements InitializingBean {
/**
* The log of this class.
*/
@Log
Logger log;
/**
* Default buffer size that will be used if not initial buffer list size is provided to create
* one buffer.
*/
public static final int DEFAULT_BUFFER_CAPACITY = 1024 * 1024;
/**
* Max waiting for the buffer to be available in milliseconds.
*/
private static final long MAX_WAIT = 5000;
/**
* Buffer size.
*/
@Value(value = "${storage.bufferSize}")
private int bufferSize = DEFAULT_BUFFER_CAPACITY;
/**
* Pool factory.
*/
private ByteBufferFactory poolFactory;
/**
* Amount of bytes this pool should have at minimum.
*/
@Value(value = "${storage.bufferPoolMinCapacity}")
private long poolMinCapacity;
/**
* Amount of bytes this pool can acquire in total.
*/
@Value(value = "${storage.bufferPoolMaxCapacity}")
private long poolMaxCapacity;
/**
* Min size based on the percentage of the direct memory available to the JVM.
*/
@Value(value = "${storage.bufferPoolMinDirectMemoryOccupancy}")
private float bufferPoolMinDirectMemoryOccupancy;
/**
* Min size based on the percentage of the direct memory available to the JVM.
*/
@Value(value = "${storage.bufferPoolMaxDirectMemoryOccupancy}")
private float bufferPoolMaxDirectMemoryOccupancy;
/**
* Default constructor.
*/
public ByteBufferProvider() {
this(new ByteBufferFactory(DEFAULT_BUFFER_CAPACITY));
}
/**
* @param poolFactory
* Pool factory to be used.
*/
protected ByteBufferProvider(ByteBufferFactory poolFactory) {
this(poolFactory, MAX_WAIT);
}
/**
* @param poolFactory
* Pool factory to be used.
* @param maxWait
* max wait in milliseconds for buffer to be available
*/
ByteBufferProvider(ByteBufferFactory poolFactory, long maxWait) {
super(poolFactory);
this.poolFactory = poolFactory;
this.setMaxWait(maxWait);
this.setWhenExhaustedAction(WHEN_EXHAUSTED_BLOCK);
}
/**
* Returns buffer from the pool. This method waits for buffer to be available or creates a new
* buffer if the {@link #createdCapacity} is less than {@link #poolMaxCapacity}.
* <p>
* Note that if pool maximum is reached, the calling thread will wait for {@value #MAX_WAIT}
* milliseconds for a buffer to become available. After this time if no buffer becomes available
* {@link IOException} will thrown.
*
* @return {@link ByteBuffer}
* @throws IOException
* If buffer can not be acquired.
*/
public ByteBuffer acquireByteBuffer() throws IOException {
try {
return super.borrowObject();
} catch (Exception e) {
IOException ioException = new IOException("Byte buffer pool can not borrow a valid byte buffer.");
ioException.initCause(e);
throw ioException;
}
}
/**
* Gives back the {@link ByteBuffer} to the pool, so that others can use it.
*
* @param byteBuffer
* {@link ByteBuffer} to put back to the pool.
*/
public void releaseByteBuffer(ByteBuffer byteBuffer) {
try {
super.returnObject(byteBuffer);
} catch (Exception e) {
log.error("Byte buffer can not be returned to the byte buffer pool.", e);
return;
}
}
/**
* @return Returns the pool size.
*/
public int getBufferPoolSize() {
return super.getNumIdle();
}
/**
* Gets {@link #createdCapacity}.
*
* @return {@link #createdCapacity}
*/
public long getCreatedCapacity() {
return (super.getNumActive() + super.getNumIdle()) * bufferSize;
}
/**
* Gets {@link #availableCapacity}.
*
* @return {@link #availableCapacity}
*/
public long getAvailableCapacity() {
return super.getNumIdle() * bufferSize;
}
/**
* <i>This setter can be removed when the Spring3.0 on the GUI side is working properly.</i>
*
* @param poolMaxCapacity
* the poolMaxCapacity to set
*/
public void setPoolMaxCapacity(long poolMaxCapacity) {
this.poolMaxCapacity = poolMaxCapacity;
}
/**
* Sets {@link #poolMinCapacity}.
*
* @param poolMinCapacity
* New value for {@link #poolMinCapacity}
*/
public void setPoolMinCapacity(long poolMinCapacity) {
this.poolMinCapacity = poolMinCapacity;
}
/**
* Sets {@link #bufferPoolMinDirectMemoryOccupancy}.
*
* @param bufferPoolMinDirectMemoryOccupancy
* New value for {@link #bufferPoolMinDirectMemoryOccupancy}
*/
public void setBufferPoolMinDirectMemoryOccupancy(float bufferPoolMinDirectMemoryOccupancy) {
this.bufferPoolMinDirectMemoryOccupancy = bufferPoolMinDirectMemoryOccupancy;
}
/**
* Sets {@link #bufferPoolMaxDirectMemoryOccupancy}.
*
* @param bufferPoolMaxDirectMemoryOccupancy
* New value for {@link #bufferPoolMaxDirectMemoryOccupancy}
*/
public void setBufferPoolMaxDirectMemoryOccupancy(float bufferPoolMaxDirectMemoryOccupancy) {
this.bufferPoolMaxDirectMemoryOccupancy = bufferPoolMaxDirectMemoryOccupancy;
}
/**
* Sets {@link #bufferSize}.
*
* @param bufferSize
* New value for {@link #bufferSize}
*/
public void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
/**
* Initializes the pool.
*/
protected void init() {
if (bufferPoolMinDirectMemoryOccupancy > bufferPoolMaxDirectMemoryOccupancy) {
throw new BeanInitializationException("Settings for the byte buffer pool are not correct. bufferPoolMinDirectMemoryOccupancy (" + bufferPoolMaxDirectMemoryOccupancy
+ ") is greater than bufferPoolMaxDirectMemoryOccupancy (" + bufferPoolMinDirectMemoryOccupancy + ")");
}
if (poolMinCapacity > poolMaxCapacity) {
throw new BeanInitializationException(
"Settings for the byte buffer pool are not correct. poolMinCapacity (" + poolMinCapacity + ") is greater than poolMaxCapacity (" + poolMaxCapacity + ")");
}
updatePoolProperties();
}
/**
* Updates the buffer capacity, min & max idle buffers based on the current properties.
* <p>
* This is an automated properties update execution method.
*/
@PropertyUpdate(properties = { "storage.bufferSize", "storage.bufferPoolMinCapacity", "storage.bufferPoolMaxCapacity", "storage.bufferPoolMinDirectMemoryOccupancy",
"storage.bufferPoolMaxDirectMemoryOccupancy" })
protected void updatePoolProperties() {
// assume that the maxDirect memory is 64MB
long maxDirectMemory = 64 * 1024 * 1024;
try {
if (UnderlyingSystemInfo.JVM_PROVIDER.equals(JvmProvider.SUN) || UnderlyingSystemInfo.JVM_PROVIDER.equals(JvmProvider.ORACLE)) {
Class<?> vmClazz = Class.forName("sun.misc.VM");
Method directMemoryMethod = vmClazz.getMethod("maxDirectMemory");
directMemoryMethod.setAccessible(true);
maxDirectMemory = (Long) directMemoryMethod.invoke(null);
}
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Exception occurred trying to use the class sun.misc.VM via reflection", e);
}
}
if (poolMinCapacity > (long) (maxDirectMemory * bufferPoolMinDirectMemoryOccupancy)) {
poolMinCapacity = (long) (maxDirectMemory * bufferPoolMinDirectMemoryOccupancy);
}
if (poolMaxCapacity > (long) (maxDirectMemory * bufferPoolMaxDirectMemoryOccupancy)) {
poolMaxCapacity = (long) (maxDirectMemory * bufferPoolMaxDirectMemoryOccupancy);
}
poolFactory.setBufferCapacity(bufferSize);
int maxIdle = (int) (poolMinCapacity / bufferSize);
int maxActive = (int) (poolMaxCapacity / bufferSize);
super.setMaxIdle(maxIdle);
super.setMaxActive(maxActive);
}
/**
* {@inheritDoc}
*/
@Override
public void afterPropertiesSet() throws Exception {
init();
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
ToStringBuilder toStringBuilder = new ToStringBuilder(this);
toStringBuilder.append("poolMinCapacity", poolMinCapacity);
toStringBuilder.append("poolMaxCapacity", poolMaxCapacity);
return toStringBuilder.toString();
}
}