// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude.fastsearch;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.logging.Logger;
import com.yahoo.log.LogLevel;
/**
* An LRU cache using number of hits cached inside the results as
* size limiting factor. Directly modelled after com.yahoo.collections.Cache.
*
* @author Steinar Knutsen
* @author bratseth
*/
public class PacketCache extends LinkedHashMap {
private static final long serialVersionUID = -7403077211906108356L;
/** The current number of bytes of packets in this cache */
private int totalSize;
/** The maximum number of bytes of packets in this cache */
private final int capacity;
/** The max size of a cached item compared to the total size */
private int maxCacheItemPercentage = 1;
/** The max age for a valid cache entry, 0 mean infinite */
private final long maxAge;
private static final Logger log = Logger.getLogger(PacketCache.class.getName());
public void clear() {
super.clear();
totalSize = 0;
}
/**
* Sets the max size of a cached item compared to the total size
* Cache requests for larger objects will be ignored
*/
public void setMaxCacheItemPercentage(int maxCapacityPercentage) {
maxCacheItemPercentage = maxCapacityPercentage;
}
/**
* Creates a cache with a size given by
* cachesizemegabytes*2^20+cachesizebytes
*
* @param capacityMegaBytes the cache size, measured in megabytes
* @param capacityBytes additional number of bytes to add to the cache size
* @param maxAge seconds a cache entry is valid, 0 or less are illegal arguments
*/
public PacketCache(int capacityMegaBytes,int capacityBytes,double maxAge) {
// hardcoded inital entry capacity, won't matter much anyway
// after a while
super(12500, 1.0f, true);
if (maxAge <= 0.0d) {
throw new IllegalArgumentException("maxAge <= 0 not legal on 5.1, use some very large number for no timeout.");
}
if (capacityMegaBytes > (Integer.MAX_VALUE >> 20)) {
log.log(LogLevel.INFO, "Packet cache of more than 2 GB requested. Reverting to 2 GB packet cache.");
this.capacity = Integer.MAX_VALUE;
} else {
this.capacity = (capacityMegaBytes << 20) + capacityBytes;
}
if (this.capacity <= 0) {
throw new IllegalArgumentException("Total cache size set to 0 or less bytes. If no caching is desired, avoid creating this object instead.");
}
this.maxAge = (long) (maxAge * 1000.0d);
}
/**
* Overrides LinkedHashMap.removeEldestEntry as suggested to implement LRU cache.
*/
protected boolean removeEldestEntry(Map.Entry eldest)
{
if (totalSize > capacity) {
totalSize -= eldest.getValue().getPacketsSize();
return true;
}
return false;
}
private void removeOverflow() {
if (totalSize < capacity) return;
for (Iterator i = values().iterator(); i.hasNext();) {
PacketWrapper eldestEntry = i.next();
totalSize -= eldestEntry.getPacketsSize();
i.remove();
if (totalSize < capacity) {
break;
}
}
}
public int getCapacity() {
return capacity >> 20;
}
public int getByteCapacity() {
return capacity;
}
/**
* Adds a PacketWrapper object to this cache,
* unless the size is more than maxCacheItemPercentage of the total size
*/
public PacketWrapper put(CacheKey key, PacketWrapper value) {
return put(key, value, System.currentTimeMillis());
}
/**
* Adds a BasicPacket array to this cache,
* unless the size is more than maxCacheItemPercentage of the total size
*
* @param timestamp the timestamp for the first packet in the array,
* unit milliseconds
*/
public PacketWrapper put(CacheKey key, PacketWrapper result, long timestamp) {
int size = result.getPacketsSize();
if (size > 0) {
result.setTimestamp(timestamp);
}
// don't insert if it is too big
if (size * 100 > capacity * maxCacheItemPercentage) {
// removeField the old one since that is now stale.
return remove(key);
}
totalSize += size;
PacketWrapper previous = super.put(key, result);
if (previous != null) {
totalSize -= previous.getPacketsSize();
}
if (totalSize > (capacity * 1.1)) {
removeOverflow();
}
return previous;
}
public PacketWrapper get(CacheKey key) {
return get(key, System.currentTimeMillis());
}
public PacketWrapper get(CacheKey key, long now) {
PacketWrapper result = super.get(key);
if (result == null) {
return result;
}
long timestamp = result.getTimestamp();
if ((now - timestamp) > maxAge) {
remove(key);
return null;
} else {
return result;
}
}
public PacketWrapper remove(CacheKey key) {
PacketWrapper removed = super.remove(key);
if (removed != null) {
totalSize -= removed.getPacketsSize();
}
return removed;
}
public int totalPacketSize() {
return totalSize;
}
}