package com.linkedin.databus.core.util;
/*
*
* Copyright 2013 LinkedIn Corp. All rights reserved
*
* 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.
*
*/
import java.nio.ByteBuffer;
import org.apache.log4j.Logger;
import com.linkedin.databus.core.DatabusRuntimeException;
public class BufferPositionParser
{
public static final String MODULE = BufferPositionParser.class.getName();
public static final Logger LOG = Logger.getLogger(MODULE);
public static final int NUM_BITS_IN_LONG = 64;
private final long _offsetMask;
private final long _indexMask;
private final long _genIdMask;
private final int _offsetShift;
private final int _indexShift;
private final int _genIdShift;
private final long _totalBufferSize;
/**
* BufferPosition contains 3 elements
*
* a) Offset - the offset within a single bufferIndex
* b) Index - the index to the byteBuffer in the EventBuffer
* c) GenId - The number of rotations the eventBuffer has seen
*
* IndividualBufferSize determines the number of bits to be used for offset
* numIndices determines the number of bits to be used for index lookup
* The rest of the bits are used to store genIds
*
* @param individualBufferSize the number of bytes each byteBuffer can hold
* @param numIndices the number of buffers in the EventBuffer
*
*/
public BufferPositionParser(int individualBufferSize, int numIndices)
{
/* Expect the input args to be +ve */
assert(individualBufferSize > 0);
assert(numIndices > 0);
LOG.info("Individual Buffer Size: " + Long.toHexString(individualBufferSize));
LOG.info("Num Buffers: " + numIndices);
int offsetLength = Long.toBinaryString(individualBufferSize - 1).length();
int indexLength = Long.toBinaryString(numIndices - 1).length();
_offsetShift = 0;
_indexShift = offsetLength;
_genIdShift = offsetLength + indexLength;
long signedBitMask = Long.MAX_VALUE;
long signMask = ~signedBitMask;
LOG.info("Offset Length: " + offsetLength);
_offsetMask = ~(signMask >> (NUM_BITS_IN_LONG - offsetLength - 1)) ;
//System.out.println("Signed Mask is: " + Long.toHexString(_offsetMask));
_indexMask = ((~(signMask >> (NUM_BITS_IN_LONG - offsetLength - indexLength - 1))) ^ _offsetMask) ;
_genIdMask = (Long.MAX_VALUE ^ _indexMask ^ _offsetMask);
_totalBufferSize = individualBufferSize * numIndices;
LOG.info("buffer position for the EventBuffer: " + toString());
}
/**
* @return the offset mask used for parsing the offset
*/
public long getOffsetMask()
{
return _offsetMask;
}
/**
* @return the index mask used for parsing the index
*/
public long getIndexMask()
{
return _indexMask;
}
/**
* @return the genId Mask used for parsing the genId
*/
public long getGenIdMask()
{
return _genIdMask;
}
/**
* @return the offset shift used for parsing the offset
*/
public int getOffsetShift()
{
return _offsetShift;
}
/**
* @return the index shift used for parsing the index
*/
public int getIndexShift()
{
return _indexShift;
}
/**
* @return the genId shift used for parsing the genId
*/
public int getGenIdShift()
{
return _genIdShift;
}
/**
* @return true if position < 0
*/
public boolean init(long position)
{
return (position < 0);
}
public long encode(long genId, int index, int offset)
{
final long shiftedOffset = ((long)offset) << _offsetShift;
if (offset < 0 || shiftedOffset > _offsetMask)
{
throw new DatabusRuntimeException("invalid position offset: " + offset);
}
final long shiftedIndex = ((long)index) << _indexShift;
if (index < 0 || shiftedIndex > _indexMask)
{
throw new DatabusRuntimeException("invalid position index: " + index);
}
final long shiftedGenId = (genId) << _genIdShift;
if (genId < 0 || shiftedGenId > _genIdMask)
{
throw new DatabusRuntimeException("invalid position gen-id: " + genId);
}
final long pos = shiftedGenId | shiftedIndex | shiftedOffset;
return pos;
}
/**
* Sets the offset in the position.
*
* @param position position where offset needs to be set
* @param offset offset to be set
* @return the position with offset set
*/
public long setOffset(long position, int offset)
{
return encode(bufferGenId(position), bufferIndex(position), offset);
}
/**
* Sets the index in the position.
*
* @param position old position
* @param index index to be set
* @return the position with the new index
*/
public long setIndex(long position, int index)
{
return encode(bufferGenId(position), index, bufferOffset(position));
}
/**
* Sets the GenId in the position.
*
* @param position old position
* @param genId GenId to be set
* @return the buffer position with the new genId
*/
public long setGenId(long position, long genId)
{
return encode(genId, bufferIndex(position), bufferOffset(position));
}
/**
* Removes the genId and returns the address part (index + offset).
*
* @param position position whose address needs to be parsed
* @return the address component of the position
*/
public long address(long position)
{
return setGenId(position,0);
}
/**
* Gets the bufferIndex of the position.
*
* @param position encoded position
* @return the index encoded in the position
*/
public int bufferIndex(long position)
{
long index = ((position & _indexMask) >> _indexShift);
return (int) index;
}
/**
* Gets the index in the position.
*
* @param position encoded position
* @return the offset encoded in the position
*/
public int bufferOffset(long position)
{
int offset = (int) ((position & _offsetMask) >> _offsetShift);
return offset;
}
/**
* Gets the GenId in the position.
*
* @param position encoded position
* @return the genId encoded in the position
*/
public long bufferGenId(long position)
{
long genId = ((position & _genIdMask) >> _genIdShift);
return genId;
}
/**
* Increments the GenId stored in the position by 1 and resets the index and offset to 0.
*
* @param position position to be incremented
* @return the incremented position
*/
public long incrementGenId(long currentPosition)
{
return encode(bufferGenId(currentPosition) + 1, 0, 0);
}
/**
* Generates the gen-id position at the beginning of the next ByteBuffer.
*
* @param position position to be incremented
* @param buffers the list of buffers in the eventBuffer which is the universe for the position
* @return the incremented position
*/
public long incrementIndex(long currentPosition, ByteBuffer[] buffers)
{
final int bufferIndex = bufferIndex(currentPosition);
final int nextIndex = (bufferIndex + 1) % buffers.length;
final long nextGenId = (0 == nextIndex) ? bufferGenId(currentPosition) + 1 :
bufferGenId(currentPosition);
return encode(nextGenId, nextIndex, 0);
}
/**
* Increments the offset stored in the position.
*
* @param position position to be incremented
* @param increment the increment value
* @param buffers list of buffers, which is the universe for the position
* @return the incremented position
*/
public long incrementOffset(long currentPosition,
int increment,
ByteBuffer[] buffers)
{
return incrementOffset(currentPosition, increment, buffers, false, false);
}
/**
* Increments the offset stored in the position.
*
* @param position position to be incremented
* @param increment the increment value
* @param buffers list of buffers, which is the universe for the position
* @param noLimit ignore the buffer limit and use its capacity value instead
* @return the incremented position
*/
public long incrementOffset(long currentPosition,
int increment,
ByteBuffer[] buffers,
boolean noLimit)
{
return incrementOffset(currentPosition, increment, buffers, false, noLimit);
}
private long incrementOffset(long currentPosition,
int increment,
ByteBuffer[] buffers,
boolean okToRegress,
boolean noLimit)
{
//System.out.println("Asked to increment " + toString(currentPosition) + " by " + increment);
int offset = bufferOffset(currentPosition);
int bufferIndex = bufferIndex(currentPosition);
int currentBufferLimit = buffers[bufferIndex].limit();
int currentBufferCapacity = buffers[bufferIndex].capacity();
//System.out.println("Offset = " + offset + ", BufferIndex = " + bufferIndex + ", currentBufferLimit = " + currentBufferLimit + ", currentBufferCapacity = " + currentBufferCapacity);
if (noLimit)
{
currentBufferLimit = currentBufferCapacity;
}
int proposedOffset = offset + increment;
if (proposedOffset < currentBufferLimit)
{
return (currentPosition + increment); // Safe to do this because offsets are the LSB's
// alternately
// return setOffset(currentPosition, proposedOffset);
}
if (proposedOffset == currentBufferLimit)
{
// move to the next buffer's position 0
return incrementIndex(currentPosition, buffers);
}
if (okToRegress)
{
return incrementIndex(currentPosition, buffers);
}
// proposedOffset > currentBufferLimit and not okToRegress.... weird
LOG.error("proposedOffset " + proposedOffset + " is greater than " + currentBufferLimit + " capacity = " + currentBufferCapacity);
LOG.error("currentPosition = " + toString(currentPosition) + " increment = " + increment);
throw new DatabusRuntimeException("Error in _bufferOffset"); // not changing to "buffer position" since widely recognized error
}
/**
* Advances the given position to point to valid data in the buffer (<= limit).
*
* If position == limit, index gets incremented (and possibly wrapped, i.e.,
* genId incremented and index/offset reset). If position > limit, throws
* runtime exception.
*
* @param currentPosition the position to be sanitized (== normalized, i.e., point
* at valid data rather than at the start of invalid data)
* @param buffers list of bytebuffers in the eventBuffer
* @return position pointing to valid data
*/
public long sanitize(long currentPosition, ByteBuffer[] buffers)
{
return incrementOffset(currentPosition, 0, buffers, false, false);
}
/**
* Advances the given position to point to valid data in the buffer (<= limit).
*
* If position == limit, or if okToRegress and position > limit, index gets
# incremented (and possibly wrapped, i.e., genId incremented and index/offset
* reset). If !okToRegress and position > limit, throws runtime exception.
*
* @param currentPosition the position to be sanitized (== normalized, i.e., point
* at valid data rather than at the start of invalid data)
* @param buffers list of bytebuffers in the eventBuffer
* @param okToRegress ??
* @return position pointing to valid data
*/
public long sanitize(long currentPosition, ByteBuffer[] buffers, boolean okToRegress)
{
return incrementOffset(currentPosition, 0, buffers, okToRegress, false);
}
/**
* Interpret the position in a human-readable way.
*
* @param position position to be converted to String
* @return the descriptive version of the elements stored in the position
*/
public String toString(long position)
{
return toString(position, null);
}
/**
* Interpret the position in a human-readable way, including ByteBuffer limit/capacity.
*
* @param position position to be converted to String
* @param buffers the list of ByteBuffers composing the event buffer, which is the universe for the position
* @return the descriptive version of the elements stored in the position
*/
public String toString(long position, ByteBuffer[] buffers)
{
if (position < 0)
{
return "["+position+"]";
}
else
{
final int index = bufferIndex(position);
StringBuilder sb = new StringBuilder();
sb.append(position)
.append(":[GenId=")
.append(bufferGenId(position))
.append(";Index=")
.append(index);
if (buffers != null && index >= 0 && index < buffers.length) // defer any out-of-range exception to end
{
sb.append("(lim=")
.append(buffers[index].limit())
.append(",cap=")
.append(buffers[index].capacity())
.append(")");
}
sb.append(";Offset=")
.append(bufferOffset(position))
.append("]");
if (buffers != null && (index < 0 || index >= buffers.length))
{
throw new DatabusRuntimeException("invalid position index: " + sb.toString());
}
return sb.toString();
}
}
@Override
public String toString()
{
return "BufferPositionParser [_offsetMask=" + _offsetMask + ", _indexMask=" + _indexMask +
", _genIdMask=" + _genIdMask + ", _offsetShift=" + _offsetShift +
", _indexShift=" + _indexShift + ", _genIdShift=" + _genIdShift +
", _totalBufferSize=" + _totalBufferSize + "]";
}
/**
* Asserts for the 2 conditions:
* 1. An end position (including genId) is greater than or equal to the start (including genId).
* 2. The difference between the end and start is not greater than the EVB space.
*/
public void assertSpan(long start, long end, boolean isDebugEnabled)
{
long diff = end - start;
double maxSpan = Math.pow(2, _genIdShift);
StringBuilder msg = null;
if ((diff < 0) || (diff > maxSpan) || isDebugEnabled)
{
msg = new StringBuilder();
msg.append("Assert Span: Start is: " + toString(start) + ", End: " +toString(end));
msg.append(", Diff: " + diff + ", MaxSpan: " + maxSpan + ", totalBufferSize: " + _totalBufferSize);
if ((diff < 0) || (diff > maxSpan))
{
LOG.fatal("Span Assertion failed: " + msg.toString());
throw new RuntimeException(msg.toString());
}
else
{
LOG.debug(msg.toString());
}
}
}
}