/*
* Copyright Terracotta, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ehcache.impl.internal.statistics;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.ehcache.Cache;
import org.ehcache.core.statistics.StoreOperationOutcomes;
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 static org.terracotta.context.query.Matchers.*;
import static org.terracotta.context.query.QueryBuilder.queryBuilder;
Class allowing to query cache and tier statistics
/**
* Class allowing to query cache and tier statistics
*/
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);
}
});
}
Search for a statistic on the descendant of the context that matches the tag and statistic name.
Params: - context – the context of the query
- discriminator – a filter on the discriminator property
- tag – the tag we are looking for
- statName – statistic name
Type parameters: - <T> – type of the statistic that will be returned
Throws: - RuntimeException – when more than one matching statistic is found
Returns: the wanted statistic or null if no such statistic is found
/**
* Search for a statistic on the descendant of the context that matches the tag and statistic name.
*
* @param context the context of the query
* @param discriminator a filter on the discriminator property
* @param tag the tag we are looking for
* @param statName statistic name
* @param <T> type of the statistic that will be returned
* @return the wanted statistic or null if no such statistic is found
* @throws RuntimeException when more than one matching statistic is found
*/
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);
}
// No such stat in this context
return Optional.empty();
}
Search for a statistic on the descendant of the context that matches the tag and statistic name.
Params: - context – the context of the query
- tag – the tag we are looking for
- statName – statistic name
Type parameters: - <T> – type of the statistic that will be returned
Throws: - RuntimeException – when more than one matching statistic is found
Returns: the wanted statistic or null if no such statistic is found
/**
* Search for a statistic on the descendant of the context that matches the tag and statistic name.
*
* @param context the context of the query
* @param tag the tag we are looking for
* @param statName statistic name
* @param <T> type of the statistic that will be returned
* @return the wanted statistic or null if no such statistic is found
* @throws RuntimeException when more than one matching statistic is found
*/
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);
}
// No such stat in this context
return Optional.empty();
}
Find an operation statistic attached (as a children) to this context that matches the statistic name and type
Params: - context – the context of the query
- type – type of the operation statistic
- statName – statistic name
Type parameters: - <T> – type of the operation statistic content
Throws: - RuntimeException – if 0 or more than 1 result is found
Returns: the operation statistic searched for
/**
* Find an operation statistic attached (as a children) to this context that matches the statistic name and type
*
* @param context the context of the query
* @param type type of the operation statistic
* @param statName statistic name
* @param <T> type of the operation statistic content
* @return the operation statistic searched for
* @throws RuntimeException if 0 or more than 1 result is found
*/
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;
}
Find the list of tiers of a cache. We assume a lot of things here.
- The "eviction" statistic is available on the tier
- That the tiers have only one tag attribute
- That this tag contains the tier name
- That the only descendants having an "eviction" statistic are the tiers
Params: - cache – the context for looking for tiers
Throws: - RuntimeException – if not tiers are found or if tiers have multiple tags
Returns: an array of tier names
/**
* Find the list of tiers of a cache. We assume a lot of things here.
* <ul>
* <li>The "eviction" statistic is available on the tier</li>
* <li>That the tiers have only one tag attribute</li>
* <li>That this tag contains the tier name</li>
* <li>That the only descendants having an "eviction" statistic are the tiers</li>
* </ul>
*
* @param cache the context for looking for tiers
* @return an array of tier names
* @throws RuntimeException if not tiers are found or if tiers have multiple tags
*/
public static String[] findTiers(Cache<?, ?> cache) {
// Here I'm randomly taking the eviction observer because it exists on all tiers
@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;
}
Find the lowest tier from a list of tier. We assume a lot of things here that the tiers depth
magically matches the alphabetical order.
Params: - tiers – all tiers
Returns: the lowest tier
/**
* Find the lowest tier from a list of tier. We assume a lot of things here that the tiers depth
* <strong>magically matches the alphabetical order<strong>.
*
* @param tiers all tiers
* @return the lowest tier
*/
public static String findLowestTier(String[] tiers) {
//if only 1 store then you don't need to find the lowest tier
if (tiers.length == 1) {
return tiers[0];
}
//we expect at least one tier
if (tiers.length == 0) {
throw new RuntimeException("No existing tier");
}
// We rely here on the alphabetical order matching the depth order so from highest to lowest we have
// OnHeap, OffHeap, Disk, Clustered
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();
}
}