/*
* 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.terracotta.statistics.registry;
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.statistics.OperationStatistic;
import org.terracotta.statistics.StatisticType;
import org.terracotta.statistics.Table;
import org.terracotta.statistics.ValueStatistic;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static org.terracotta.context.query.Matchers.attributes;
import static org.terracotta.context.query.Matchers.context;
import static org.terracotta.context.query.Matchers.hasAttribute;
import static org.terracotta.context.query.Matchers.identifier;
import static org.terracotta.context.query.Matchers.subclassOf;
import static org.terracotta.context.query.QueryBuilder.queryBuilder;
import static org.terracotta.statistics.ValueStatistics.counter;
import static org.terracotta.statistics.ValueStatistics.gauge;
import static org.terracotta.statistics.ValueStatistics.supply;
import static org.terracotta.statistics.ValueStatistics.table;
This class replaces the previous StatisticRegistry
in the cases where you do not need any sampling and history.
This class typically does a sort of mapping between the registrations and the discovered
operations or passthrough statistics.
This class also support the generation of management metadata from the discovered statistics.
Non thread-safe.
Author: Mathieu Carbou
/**
* This class replaces the previous {@link StatisticRegistry}
* in the cases where you do not need any sampling and history.
* <p>
* This class typically does a sort of mapping between the registrations and the discovered
* operations or passthrough statistics.
* <p>
* This class also support the generation of management metadata from the discovered statistics.
* <p>
* Non thread-safe.
*
* @author Mathieu Carbou
*/
public class StatisticRegistry {
private final Object contextObject;
private final LongSupplier timeSource;
private final Map<String, ValueStatistic<? extends Serializable>> statistics = new HashMap<>();
public StatisticRegistry(Object contextObject, LongSupplier timeSource) {
this.contextObject = contextObject;
this.timeSource = Objects.requireNonNull(timeSource);
}
protected Map<String, ValueStatistic<? extends Serializable>> getStatistics() {
return statistics;
}
Query a statistic based on the full statistic name. Returns null if not found.
/**
* Query a statistic based on the full statistic name. Returns null if not found.
*/
public <T extends Serializable> Optional<Statistic<T>> queryStatistic(String fullStatisticName) {
return queryStatistic(fullStatisticName, 0);
}
Query a statistic based on the full statistic name. Returns null if not found.
/**
* Query a statistic based on the full statistic name. Returns null if not found.
*/
@SuppressWarnings("unchecked")
public <T extends Serializable> Optional<Statistic<T>> queryStatistic(String fullStatisticName, long sinceMillis) {
ValueStatistic<T> valueStatistic = (ValueStatistic<T>) statistics.get(fullStatisticName);
if (valueStatistic == null) {
return Optional.empty();
}
return Optional.of(Statistic.extract(valueStatistic, sinceMillis, timeSource.getAsLong()));
}
public Map<String, Statistic<? extends Serializable>> queryStatistics() {
return queryStatistics(0);
}
@SuppressWarnings("unchecked")
public Map<String, Statistic<? extends Serializable>> queryStatistics(long sinceMillis) {
long now = timeSource.getAsLong();
return statistics.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> Statistic.extract(e.getValue(), sinceMillis, now)));
}
public <T extends Serializable> void registerStatistic(String fullStatName, StatisticType type, Supplier<T> accessor) {
registerStatistic(fullStatName, supply(type, accessor));
}
public <T extends Serializable> void registerStatistic(String fullStatName, ValueStatistic<T> accessor) {
if (statistics.put(fullStatName, accessor) != null) {
throw new IllegalArgumentException("Found duplicate statistic " + fullStatName);
}
}
Directly register a TABLE stat with its accessors
/**
* Directly register a TABLE stat with its accessors
*/
public void registerTable(String fullStatName, Supplier<Table> accessor) {
registerStatistic(fullStatName, table(accessor));
}
Directly register a GAUGE stat with its accessor
/**
* Directly register a GAUGE stat with its accessor
*/
public void registerGauge(String fullStatName, Supplier<Number> accessor) {
registerStatistic(fullStatName, gauge(accessor));
}
Directly register a COUNTER stat with its accessor
/**
* Directly register a COUNTER stat with its accessor
*/
public void registerCounter(String fullStatName, Supplier<Number> accessor) {
registerStatistic(fullStatName, counter(accessor));
}
@SuppressWarnings("unchecked")
public <T extends Serializable> boolean registerStatistic(String statNameSuffix, ValueStatisticDescriptor descriptor) {
// ignore registering through descriptors if we do not have a context object to find in the tree
if (contextObject == null) {
return false;
}
TreeNode treeNode = ContextManager.nodeFor(contextObject);
if (treeNode == null) {
return false;
}
Set<TreeNode> result = queryBuilder()
.descendants()
.filter(context(attributes(Matchers.allOf(
hasAttribute("name", descriptor.getObserverName()),
hasTags(descriptor.getTags())))))
.filter(context(identifier(subclassOf(ValueStatistic.class))))
.build().execute(Collections.singleton(treeNode));
if (!result.isEmpty()) {
for (TreeNode node : result) {
String discriminator = null;
Map<String, Object> properties = (Map<String, Object>) node.getContext().attributes().get("properties");
if (properties != null && properties.containsKey("discriminator")) {
discriminator = properties.get("discriminator").toString();
}
String fullStatName = (discriminator == null ? "" : (discriminator + ":")) + statNameSuffix;
ValueStatistic<T> statistic = (ValueStatistic<T>) node.getContext().attributes().get("this");
registerStatistic(fullStatName, statistic);
}
return true;
} else {
return false;
}
}
@SuppressWarnings("unchecked")
public <T extends Enum<T>> boolean registerStatistic(String statNameSuffix, final OperationStatisticDescriptor<T> descriptor, final EnumSet<T> outcomes) {
// ignore registering through descriptors if we do not have a context object to find in the tree
if (contextObject == null) {
return false;
}
TreeNode treeNode = ContextManager.nodeFor(contextObject);
if (treeNode == null) {
return false;
}
Set<TreeNode> result = queryBuilder()
.descendants()
.filter(context(attributes(Matchers.allOf(
hasAttribute("type", descriptor.getType()),
hasAttribute("name", descriptor.getObserverName()),
hasTags(descriptor.getTags())))))
.filter(context(identifier(subclassOf(OperationStatistic.class))))
.build().execute(Collections.singleton(treeNode));
if (!result.isEmpty()) {
for (TreeNode node : result) {
String discriminator = null;
Map<String, Object> properties = (Map<String, Object>) node.getContext().attributes().get("properties");
if (properties != null && properties.containsKey("discriminator")) {
discriminator = properties.get("discriminator").toString();
}
String fullStatName = (discriminator == null ? "" : (discriminator + ":")) + statNameSuffix;
final OperationStatistic<T> statistic = (OperationStatistic<T>) node.getContext().attributes().get("this");
registerStatistic(fullStatName, statistic.statistic(outcomes));
}
return true;
} else {
return false;
}
}
private Matcher<Map<String, Object>> hasTags(final Collection<String> tags) {
return hasAttribute("tags", new Matcher<Collection<String>>() {
@Override
protected boolean matchesSafely(Collection<String> object) {
return object.containsAll(tags);
}
});
}
}