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

io.datakernel.memcache.server.RingBuffer Maven / Gradle / Ivy

package io.datakernel.memcache.server;

import com.carrotsearch.hppc.IntLongHashMap;
import com.carrotsearch.hppc.LongLongHashMap;
import com.carrotsearch.hppc.ObjectLongHashMap;
import io.datakernel.eventloop.jmx.EventStats;
import io.datakernel.memcache.protocol.MemcacheRpcMessage.Slice;

import java.time.Duration;
import java.util.Arrays;

import static io.datakernel.common.StringFormatUtils.formatDuration;
import static io.datakernel.eventloop.jmx.MBeanFormat.formatTimestamp;
import static java.lang.System.currentTimeMillis;

/**
 * The implementation to handle the big amount of date
 * It works like cache, when you use it you shouldn`t rely on result.
 * Because it can be rewritten by new date, if the written date was oversize
 */
public final class RingBuffer implements RingBufferMBean {

	/**
	 * The main class for the caching the byte-arrays
	 */
	private static class Buffer {
		private final byte[] array;
		private final IntLongHashMap indexInt = new IntLongHashMap();
		private final LongLongHashMap indexLong = new LongLongHashMap();
		private final ObjectLongHashMap indexBytes = new ObjectLongHashMap() {
			@Override
			protected int hashKey(byte[] key) {
				int result = 0;
				for (byte element : key) {
					result = 92821 * result + element;
				}
				return result;
			}

			@Override
			protected boolean equals(Object v1, Object v2) {
				return Arrays.equals((byte[]) v1, (byte[]) v2);
			}
		};

		private int position = 0;
		private long timestamp;

		Buffer(int capacity) {
			this.array = new byte[capacity];
			this.timestamp = currentTimeMillis();
		}

		void clear() {
			indexInt.clear();
			indexLong.clear();
			indexBytes.clear();
			position = 0;
			timestamp = currentTimeMillis();
		}

		int capacity() {
			return array.length;
		}

		int position() {
			return position;
		}

		static int intValueOf(byte[] bytes) {
			return ((bytes[0] << 24)) |
					((bytes[1] & 0xff) << 16) |
					((bytes[2] & 0xff) << 8) |
					((bytes[3] & 0xff));
		}

		static long longValueOf(byte[] bytes) {
			return ((((long) bytes[0]) << 56) |
					(((long) bytes[1] & 0xff) << 48) |
					(((long) bytes[2] & 0xff) << 40) |
					(((long) bytes[3] & 0xff) << 32) |
					(((long) bytes[4] & 0xff) << 24) |
					(((long) bytes[5] & 0xff) << 16) |
					(((long) bytes[6] & 0xff) << 8) |
					(((long) bytes[7] & 0xff)));
		}

		Slice get(byte[] key) {
			long segment;
			if (key.length == 4) {
				segment = indexInt.getOrDefault(intValueOf(key), -1L);
			} else if (key.length == 8) {
				segment = indexLong.getOrDefault(longValueOf(key), -1L);
			} else {
				segment = indexBytes.getOrDefault(key, -1L);
			}
			if (segment < 0)
				return null;
			int offset = (int) (segment);
			int size = (int) (segment >>> 32);
			return new Slice(array, offset, size);
		}

		void put(byte[] key, byte[] data, int offset, int length) {
			assert length <= remaining();
			long segment = ((long) length << 32) | position;
			if (key.length == 4) {
				indexInt.put(intValueOf(key), segment);
			} else if (key.length == 8) {
				indexLong.put(longValueOf(key), segment);
			} else {
				indexBytes.put(key, segment);
			}
			System.arraycopy(data, offset, array, position, length);
			position += length;
		}

		int remaining() {
			return array.length - position;
		}

		long getTimestamp() {
			return timestamp;
		}

		int items() {
			return indexInt.size() + indexLong.size() + indexBytes.size();
		}
	}

	private final Buffer[] ringBuffers;
	private int currentBuffer = 0;

	// JMX
	private static final Duration SMOOTHING_WINDOW = Duration.ofMinutes(1);
	private final EventStats statsPuts = EventStats.create(SMOOTHING_WINDOW);
	private final EventStats statsGets = EventStats.create(SMOOTHING_WINDOW);
	private final EventStats statsMisses = EventStats.create(SMOOTHING_WINDOW);
	private int countCycles = 0;

	public static RingBuffer create(int amountBuffers, long bufferCapacity) {
		Buffer[] ringBuffers = new Buffer[amountBuffers];
		for (int i = 0; i < amountBuffers; i++) {
			ringBuffers[i] = new Buffer((int) bufferCapacity);
		}
		return new RingBuffer(ringBuffers);
	}

	private RingBuffer(Buffer[] ringBuffers) {
		this.ringBuffers = ringBuffers;
	}

	/**
	 * The method is used to try to get the from the {@see Buffer}
	 * It will return the latest actual data for the {@param key}
	 *
	 * @param key of your item
	 * @return the item in case your item is still present in {@see Buffer}
	 */
	public Slice get(byte[] key) {
		statsGets.recordEvent();
		for (int i = 0; i < ringBuffers.length; i++) {
			int current = currentBuffer - i;
			if (current < 0)
				current = ringBuffers.length + current;
			Slice slice = ringBuffers[current].get(key);
			if (slice != null) {
				return slice;
			}
		}
		statsMisses.recordEvent();
		return null;
	}

	/**
	 * The method is used to cache the actual information for the {@param key}
	 *
	 * @param key  is used as a pointer for the cached {@param data}
	 * @param data is thing to need to cache
	 */
	public void put(byte[] key, byte[] data) {
		put(key, data, 0, data.length);
	}

	/**
	 * The same to the above method,
	 * there are extra params to handle the {@param data}
	 */
	public void put(byte[] key, byte[] data, int offset, int length) {
		statsPuts.recordEvent();
		if (ringBuffers[currentBuffer].remaining() < length) {
			if (currentBuffer == ringBuffers.length - 1) {
				countCycles++;
			}
			currentBuffer = (currentBuffer + 1) % ringBuffers.length;
			ringBuffers[currentBuffer].clear();
		}
		ringBuffers[currentBuffer].put(key, data, offset, length);
	}

	private long getLifetimeMillis() {
		return currentTimeMillis() - ringBuffers[(currentBuffer + 1) % ringBuffers.length].getTimestamp();
	}

	// JMX
	@Override
	public void reset() {
		countCycles = 0;
		statsMisses.resetStats();
	}

	@Override
	public String getStatsPuts() {
		return statsPuts.toString();
	}

	@Override
	public double getStatsPutsRate() {
		return statsPuts.getSmoothedRate();
	}

	@Override
	public long getStatsPutsTotal() {
		return statsPuts.getTotalCount();
	}

	@Override
	public String getStatsGets() {
		return statsGets.toString();
	}

	@Override
	public double getStatsGetsRate() {
		return statsGets.getSmoothedRate();
	}

	@Override
	public long getStatsGetsTotal() {
		return statsGets.getTotalCount();
	}

	@Override
	public String getStatsMisses() {
		return statsMisses.toString();
	}

	@Override
	public double getStatsMissesRate() {
		return statsMisses.getSmoothedRate();
	}

	@Override
	public long getStatsMissesTotal() {
		return statsMisses.getTotalCount();
	}

	/**
	 * Is used to figure out the amount of byte[] arrays which are stored
	 *
	 * @return amount of stored data
	 */
	@Override
	public int getItems() {
		int items = 0;
		for (Buffer ringBuffer : ringBuffers) {
			items += ringBuffer.items();
		}
		return items;
	}

	/**
	 * Is used to get the occupied capacity
	 *
	 * @return amount of occupied capacity
	 */
	@Override
	public long getSize() {
		long size = 0;
		for (Buffer ringBuffer : ringBuffers) {
			size += ringBuffer.position;
		}
		return size;
	}

	@Override
	public String getLifetime() {
		return formatDuration(Duration.ofMillis(getLifetimeMillis()));
	}

	@Override
	public long getLifetimeSeconds() {
		return getLifetimeMillis() / 1000;
	}

	@Override
	public String getCurrentBuffer() {
		return (currentBuffer + 1) + " / " + ringBuffers.length + " @ " +
				formatTimestamp(ringBuffers[currentBuffer].getTimestamp());
	}

	@Override
	public int getFullCycles() {
		return countCycles;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy