/* * Copyright 2014-2017 Real Logic Ltd. * * 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 io.aeron.logbuffer; import org.agrona.DirectBuffer; import org.agrona.concurrent.UnsafeBuffer; import static io.aeron.logbuffer.FrameDescriptor.FRAME_ALIGNMENT; import static io.aeron.protocol.DataHeaderFlyweight.HEADER_LENGTH; import static org.agrona.BitUtil.CACHE_LINE_LENGTH; import static org.agrona.BitUtil.SIZE_OF_INT; import static org.agrona.BitUtil.SIZE_OF_LONG; /** * Layout description for log buffers which contains partitions of terms with associated term meta data, * plus ending with overall log meta data. * * <pre> * +----------------------------+ * | Term 0 | * +----------------------------+ * | Term 1 | * +----------------------------+ * | Term 2 | * +----------------------------+ * | Log Meta Data | * +----------------------------+ * </pre> */ public class LogBufferDescriptor { /** * The number of partitions the log is divided into. */ public static final int PARTITION_COUNT = 3; /** * Section index for which buffer contains the log meta data. */ public static final int LOG_META_DATA_SECTION_INDEX = PARTITION_COUNT; /** * Minimum buffer length for a log term */ public static final int TERM_MIN_LENGTH = 64 * 1024; // ******************************* // *** Log Meta Data Constants *** // ******************************* /** * Offset within the meta data where the tail values are stored. */ public static final int TERM_TAIL_COUNTERS_OFFSET; /** * Offset within the log meta data where the active partition index is stored. */ public static final int LOG_ACTIVE_PARTITION_INDEX_OFFSET; /** * Offset within the log meta data where the time of last SM is stored. */ public static final int LOG_TIME_OF_LAST_SM_OFFSET; /** * Offset within the log meta data where the active term id is stored. */ public static final int LOG_INITIAL_TERM_ID_OFFSET; /** * Offset within the log meta data which the length field for the frame header is stored. */ public static final int LOG_DEFAULT_FRAME_HEADER_LENGTH_OFFSET; /** * Offset within the log meta data which the MTU length is stored; */ public static final int LOG_MTU_LENGTH_OFFSET; /** * Offset within the log meta data which the */ public static final int LOG_CORRELATION_ID_OFFSET; /** * Offset at which the default frame headers begin. */ public static final int LOG_DEFAULT_FRAME_HEADER_OFFSET; /** * Maximum length of a frame header. */ public static final int LOG_DEFAULT_FRAME_HEADER_MAX_LENGTH = CACHE_LINE_LENGTH * 2; static { int offset = 0; TERM_TAIL_COUNTERS_OFFSET = offset; offset += (SIZE_OF_LONG * PARTITION_COUNT); LOG_ACTIVE_PARTITION_INDEX_OFFSET = offset; offset = (CACHE_LINE_LENGTH * 2); LOG_TIME_OF_LAST_SM_OFFSET = offset; offset += (CACHE_LINE_LENGTH * 2); LOG_CORRELATION_ID_OFFSET = offset; LOG_INITIAL_TERM_ID_OFFSET = LOG_CORRELATION_ID_OFFSET + SIZE_OF_LONG; LOG_DEFAULT_FRAME_HEADER_LENGTH_OFFSET = LOG_INITIAL_TERM_ID_OFFSET + SIZE_OF_INT; LOG_MTU_LENGTH_OFFSET = LOG_DEFAULT_FRAME_HEADER_LENGTH_OFFSET + SIZE_OF_INT; offset += CACHE_LINE_LENGTH; LOG_DEFAULT_FRAME_HEADER_OFFSET = offset; LOG_META_DATA_LENGTH = offset + LOG_DEFAULT_FRAME_HEADER_MAX_LENGTH; } /** * Total length of the log meta data buffer in bytes. * * <pre> * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Tail Counter 0 | * | | * +---------------------------------------------------------------+ * | Tail Counter 1 | * | | * +---------------------------------------------------------------+ * | Tail Counter 2 | * | | * +---------------------------------------------------------------+ * | Active Partition Index | * +---------------------------------------------------------------+ * | Cache Line Padding ... * ... | * +---------------------------------------------------------------+ * | Time of Last Status Message | * | | * +---------------------------------------------------------------+ * | Cache Line Padding ... * ... | * +---------------------------------------------------------------+ * | Registration / Correlation ID | * | | * +---------------------------------------------------------------+ * | Initial Term Id | * +---------------------------------------------------------------+ * | Default Frame Header Length | * +---------------------------------------------------------------+ * | MTU Length | * +---------------------------------------------------------------+ * | Cache Line Padding ... * ... | * +---------------------------------------------------------------+ * | Default Frame Header ... * ... | * +---------------------------------------------------------------+ * </pre> */ public static final int LOG_META_DATA_LENGTH; /** * Check that term length is valid and alignment is valid. * * @param termLength to be checked. * @throws IllegalStateException if the length is not as expected. */ public static void checkTermLength(final int termLength) { if (termLength < TERM_MIN_LENGTH) { final String s = String.format( "Term length less than min length of %d, length=%d", TERM_MIN_LENGTH, termLength); throw new IllegalStateException(s); } if ((termLength & (FRAME_ALIGNMENT - 1)) != 0) { final String s = String.format( "Term length not a multiple of %d, length=%d", FRAME_ALIGNMENT, termLength); throw new IllegalStateException(s); } } /** * Get the value of the initial Term id used for this log. * * @param logMetaDataBuffer containing the meta data. * @return the value of the initial Term id used for this log. */ public static int initialTermId(final UnsafeBuffer logMetaDataBuffer) { return logMetaDataBuffer.getInt(LOG_INITIAL_TERM_ID_OFFSET); } /** * Set the initial term at which this log begins. Initial should be randomised so that stream does not get * reused accidentally. * * @param logMetaDataBuffer containing the meta data. * @param initialTermId value to be set. */ public static void initialTermId(final UnsafeBuffer logMetaDataBuffer, final int initialTermId) { logMetaDataBuffer.putInt(LOG_INITIAL_TERM_ID_OFFSET, initialTermId); } /** * Get the value of the MTU length used for this log. * * @param logMetaDataBuffer containing the meta data. * @return the value of the MTU length used for this log. */ public static int mtuLength(final UnsafeBuffer logMetaDataBuffer) { return logMetaDataBuffer.getInt(LOG_MTU_LENGTH_OFFSET); } /** * Set the MTU length used for this log. * * @param logMetaDataBuffer containing the meta data. * @param mtuLength value to be set. */ public static void mtuLength(final UnsafeBuffer logMetaDataBuffer, final int mtuLength) { logMetaDataBuffer.putInt(LOG_MTU_LENGTH_OFFSET, mtuLength); } /** * Get the value of the correlation ID for this log relating to the command which created it. * * @param logMetaDataBuffer containing the meta data. * @return the value of the correlation ID used for this log. */ public static long correlationId(final UnsafeBuffer logMetaDataBuffer) { return logMetaDataBuffer.getLong(LOG_CORRELATION_ID_OFFSET); } /** * Set the correlation ID used for this log relating to the command which created it. * * @param logMetaDataBuffer containing the meta data. * @param id value to be set. */ public static void correlationId(final UnsafeBuffer logMetaDataBuffer, final long id) { logMetaDataBuffer.putLong(LOG_CORRELATION_ID_OFFSET, id); } /** * Get the value of the time of last SM in {@link System#currentTimeMillis()}. * * @param logMetaDataBuffer containing the meta data. * @return the value of time of last SM */ public static long timeOfLastStatusMessage(final UnsafeBuffer logMetaDataBuffer) { return logMetaDataBuffer.getLongVolatile(LOG_TIME_OF_LAST_SM_OFFSET); } /** * Set the value of the time of last SM used by the producer of this log. * * @param logMetaDataBuffer containing the meta data. * @param timeInMillis value of the time of last SM in {@link System#currentTimeMillis()} */ public static void timeOfLastStatusMessage(final UnsafeBuffer logMetaDataBuffer, final long timeInMillis) { logMetaDataBuffer.putLongOrdered(LOG_TIME_OF_LAST_SM_OFFSET, timeInMillis); } /** * Get the value of the active partition index used by the producer of this log. Consumers may have a different * active index if they are running behind. The read is done with volatile semantics. * * @param logMetaDataBuffer containing the meta data. * @return the value of the active partition index used by the producer of this log. */ public static int activePartitionIndex(final UnsafeBuffer logMetaDataBuffer) { return logMetaDataBuffer.getIntVolatile(LOG_ACTIVE_PARTITION_INDEX_OFFSET); } /** * Set the value of the current active partition index for the producer using memory ordered semantics. * * @param logMetaDataBuffer containing the meta data. * @param activePartitionIndex value of the active partition index used by the producer of this log. */ public static void activePartitionIndexOrdered(final UnsafeBuffer logMetaDataBuffer, final int activePartitionIndex) { logMetaDataBuffer.putIntOrdered(LOG_ACTIVE_PARTITION_INDEX_OFFSET, activePartitionIndex); } /** * Set the value of the current active partition index for the producer. * * @param logMetaDataBuffer containing the meta data. * @param activePartitionIndex value of the active partition index used by the producer of this log. */ public static void activePartitionIndex(final UnsafeBuffer logMetaDataBuffer, final int activePartitionIndex) { logMetaDataBuffer.putInt(LOG_ACTIVE_PARTITION_INDEX_OFFSET, activePartitionIndex); } /** * Rotate to the next partition in sequence for the term id. * * @param currentIndex partition index * @return the next partition index */ public static int nextPartitionIndex(final int currentIndex) { return (currentIndex + 1) % PARTITION_COUNT; } /** * Determine the partition index to be used given the initial term and active term ids. * * @param initialTermId at which the log buffer usage began * @param activeTermId that is in current usage * @return the index of which buffer should be used */ public static int indexByTerm(final int initialTermId, final int activeTermId) { return (activeTermId - initialTermId) % PARTITION_COUNT; } /** * Determine the partition index based on number of terms that have passed. * * @param termCount for the number of terms that have passed. * @return the partition index for the term count. */ public static int indexByTermCount(final long termCount) { return (int)(termCount % PARTITION_COUNT); } /** * Determine the partition index given a stream position. * * @param position in the stream in bytes. * @param positionBitsToShift number of times to right shift the position for term count * @return the partition index for the position */ public static int indexByPosition(final long position, final int positionBitsToShift) { return (int)((position >>> positionBitsToShift) % PARTITION_COUNT); } /** * Compute the current position in absolute number of bytes. * * @param activeTermId active term id. * @param termOffset in the term. * @param positionBitsToShift number of times to left shift the term count * @param initialTermId the initial term id that this stream started on * @return the absolute position in bytes */ public static long computePosition( final int activeTermId, final int termOffset, final int positionBitsToShift, final int initialTermId) { final long termCount = activeTermId - initialTermId; // copes with negative activeTermId on rollover return (termCount << positionBitsToShift) + termOffset; } /** * Compute the current position in absolute number of bytes for the beginning of a term. * * @param activeTermId active term id. * @param positionBitsToShift number of times to left shift the term count * @param initialTermId the initial term id that this stream started on * @return the absolute position in bytes */ public static long computeTermBeginPosition( final int activeTermId, final int positionBitsToShift, final int initialTermId) { final long termCount = activeTermId - initialTermId; // copes with negative activeTermId on rollover return termCount << positionBitsToShift; } /** * Compute the term id from a position. * * @param position to calculate from * @param positionBitsToShift number of times to right shift the position * @param initialTermId the initial term id that this stream started on * @return the term id according to the position */ public static int computeTermIdFromPosition( final long position, final int positionBitsToShift, final int initialTermId) { return ((int)(position >>> positionBitsToShift) + initialTermId); } /** * Compute the term offset from a given position. * * @param position to calculate from * @param positionBitsToShift number of times to right shift the position * @return the offset within the term that represents the position */ public static int computeTermOffsetFromPosition(final long position, final int positionBitsToShift) { final long mask = (1L << positionBitsToShift) - 1L; return (int)(position & mask); } /** * Compute the total length of a log file given the term length. * * @param termLength on which to base the calculation. * @return the total length of the log file. */ public static long computeLogLength(final int termLength) { return (termLength * PARTITION_COUNT) + LOG_META_DATA_LENGTH; } /** * Compute the term length based on the total length of the log. * * @param logLength the total length of the log. * @return length of an individual term buffer in the log. */ public static int computeTermLength(final long logLength) { return (int)((logLength - LOG_META_DATA_LENGTH) / PARTITION_COUNT); } /** * Store the default frame header to the log meta data buffer. * * @param logMetaDataBuffer into which the default headers should be stored. * @param defaultHeader to be stored. * @throws IllegalArgumentException if the defaultHeader is larger than {@link #LOG_DEFAULT_FRAME_HEADER_MAX_LENGTH} */ public static void storeDefaultFrameHeader(final UnsafeBuffer logMetaDataBuffer, final DirectBuffer defaultHeader) { if (defaultHeader.capacity() != HEADER_LENGTH) { throw new IllegalArgumentException(String.format( "Default header of %d not equal to %d", defaultHeader.capacity(), HEADER_LENGTH)); } logMetaDataBuffer.putInt(LOG_DEFAULT_FRAME_HEADER_LENGTH_OFFSET, HEADER_LENGTH); logMetaDataBuffer.putBytes(LOG_DEFAULT_FRAME_HEADER_OFFSET, defaultHeader, 0, HEADER_LENGTH); } /** * Get a wrapper around the default frame header from the log meta data. * * @param logMetaDataBuffer containing the raw bytes for the default frame header. * @return a buffer wrapping the raw bytes. */ public static UnsafeBuffer defaultFrameHeader(final UnsafeBuffer logMetaDataBuffer) { return new UnsafeBuffer(logMetaDataBuffer, LOG_DEFAULT_FRAME_HEADER_OFFSET, HEADER_LENGTH); } /** * Apply the default header for a message in a term. * * @param logMetaDataBuffer containing the default headers. * @param termBuffer to which the default header should be applied. * @param termOffset at which the default should be applied. */ public static void applyDefaultHeader( final UnsafeBuffer logMetaDataBuffer, final UnsafeBuffer termBuffer, final int termOffset) { termBuffer.putBytes(termOffset, logMetaDataBuffer, LOG_DEFAULT_FRAME_HEADER_OFFSET, HEADER_LENGTH); } /** * Rotate the log and update the default headers for the new term. * * @param logMetaDataBuffer for the meta data. * @param activePartitionIndex current active index. * @param termId to be used in the default headers. */ public static void rotateLog(final UnsafeBuffer logMetaDataBuffer, final int activePartitionIndex, final int termId) { final int nextIndex = nextPartitionIndex(activePartitionIndex); initialiseTailWithTermId(logMetaDataBuffer, nextIndex, termId); activePartitionIndexOrdered(logMetaDataBuffer, nextIndex); } /** * Set the initial value for the termId in the upper bits of the tail counter. * * @param logMetaData contain the tail counter. * @param partitionIndex to be initialised. * @param termId to be set. */ public static void initialiseTailWithTermId( final UnsafeBuffer logMetaData, final int partitionIndex, final int termId) { logMetaData.putLong(TERM_TAIL_COUNTERS_OFFSET + (partitionIndex * SIZE_OF_LONG), ((long)termId) << 32); } /** * Get the termId from a packed raw tail value. * * @param rawTail containing the termId * @return the termId from a packed raw tail value. */ public static int termId(final long rawTail) { return (int)(rawTail >>> 32); } /** * Read the termOffset from a packed raw tail value. * * @param rawTail containing the termOffset. * @param termLength that the offset cannot exceed. * @return the termOffset value. */ public static int termOffset(final long rawTail, final long termLength) { final long tail = rawTail & 0xFFFF_FFFFL; return (int)Math.min(tail, termLength); } /** * Pack a termId and termOffset into a raw tail value. * * @param termId to be packed. * @param termOffset to be packed. * @return the packed value. */ public static long packTail(final int termId, final int termOffset) { return (((long)termId) << 32) + termOffset; } /** * Set the raw value of the tail for the given partition. * * @param logMetaDataBuffer containing the tail counters. * @param partitionIndex for the tail counter. * @param rawTail to be stored */ public static void rawTail( final UnsafeBuffer logMetaDataBuffer, final int partitionIndex, final long rawTail) { logMetaDataBuffer.putLong(TERM_TAIL_COUNTERS_OFFSET + (SIZE_OF_LONG * partitionIndex), rawTail); } /** * Set the raw value of the tail for the given partition. * * @param logMetaDataBuffer containing the tail counters. * @param partitionIndex for the tail counter. * @param rawTail to be stored */ public static void rawTailVolatile( final UnsafeBuffer logMetaDataBuffer, final int partitionIndex, final long rawTail) { logMetaDataBuffer.putLongVolatile(TERM_TAIL_COUNTERS_OFFSET + (SIZE_OF_LONG * partitionIndex), rawTail); } /** * Get the raw value of the tail for the given partition. * * @param logMetaDataBuffer containing the tail counters. * @param partitionIndex for the tail counter. * @return the raw value of the tail for the current active partition. */ public static long rawTailVolatile(final UnsafeBuffer logMetaDataBuffer, final int partitionIndex) { return logMetaDataBuffer.getLongVolatile(TERM_TAIL_COUNTERS_OFFSET + (SIZE_OF_LONG * partitionIndex)); } /** * Get the raw value of the tail for the current active partition. * * @param logMetaDataBuffer containing the tail counters. * @return the raw value of the tail for the current active partition. */ public static long rawTailVolatile(final UnsafeBuffer logMetaDataBuffer) { final int partitionIndex = activePartitionIndex(logMetaDataBuffer); return logMetaDataBuffer.getLongVolatile(TERM_TAIL_COUNTERS_OFFSET + (SIZE_OF_LONG * partitionIndex)); } }