package org.yamcs.parameter; import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.yamcs.parameter.ParameterValue; import org.yamcs.xtce.Parameter; /** * * * Used by the {@link org.yamcs.parameter.ParameterRequestManagerImpl} to cache last values of parameters. * * The cache will contain the parameters for a predefined time period but can exceed that period if space is available in the CacheEntry. * * A CacheEntry also has a maximum size to prevent it accumulating parameters ad infinitum (e.g. if there are bogus parameters with the timestamp never changing) * * * We keep delivery consisting of lists of parameter values together such that * if two parameters have been acquired in the same delivery, they will be given from the same delivery to the clients. * * @author nm * */ public class ParameterCache { final ConcurrentHashMap<Parameter, CacheEntry> cache = new ConcurrentHashMap<Parameter, CacheEntry>(); final long timeToCache; final int maxNumEntries; public ParameterCache(ParameterCacheConfig cacheConfig) { this.timeToCache = cacheConfig.maxDuration; this.maxNumEntries = cacheConfig.maxNumEntries; } /** * update the parameters in the cache * @param pvs - parameter value list */ public void update(Collection<ParameterValue> pvs) { //System.out.println("ParameterCache ------- updated with "+pvs); ParameterValueList pvlist = new ParameterValueList(pvs); for (ParameterValue pv:pvs) { Parameter p = pv.getParameter(); CacheEntry ce = cache.get(p); if(ce==null) { ce = new CacheEntry(p, timeToCache, maxNumEntries); cache.put(p, ce); } ce.add(pvlist); } } /** * Returns cached value for parameter or an empty list if there is no value in the cache * * * @param plist * @return */ public List<ParameterValue> getValues(List<Parameter> plist) { //use a bitset to clear out the parameters that have already been found BitSet bs = new BitSet(plist.size()); List<ParameterValue> result = new ArrayList<ParameterValue>(plist.size()); bs.set(0, plist.size(), true); for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) { Parameter p = plist.get(i); CacheEntry ce = cache.get(p); if(ce!=null) { //last delivery where this parameter appears ParameterValueList pvlist = ce.getLast(); ParameterValue pv = pvlist.getLastInserted(p); result.add(pv); bs.clear(i); //find all the other parameters that are in this delivery for (int j = bs.nextSetBit(i); j >= 0; j = bs.nextSetBit(j+1)) { Parameter p1 = plist.get(j); pv = pvlist.getLastInserted(p1); if(pv!=null) { result.add(pv); bs.clear(j); } } } else { //no value for this parameter bs.clear(i); } } return result; } /** * Returns last cached value for parameter or null if there is no value in the cache * @param p - parameter for which the last value is returned * @return */ public ParameterValue getLastValue(Parameter p) { CacheEntry ce = cache.get(p); if(ce==null) return null; ParameterValueList pvlist = ce.getLast(); return pvlist.getLastInserted(p); } /** * Returns all values from the cache for the parameter or null if there is no value cached * * The parameter are returned in descending order (newest parameter is returned first) * @param p - parameter for which all values are returned * @return */ public List<ParameterValue> getAllValues(Parameter p) { CacheEntry ce = cache.get(p); if(ce==null) return null; return ce.getAll(); } /** * Stores a cache for one parameter as an array of the ParameterValueList in which it is part of. * * It ensure minimum * * @author nm * */ static final class CacheEntry { final Parameter parameter; private ParameterValueList[] elements; int tail = 0; static final int INITIAL_CAPACITY = 128; final long timeToCache; final int maxNumEntries; ReadWriteLock lock = new ReentrantReadWriteLock(); public CacheEntry(Parameter p, long timeToCache, int maxNumEntries) { this.parameter = p; this.timeToCache = timeToCache; this.maxNumEntries = maxNumEntries; elements = new ParameterValueList[INITIAL_CAPACITY]; } public List<ParameterValue> getAll() { lock.readLock().lock(); try { List<ParameterValue> plist = new ArrayList<>(); int _tail = tail; int n = elements.length; int t = _tail; do { t = (t-1)&(n-1); ParameterValueList pvl = elements[t]; if(pvl==null) break; pvl.forEach(parameter, (ParameterValue pv) -> plist.add(pv)); } while (t!=_tail); return plist; } finally { lock.readLock().unlock(); } } ParameterValueList getLast() { lock.readLock().lock(); try { return elements[(tail-1)&(elements.length-1)]; } finally { lock.readLock().unlock(); } } public void add(ParameterValueList pvlist) { lock.writeLock().lock(); try { ParameterValueList pv1 = elements[tail]; if(pv1!=null) { ParameterValue oldpv = pv1.getFirstInserted(parameter); ParameterValue newpv = pvlist.getFirstInserted(parameter); if((oldpv==null) || (newpv==null)) return; // shouldn't happen if(newpv.getGenerationTime() < oldpv.getGenerationTime()) { // parameter older than the last one in the queue -> ignore return; } if(newpv.getGenerationTime()-oldpv.getGenerationTime()<timeToCache) { doubleCapacity(); } } elements[tail] = pvlist; tail = (tail+1) & (elements.length-1); } finally { lock.writeLock().unlock(); } } private void doubleCapacity() { int capacity = elements.length; if(capacity>=maxNumEntries) return; int newCapacity = 2*capacity; ParameterValueList[] newElements = new ParameterValueList[newCapacity]; System.arraycopy(elements, 0, newElements, 0, tail); System.arraycopy(elements, tail, newElements, tail+capacity, capacity-tail); elements = newElements; } } }