package org.ehcache.core.statistics;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import org.ehcache.Cache;
import org.terracotta.context.ContextManager;
import org.terracotta.context.TreeNode;
import org.terracotta.context.query.Matcher;
import org.terracotta.context.query.Matchers;
import org.terracotta.context.query.Query;
import org.terracotta.statistics.OperationStatistic;
import org.terracotta.statistics.derived.OperationResultFilter;
import static org.terracotta.context.query.Matchers.*;
import static org.terracotta.context.query.QueryBuilder.queryBuilder;
public final class StatsUtils {
private StatsUtils() {}
public static Matcher<Map<String, Object>> hasTag(final String tag) {
return hasAttribute("tags", new Matcher<Set<String>>() {
@Override
protected boolean matchesSafely(Set<String> object) {
return object.contains(tag);
}
});
}
public static Matcher<Map<String, Object>> hasProperty(final String key, final String value) {
return hasAttribute("properties", new Matcher<Map<String, Object>>() {
@Override
protected boolean matchesSafely(Map<String, Object> properties) {
Object val = properties.get(key);
return val != null && value.equals(val);
}
});
}
public static <T> Optional<T> findStatisticOnDescendants(Object context, String discriminator, String tag, String statName) {
@SuppressWarnings("unchecked")
Set<TreeNode> statResult = queryBuilder()
.descendants()
.filter(context(attributes(Matchers.allOf(
hasAttribute("name", statName),
hasProperty("discriminator", discriminator),
hasTag(tag)))))
.build().execute(Collections.singleton(ContextManager.nodeFor(context)));
if (statResult.size() > 1) {
throw new RuntimeException("One stat expected for " + statName + " but found " + statResult.size());
}
if (statResult.size() == 1) {
@SuppressWarnings("unchecked")
T result = (T) statResult.iterator().next().getContext().attributes().get("this");
return Optional.ofNullable(result);
}
return Optional.empty();
}
public static <T> Optional<T> findStatisticOnDescendants(Object context, String tag, String statName) {
@SuppressWarnings("unchecked")
Set<TreeNode> statResult = queryBuilder()
.descendants()
.filter(context(attributes(Matchers.allOf(
hasAttribute("name", statName),
hasTag(tag)))))
.build().execute(Collections.singleton(ContextManager.nodeFor(context)));
if (statResult.size() > 1) {
throw new RuntimeException("One stat expected for " + statName + " but found " + statResult.size());
}
if (statResult.size() == 1) {
@SuppressWarnings("unchecked")
T result = (T) statResult.iterator().next().getContext().attributes().get("this");
return Optional.ofNullable(result);
}
return Optional.empty();
}
public static <T extends Enum<T>> OperationStatistic<T> findOperationStatisticOnChildren(Object context, Class<T> type, String statName) {
@SuppressWarnings("unchecked")
Query query = queryBuilder()
.children()
.filter(context(attributes(Matchers.allOf(hasAttribute("name", statName), hasAttribute("type", type)))))
.build();
Set<TreeNode> result = query.execute(Collections.singleton(ContextManager.nodeFor(context)));
if (result.size() > 1) {
throw new RuntimeException("result must be unique");
}
if (result.isEmpty()) {
throw new RuntimeException("result must not be null");
}
@SuppressWarnings("unchecked")
OperationStatistic<T> statistic = (OperationStatistic<T>) result.iterator().next().getContext().attributes().get("this");
return statistic;
}
public static String[] findTiers(Cache<?, ?> cache) {
@SuppressWarnings("unchecked")
Query statQuery = queryBuilder()
.descendants()
.filter(context(attributes(Matchers.allOf(hasAttribute("name", "eviction"), hasAttribute("type", StoreOperationOutcomes.EvictionOutcome.class)))))
.build();
Set<TreeNode> statResult = statQuery.execute(Collections.singleton(ContextManager.nodeFor(cache)));
if (statResult.isEmpty()) {
throw new RuntimeException("Failed to find tiers using the eviction observer, valid result Set sizes must 1 or more");
}
String[] tiers = new String[statResult.size()];
int i = 0;
for (TreeNode treeNode : statResult) {
Set<?> tags = (Set<?>) treeNode.getContext().attributes().get("tags");
if (tags.size() != 1) {
throw new RuntimeException("We expect tiers to have only one tag");
}
String storeType = tags.iterator().next().toString();
tiers[i++] = storeType;
}
return tiers;
}
public static String findLowestTier(String[] tiers) {
if (tiers.length == 1) {
return tiers[0];
}
if (tiers.length == 0) {
throw new RuntimeException("No existing tier");
}
String lowestTier = tiers[0];
for (int i = 1; i < tiers.length; i++) {
if (tiers[i].compareTo(lowestTier) < 0) {
lowestTier = tiers[i];
}
}
return lowestTier;
}
public static <T extends Enum<T>> boolean hasOperationStat(Object rootNode, Class<T> statisticType, String statName) {
Query q = queryBuilder().descendants()
.filter(context(identifier(subclassOf(OperationStatistic.class))))
.filter(context(attributes(Matchers.allOf(
hasAttribute("name", statName),
hasAttribute("this", new Matcher<OperationStatistic<T>>() {
@Override
protected boolean matchesSafely(OperationStatistic<T> object) {
return object.type().equals(statisticType);
}
})
))))
.build();
Set<TreeNode> result = q.execute(Collections.singleton(ContextManager.nodeFor(rootNode)));
if (result.size() > 1) {
throw new RuntimeException("a zero or a single stat was expected; found " + result.size());
}
return !result.isEmpty();
}
public static void registerClearNotification(String alias, Cache<?, ?> cache, Consumer<String> cacheClear) {
OperationStatistic<CacheOperationOutcomes.ClearOutcome> clear = StatsUtils.findOperationStatisticOnChildren(cache,
CacheOperationOutcomes.ClearOutcome.class, "clear");
clear.addDerivedStatistic(new OperationResultFilter<>(EnumSet.of(CacheOperationOutcomes.ClearOutcome.SUCCESS),
(time, latency) -> cacheClear.accept(alias)));
}
}