package org.infinispan.stats.topK; import java.util.ArrayList; import java.util.EnumMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import org.infinispan.Cache; import org.infinispan.factories.ComponentRegistry; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import com.clearspring.analytics.stream.Counter; import com.clearspring.analytics.stream.StreamSummary; /** * This contains all the stream lib top keys. Stream lib is a space efficient technique to obtains the top-most * counters. * * @author Pedro Ruivo * @since 6.0 */ public class StreamSummaryContainer { private static final int MAX_CAPACITY = 100000; private static final Log log = LogFactory.getLog(StreamSummaryContainer.class); private static final boolean trace = log.isTraceEnabled(); private final String cacheName; private final String address; private final AtomicBoolean flushing; private final EnumMap<Stat, TopKeyWrapper> topKeyWrapper; private volatile int capacity = 1000; private volatile boolean enabled = false; private volatile boolean reset = false; public StreamSummaryContainer(String cacheName, String address) { this.cacheName = cacheName; this.address = address; flushing = new AtomicBoolean(false); topKeyWrapper = new EnumMap<Stat, TopKeyWrapper>(Stat.class); for (Stat stat : Stat.values()) { topKeyWrapper.put(stat, new TopKeyWrapper()); } resetAll(); } public static StreamSummaryContainer getOrCreateStreamLibContainer(Cache cache) { ComponentRegistry componentRegistry = cache.getAdvancedCache().getComponentRegistry(); StreamSummaryContainer streamLibContainer = componentRegistry.getComponent(StreamSummaryContainer.class); if (streamLibContainer == null) { String cacheName = cache.getName(); String address = String.valueOf(cache.getCacheManager().getAddress()); componentRegistry.registerComponent(new StreamSummaryContainer(cacheName, address), StreamSummaryContainer.class); } return componentRegistry.getComponent(StreamSummaryContainer.class); } /** * @return {@code true} if the top-key collection is enabled, {@code false} otherwise. */ public boolean isEnabled() { return enabled; } /** * Enables or disables the top-key collection */ public void setEnabled(boolean enabled) { if (!this.enabled && enabled) { resetAll(); } else if (!enabled) { resetAll(); } this.enabled = enabled; } public int getCapacity() { return capacity; } /** * Sets the capacity of the top-key. The capacity defines the maximum number of keys that are tracked. Remember that * top-key is a probabilistic counter so the higher the number of keys, the more precise will be the counters */ public void setCapacity(int capacity) { if (capacity <= 0) { this.capacity = 1; } else { this.capacity = Math.min(capacity, MAX_CAPACITY); } } /** * Adds the key to the read top-key. * * @param remote {@code true} if the key is remote, {@code false} otherwise. */ public void addGet(Object key, boolean remote) { if (!isEnabled()) { return; } syncOffer(remote ? Stat.REMOTE_GET : Stat.LOCAL_GET, key); } /** * Adds the key to the put top-key. * * @param remote {@code true} if the key is remote, {@code false} otherwise. */ public void addPut(Object key, boolean remote) { if (!isEnabled()) { return; } syncOffer(remote ? Stat.REMOTE_PUT : Stat.LOCAL_PUT, key); } /** * Adds the lock information about the key, namely if the key suffer some contention and if the keys was locked or * not. * * @param contention {@code true} if the key was contented. * @param failLock {@code true} if the key was not locked. */ public void addLockInformation(Object key, boolean contention, boolean failLock) { if (!isEnabled()) { return; } syncOffer(Stat.MOST_LOCKED_KEYS, key); if (contention) { syncOffer(Stat.MOST_CONTENDED_KEYS, key); } if (failLock) { syncOffer(Stat.MOST_FAILED_KEYS, key); } } /** * Adds the key to the write skew failed top-key. */ public void addWriteSkewFailed(Object key) { syncOffer(Stat.MOST_WRITE_SKEW_FAILED_KEYS, key); } /** * See {@link #getTopKFrom(StreamSummaryContainer.Stat, int)}. * * @return the top-key referring to the stat for all the keys. */ public Map<Object, Long> getTopKFrom(Stat stat) { return getTopKFrom(stat, capacity); } /** * @param topK the topK-th first key. * @return the topK-th first key referring to the stat. */ public Map<Object, Long> getTopKFrom(Stat stat, int topK) { tryFlushAll(); return topKeyWrapper.get(stat).topK(topK); } /** * Same as {@link #getTopKFrom(org.infinispan.stats.topK.StreamSummaryContainer.Stat)} but the keys are returned in * their String format. */ public Map<String, Long> getTopKFromAsKeyString(Stat stat) { return getTopKFromAsKeyString(stat, capacity); } /** * Same as {@link #getTopKFrom(org.infinispan.stats.topK.StreamSummaryContainer.Stat, int)} but the keys are returned * in their String format. */ public Map<String, Long> getTopKFromAsKeyString(Stat stat, int topK) { tryFlushAll(); return topKeyWrapper.get(stat).topKAsString(topK); } /** * Resets all the top-key collected so far. */ public final void resetAll() { reset = true; tryFlushAll(); } /** * Tries to flush all the enqueue offers to be visible globally. */ public final void tryFlushAll() { if (flushing.compareAndSet(false, true)) { if (reset) { for (Stat stat : Stat.values()) { topKeyWrapper.get(stat).reset(this, capacity); } reset = false; } else { for (Stat stat : Stat.values()) { topKeyWrapper.get(stat).flush(); } } flushing.set(false); } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; StreamSummaryContainer that = (StreamSummaryContainer) o; return !(address != null ? !address.equals(that.address) : that.address != null) && !(cacheName != null ? !cacheName.equals(that.cacheName) : that.cacheName != null); } @Override public int hashCode() { int result = cacheName != null ? cacheName.hashCode() : 0; result = 31 * result + (address != null ? address.hashCode() : 0); return result; } @Override public String toString() { return "StreamSummaryContainer{" + "cacheName='" + cacheName + '\'' + ", address='" + address + '\'' + '}'; } private StreamSummary<Object> createNewStreamSummary(int customCapacity) { return new StreamSummary<Object>(Math.min(MAX_CAPACITY, customCapacity)); } private void syncOffer(final Stat stat, Object key) { if (trace) { log.tracef("Offer key=%s to stat=%s in %s", key, stat, this); } topKeyWrapper.get(stat).offer(key); tryFlushAll(); } public static enum Stat { REMOTE_GET, LOCAL_GET, REMOTE_PUT, LOCAL_PUT, MOST_LOCKED_KEYS, MOST_CONTENDED_KEYS, MOST_FAILED_KEYS, MOST_WRITE_SKEW_FAILED_KEYS } private class TopKeyWrapper { private final BlockingQueue<Object> pendingOffers = new LinkedBlockingQueue<Object>(); private volatile StreamSummary<Object> streamSummary; private void offer(final Object element) { pendingOffers.add(element); } private void reset(StreamSummaryContainer container, int capacity) { pendingOffers.clear(); streamSummary = container.createNewStreamSummary(capacity); } private void flush() { List<Object> keys = new ArrayList<Object>(); pendingOffers.drainTo(keys); final StreamSummary<Object> summary = streamSummary; for (Object key : keys) { synchronized (this) { summary.offer(key); } } } private Map<Object, Long> topK(int k) { List<Counter<Object>> counterList; synchronized (this) { counterList = streamSummary.topK(k); } Map<Object, Long> map = new LinkedHashMap<Object, Long>(); for (Counter<Object> counter : counterList) { map.put(counter.getItem(), counter.getCount()); } if (trace) { log.tracef(this + " top-k is " + map); } return map; } private Map<String, Long> topKAsString(int k) { List<Counter<Object>> counterList; synchronized (this) { counterList = streamSummary.topK(k); } Map<String, Long> map = new LinkedHashMap<String, Long>(); for (Counter<Object> counter : counterList) { map.put(String.valueOf(counter.getItem()), counter.getCount()); } return map; } } }