package org.infinispan.stats.impl; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import org.infinispan.commons.CacheException; import org.infinispan.factories.annotations.Inject; import org.infinispan.factories.annotations.Start; import org.infinispan.jmx.JmxStatisticsExposer; import org.infinispan.jmx.annotations.DataType; import org.infinispan.jmx.annotations.DisplayType; import org.infinispan.jmx.annotations.ManagedAttribute; import org.infinispan.jmx.annotations.ManagedOperation; import org.infinispan.jmx.annotations.Units; import org.infinispan.util.TimeService; import org.infinispan.util.logging.Log; /** * @author Ryan Emerson * @since 9.0 */ public abstract class AbstractClusterStats implements JmxStatisticsExposer { public static final long DEFAULT_STALE_STATS_THRESHOLD = 3000; private TimeService timeService; private volatile long staleStatsThreshold = DEFAULT_STALE_STATS_THRESHOLD; private volatile long statsUpdateTimestamp = 0; volatile boolean statisticsEnabled = false; private final Log log; private final AtomicLong resetNanoseconds = new AtomicLong(0); final HashMap<String, Number> statsMap = new HashMap<>(); AbstractClusterStats(Log log) { this.log = log; } abstract void updateStats() throws Exception; @Inject void init(TimeService timeService) { this.timeService = timeService; } @Start void start() { setStatisticsEnabled(statisticsEnabled); } public void reset() { statsMap.clear(); } @ManagedAttribute(description = "Gets the threshold for cluster wide stats refresh (milliseconds)", displayName = "Stale Stats Threshold", dataType = DataType.TRAIT, writable = true) public long getStaleStatsThreshold() { return staleStatsThreshold; } public void setStaleStatsThreshold(long staleStatsThreshold) { this.staleStatsThreshold = staleStatsThreshold; } @Override @ManagedOperation(description = "Resets statistics gathered by this component", displayName = "Reset statistics") public void resetStatistics() { if (isStatisticsEnabled()) { reset(); resetNanoseconds.set(timeService.time()); } } @ManagedAttribute( description = "Number of seconds since the cluster-wide statistics were last reset", displayName = "Seconds since cluster-wide statistics were reset", units = Units.SECONDS, displayType = DisplayType.SUMMARY ) public long getTimeSinceReset() { long result = -1; if (isStatisticsEnabled()) { result = timeService.timeDuration(resetNanoseconds.get(), TimeUnit.SECONDS); } return result; } @Override public void setStatisticsEnabled(boolean enabled) { this.statisticsEnabled = enabled; if (enabled) { //yes technically we do not reset stats but we initialize them resetNanoseconds.set(timeService.time()); } } @Override public boolean getStatisticsEnabled() { return statisticsEnabled; } @ManagedAttribute(description = "Enables or disables the gathering of statistics by this component", displayName = "Statistics enabled", dataType = DataType.TRAIT, writable = true) public boolean isStatisticsEnabled() { return getStatisticsEnabled(); } synchronized void fetchClusterWideStatsIfNeeded() { long duration = timeService.timeDuration(statsUpdateTimestamp, timeService.time(), TimeUnit.MILLISECONDS); if (duration > DEFAULT_STALE_STATS_THRESHOLD) { try { updateStats(); } catch (Exception e) { log.error("Could not execute cluster wide cache stats operation ", e); throw new CacheException("Could not execute cluster wide cache stats operation", e); } finally { statsUpdateTimestamp = timeService.time(); } } } long addLongAttributes(List<Map<String, Number>> responseList, String attribute) { long total = 0; for (Map<String, Number> m : responseList) { Number value = m.get(attribute); long longValue = value.longValue(); if (longValue > -1) { total += longValue; } } return total; } double addDoubleAttributes(List<Map<String, Number>> responseList, String attribute) { double total = 0; for (Map<String, Number> m : responseList) { Number value = m.get(attribute); double doubleValue = value.doubleValue(); if (doubleValue > -1) { total += doubleValue; } } return total; } int addIntAttributes(List<Map<String, Number>> responseList, String attribute) { int total = 0; for (Map<String, Number> m : responseList) { Number value = m.get(attribute); long intValue = value.intValue(); if (intValue > -1) { total += intValue; } } return total; } void putLongAttributesAverage(List<Map<String, Number>> responseList, String attribute) { long average = addLongAttributes(responseList, attribute) / responseList.size(); statsMap.put(attribute, average); } void putLongAttributes(List<Map<String, Number>> responseList, String attribute) { statsMap.put(attribute, addLongAttributes(responseList, attribute)); } void putIntAttributes(List<Map<String, Number>> responseList, String attribute) { statsMap.put(attribute, addIntAttributes(responseList, attribute)); } long getStatAsLong(String attribute) { return getStat(attribute).longValue(); } int getStatAsInt(String attribute) { return getStat(attribute).intValue(); } private Number getStat(String attribute) { if (isStatisticsEnabled()) { fetchClusterWideStatsIfNeeded(); return statsMap.getOrDefault(attribute, 0); } else { return -1; } } }