All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fluxtion.agrona.concurrent.status.CountersManager Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014-2024 Real Logic Limited.
 *
 * 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
 *
 * https://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 com.fluxtion.agrona.concurrent.status;

import com.fluxtion.agrona.DirectBuffer;
import com.fluxtion.agrona.LangUtil;
import com.fluxtion.agrona.MutableDirectBuffer;
import com.fluxtion.agrona.collections.IntArrayList;
import com.fluxtion.agrona.concurrent.*;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;

import static com.fluxtion.agrona.BitUtil.SIZE_OF_INT;

/**
 * Manages the allocation and freeing of counters that are normally stored in a memory-mapped file.
 * 

* This class in not threadsafe. Counters should be centrally managed. *

* Values Buffer *

 *   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
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |                        Counter Value                          |
 *  |                                                               |
 *  +---------------------------------------------------------------+
 *  |                       Registration Id                         |
 *  |                                                               |
 *  +---------------------------------------------------------------+
 *  |                          Owner Id                             |
 *  |                                                               |
 *  +---------------------------------------------------------------+
 *  |                        Reference Id                           |
 *  |                                                               |
 *  +---------------------------------------------------------------+
 *  |                     96 bytes of padding                     ...
 * ...                                                              |
 *  +---------------------------------------------------------------+
 *  |                   Repeats to end of buffer                   ...
 *  |                                                               |
 * ...                                                              |
 *  +---------------------------------------------------------------+
 * 
*

* Meta Data Buffer *

 *   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
 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *  |                        Record State                           |
 *  +---------------------------------------------------------------+
 *  |                          Type Id                              |
 *  +---------------------------------------------------------------+
 *  |                 Free-for-reuse Deadline (ms)                  |
 *  |                                                               |
 *  +---------------------------------------------------------------+
 *  |                      112 bytes for key                       ...
 * ...                                                              |
 *  +-+-------------------------------------------------------------+
 *  |R|                      Label Length                           |
 *  +-+-------------------------------------------------------------+
 *  |                     380 bytes of Label                       ...
 * ...                                                              |
 *  +---------------------------------------------------------------+
 *  |                   Repeats to end of buffer                   ...
 *  |                                                               |
 * ...                                                              |
 *  +---------------------------------------------------------------+
 * 
*/ public class CountersManager extends CountersReader { private final long freeToReuseTimeoutMs; private int highWaterMarkId = -1; private final IntArrayList freeList = new IntArrayList(); private final EpochClock epochClock; /** * Create a new counter manager over two buffers. * * @param metaDataBuffer containing the types, keys, and labels for the counters. * @param valuesBuffer containing the values of the counters themselves. * @param labelCharset for the label encoding. * @param epochClock to use for determining time for keep counter from being reused after being freed. * @param freeToReuseTimeoutMs timeout (in milliseconds) to keep counter from being reused after being freed. */ public CountersManager( final AtomicBuffer metaDataBuffer, final AtomicBuffer valuesBuffer, final Charset labelCharset, final EpochClock epochClock, final long freeToReuseTimeoutMs) { super(metaDataBuffer, valuesBuffer, labelCharset); valuesBuffer.verifyAlignment(); this.epochClock = epochClock; this.freeToReuseTimeoutMs = freeToReuseTimeoutMs; if (metaDataBuffer.capacity() < (valuesBuffer.capacity() * (METADATA_LENGTH / COUNTER_LENGTH))) { throw new IllegalArgumentException("metadata buffer is too small"); } } /** * Create a new counter manager over two buffers. * * @param metaDataBuffer containing the types, keys, and labels for the counters. * @param valuesBuffer containing the values of the counters themselves. * @param labelCharset for the label encoding. */ public CountersManager( final AtomicBuffer metaDataBuffer, final AtomicBuffer valuesBuffer, final Charset labelCharset) { this(metaDataBuffer, valuesBuffer, labelCharset, new CachedEpochClock(), 0); } /** * Create a new counter manager over two buffers. * * @param metaDataBuffer containing the types, keys, and labels for the counters. * @param valuesBuffer containing the values of the counters themselves. */ public CountersManager(final AtomicBuffer metaDataBuffer, final AtomicBuffer valuesBuffer) { this(metaDataBuffer, valuesBuffer, StandardCharsets.UTF_8, new CachedEpochClock(), 0); } /** * Capacity as a count of counters which can be allocated. * * @return capacity as a count of counters which can be allocated. */ public int capacity() { return maxCounterId + 1; } /** * Number of counters available to be allocated which is a function of {@link #capacity()} minus the number already * allocated. * * @return the number of counter available to be allocated. */ public int available() { int freeListCount = 0; if (!freeList.isEmpty()) { final long nowMs = epochClock.time(); for (int i = 0, size = freeList.size(); i < size; i++) { final int counterId = freeList.getInt(i); if (nowMs >= metaDataBuffer.getLong(metaDataOffset(counterId) + FREE_FOR_REUSE_DEADLINE_OFFSET)) { freeListCount++; } } } return (capacity() - highWaterMarkId - 1) + freeListCount; } /** * Allocate a new counter with a given label with a default type of {@link #DEFAULT_TYPE_ID}. * * @param label to describe the counter. * @return the id allocated for the counter. */ public int allocate(final String label) { return allocate(label, DEFAULT_TYPE_ID); } /** * Allocate a new counter with a given label and type. * * @param label to describe the counter. * @param typeId for the type of counter. * @return the id allocated for the counter. */ public int allocate(final String label, final int typeId) { final int counterId = nextCounterId(); final int recordOffset = metaDataOffset(counterId); try { metaDataBuffer.putInt(recordOffset + TYPE_ID_OFFSET, typeId); metaDataBuffer.putLong(recordOffset + FREE_FOR_REUSE_DEADLINE_OFFSET, NOT_FREE_TO_REUSE); putLabel(recordOffset, label); metaDataBuffer.putIntOrdered(recordOffset, RECORD_ALLOCATED); } catch (final Exception ex) { freeList.pushInt(counterId); LangUtil.rethrowUnchecked(ex); } return counterId; } /** * Allocate a new counter with a given label. *

* The key function will be called with a buffer with the exact length of available key space * in the record for the user to store what they want for the key. No offset is required. * * @param label to describe the counter. * @param typeId for the type of counter. * @param keyFunc for setting the key value for the counter. * @return the id allocated for the counter. */ public int allocate(final String label, final int typeId, final Consumer keyFunc) { final int counterId = nextCounterId(); try { final int recordOffset = metaDataOffset(counterId); metaDataBuffer.putInt(recordOffset + TYPE_ID_OFFSET, typeId); keyFunc.accept(new UnsafeBuffer(metaDataBuffer, recordOffset + KEY_OFFSET, MAX_KEY_LENGTH)); metaDataBuffer.putLong(recordOffset + FREE_FOR_REUSE_DEADLINE_OFFSET, NOT_FREE_TO_REUSE); putLabel(recordOffset, label); metaDataBuffer.putIntOrdered(recordOffset, RECORD_ALLOCATED); } catch (final Exception ex) { freeList.pushInt(counterId); LangUtil.rethrowUnchecked(ex); } return counterId; } /** * Allocate a counter with the minimum of allocation by allowing the label a key to be provided and copied. *

* If the keyBuffer is null then a copy of the key is not attempted. * * @param typeId for the counter. * @param keyBuffer containing the optional key for the counter. * @param keyOffset within the keyBuffer at which the key begins. * @param keyLength of the key in the keyBuffer. * @param labelBuffer containing the mandatory label for the counter. * @param labelOffset within the labelBuffer at which the label begins. * @param labelLength of the label in the labelBuffer. * @return the id allocated for the counter. */ public int allocate( final int typeId, final DirectBuffer keyBuffer, final int keyOffset, final int keyLength, final DirectBuffer labelBuffer, final int labelOffset, final int labelLength) { final int counterId = nextCounterId(); try { final int recordOffset = metaDataOffset(counterId); metaDataBuffer.putInt(recordOffset + TYPE_ID_OFFSET, typeId); metaDataBuffer.putLong(recordOffset + FREE_FOR_REUSE_DEADLINE_OFFSET, NOT_FREE_TO_REUSE); if (null != keyBuffer) { final int length = Math.min(keyLength, MAX_KEY_LENGTH); metaDataBuffer.putBytes(recordOffset + KEY_OFFSET, keyBuffer, keyOffset, length); } final int length = Math.min(labelLength, MAX_LABEL_LENGTH); metaDataBuffer.putInt(recordOffset + LABEL_OFFSET, length); metaDataBuffer.putBytes(recordOffset + LABEL_OFFSET + SIZE_OF_INT, labelBuffer, labelOffset, length); metaDataBuffer.putIntOrdered(recordOffset, RECORD_ALLOCATED); } catch (final Exception ex) { freeList.pushInt(counterId); LangUtil.rethrowUnchecked(ex); } return counterId; } /** * Allocate a counter record and wrap it with a new {@link AtomicCounter} for use with a default type * of {@link #DEFAULT_TYPE_ID}. * * @param label to describe the counter. * @return a newly allocated {@link AtomicCounter} */ public AtomicCounter newCounter(final String label) { return new AtomicCounter(valuesBuffer, allocate(label), this); } /** * Allocate a counter record and wrap it with a new {@link AtomicCounter} for use. * * @param label to describe the counter. * @param typeId for the type of counter. * @return a newly allocated {@link AtomicCounter} */ public AtomicCounter newCounter(final String label, final int typeId) { return new AtomicCounter(valuesBuffer, allocate(label, typeId), this); } /** * Allocate a counter record and wrap it with a new {@link AtomicCounter} for use. * * @param label to describe the counter. * @param typeId for the type of counter. * @param keyFunc for setting the key value for the counter. * @return a newly allocated {@link AtomicCounter} */ public AtomicCounter newCounter(final String label, final int typeId, final Consumer keyFunc) { return new AtomicCounter(valuesBuffer, allocate(label, typeId, keyFunc), this); } /** * Allocate a counter record and wrap it with a new {@link AtomicCounter} for use. *

* If the keyBuffer is null then a copy of the key is not attempted. * * @param typeId for the counter. * @param keyBuffer containing the optional key for the counter. * @param keyOffset within the keyBuffer at which the key begins. * @param keyLength of the key in the keyBuffer. * @param labelBuffer containing the mandatory label for the counter. * @param labelOffset within the labelBuffer at which the label begins. * @param labelLength of the label in the labelBuffer. * @return the id allocated for the counter. */ public AtomicCounter newCounter( final int typeId, final DirectBuffer keyBuffer, final int keyOffset, final int keyLength, final DirectBuffer labelBuffer, final int labelOffset, final int labelLength) { return new AtomicCounter( valuesBuffer, allocate(typeId, keyBuffer, keyOffset, keyLength, labelBuffer, labelOffset, labelLength), this); } /** * Free the counter identified by counterId. * * @param counterId the counter to freed */ public void free(final int counterId) { validateCounterId(counterId); final int offset = metaDataOffset(counterId); if (RECORD_ALLOCATED != metaDataBuffer.getIntVolatile(offset)) { throw new IllegalStateException("counter not allocated: id=" + counterId); } metaDataBuffer.putIntOrdered(offset, RECORD_RECLAIMED); metaDataBuffer.setMemory(offset + KEY_OFFSET, MAX_KEY_LENGTH, (byte)0); metaDataBuffer.putLong(offset + FREE_FOR_REUSE_DEADLINE_OFFSET, epochClock.time() + freeToReuseTimeoutMs); freeList.addInt(counterId); } /** * Set an {@link AtomicCounter} value based for a counter id with volatile memory ordering. * * @param counterId to be set. * @param value to set for the counter. */ public void setCounterValue(final int counterId, final long value) { validateCounterId(counterId); valuesBuffer.putLongOrdered(counterOffset(counterId), value); } /** * Set an {@link AtomicCounter} registration id for a counter id with volatile memory ordering. * * @param counterId to be set. * @param registrationId to set for the counter. */ public void setCounterRegistrationId(final int counterId, final long registrationId) { validateCounterId(counterId); valuesBuffer.putLongOrdered(counterOffset(counterId) + REGISTRATION_ID_OFFSET, registrationId); } /** * Set an {@link AtomicCounter} owner id for a counter id. * * @param counterId to be set. * @param ownerId to set for the counter. */ public void setCounterOwnerId(final int counterId, final long ownerId) { validateCounterId(counterId); valuesBuffer.putLong(counterOffset(counterId) + OWNER_ID_OFFSET, ownerId); } /** * Set an {@link AtomicCounter} reference id for a counter id. * * @param counterId to be set. * @param referenceId to set for the counter. */ public void setCounterReferenceId(final int counterId, final long referenceId) { validateCounterId(counterId); valuesBuffer.putLong(counterOffset(counterId) + REFERENCE_ID_OFFSET, referenceId); } /** * Set an {@link AtomicCounter} label by counter id. * * @param counterId to be set. * @param label to set for the counter. */ public void setCounterLabel(final int counterId, final String label) { validateCounterId(counterId); putLabel(metaDataOffset(counterId), label); } /** * Set an {@link AtomicCounter} key by on counter id, using a consumer callback to update the key metadata buffer. * * @param counterId to be set. * @param keyFunc callback used to set the key. */ public void setCounterKey(final int counterId, final Consumer keyFunc) { validateCounterId(counterId); keyFunc.accept(new UnsafeBuffer(metaDataBuffer, metaDataOffset(counterId) + KEY_OFFSET, MAX_KEY_LENGTH)); } /** * Set an {@link AtomicCounter} key by on counter id, copying the key metadata from the supplied buffer. * * @param counterId to be set. * @param keyBuffer containing the updated key. * @param offset offset into buffer. * @param length length of data to copy. */ public void setCounterKey(final int counterId, final DirectBuffer keyBuffer, final int offset, final int length) { validateCounterId(counterId); if (length > MAX_KEY_LENGTH) { throw new IllegalArgumentException("key is too long: " + length + ", max: " + MAX_KEY_LENGTH); } metaDataBuffer.putBytes(metaDataOffset(counterId) + KEY_OFFSET, keyBuffer, offset, length); } /** * Set an {@link AtomicCounter} label based on counter id. * * @param counterId to be set. * @param label to set for the counter. */ public void appendToLabel(final int counterId, final String label) { appendLabel(metaDataOffset(counterId), label); } /** * {@inheritDoc} */ public String toString() { return "CountersManager{" + "freeToReuseTimeoutMs=" + freeToReuseTimeoutMs + ", highWaterMarkId=" + highWaterMarkId + ", freeList=" + freeList + ", epochClock=" + epochClock + '}'; } private int nextCounterId() { if (!freeList.isEmpty()) { final long nowMs = epochClock.time(); for (int i = 0, size = freeList.size(); i < size; i++) { final int counterId = freeList.getInt(i); if (nowMs >= metaDataBuffer.getLong(metaDataOffset(counterId) + FREE_FOR_REUSE_DEADLINE_OFFSET)) { freeList.remove(i); final int offset = counterOffset(counterId); valuesBuffer.putLongOrdered(offset + REGISTRATION_ID_OFFSET, DEFAULT_REGISTRATION_ID); valuesBuffer.putLong(offset + OWNER_ID_OFFSET, DEFAULT_OWNER_ID); valuesBuffer.putLong(offset + REFERENCE_ID_OFFSET, DEFAULT_REFERENCE_ID); valuesBuffer.putLongOrdered(offset, 0L); return counterId; } } } checkCountersCapacity(highWaterMarkId + 1); return ++highWaterMarkId; } private void putLabel(final int recordOffset, final String label) { if (StandardCharsets.US_ASCII == labelCharset) { final int length = metaDataBuffer.putStringWithoutLengthAscii( recordOffset + LABEL_OFFSET + SIZE_OF_INT, label, 0, MAX_LABEL_LENGTH); metaDataBuffer.putIntOrdered(recordOffset + LABEL_OFFSET, length); } else { final byte[] bytes = label.getBytes(labelCharset); final int length = Math.min(bytes.length, MAX_LABEL_LENGTH); metaDataBuffer.putBytes(recordOffset + LABEL_OFFSET + SIZE_OF_INT, bytes, 0, length); metaDataBuffer.putIntOrdered(recordOffset + LABEL_OFFSET, length); } } private void appendLabel(final int recordOffset, final String suffix) { final int existingLength = metaDataBuffer.getInt(recordOffset + LABEL_OFFSET); final int maxSuffixLength = MAX_LABEL_LENGTH - existingLength; if (StandardCharsets.US_ASCII == labelCharset) { final int suffixLength = metaDataBuffer.putStringWithoutLengthAscii( recordOffset + LABEL_OFFSET + SIZE_OF_INT + existingLength, suffix, 0, maxSuffixLength); metaDataBuffer.putIntOrdered(recordOffset + LABEL_OFFSET, existingLength + suffixLength); } else { final byte[] suffixBytes = suffix.getBytes(labelCharset); final int suffixLength = Math.min(suffixBytes.length, maxSuffixLength); metaDataBuffer.putBytes( recordOffset + LABEL_OFFSET + SIZE_OF_INT + existingLength, suffixBytes, 0, suffixLength); metaDataBuffer.putIntOrdered(recordOffset + LABEL_OFFSET, existingLength + suffixLength); } } private void checkCountersCapacity(final int counterId) { if (counterId > maxCounterId) { throw new IllegalStateException("unable to allocate counter, buffer is full: maxCounterId=" + maxCounterId); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy