build.tmp.org.xbib.metrics.common.MetricRegistry Maven / Gradle / Ivy
package org.xbib.metrics.common;
import org.xbib.metrics.api.Gauge;
import org.xbib.metrics.api.Metric;
import org.xbib.metrics.api.MetricFilter;
import org.xbib.metrics.api.MetricName;
import org.xbib.metrics.api.MetricSet;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A registry of metric instances.
*/
public class MetricRegistry implements MetricSet {
private static final Logger logger = Logger.getLogger(MetricRegistry.class.getName());
private final ConcurrentMap metrics;
private final List listeners;
/**
* Creates a new {@link MetricRegistry}.
*/
public MetricRegistry() {
this(new ConcurrentHashMap<>());
}
/**
* Creates a {@link MetricRegistry} with a custom {@link ConcurrentMap} implementation for use
* inside the registry. Call as the super-constructor to create a {@link MetricRegistry} with
* space- or time-bounded metric lifecycles, for example.
*
* @param metricsMap metrics map
*/
protected MetricRegistry(ConcurrentMap metricsMap) {
this.metrics = metricsMap;
this.listeners = new CopyOnWriteArrayList<>();
}
/**
* @param klass the class
* @param names The remaining elements of the name
* @return A metric name matching the specified components.
* @see #name(String, String...)
*/
public static MetricName name(Class> klass, String... names) {
return name(klass.getName(), names);
}
/**
* Shorthand method for backwards compatibility in creating metric names.
*
* Uses {@link MetricName#build(String...)} for its
* heavy lifting.
*
* @param name The first element of the name
* @param names The remaining elements of the name
* @return A metric name matching the specified components.
* @see MetricName#build(String...)
*/
public static MetricName name(String name, String... names) {
final int length;
if (names == null) {
length = 0;
} else {
length = names.length;
}
final String[] parts = new String[length + 1];
parts[0] = name;
if (names != null) {
System.arraycopy(names, 0, parts, 1, length);
}
return MetricName.build(parts);
}
/**
* @param name the metric name
* @param metric the metric
* @param the type of the metric
* @return {@code metric}
* @see #register(MetricName, Metric)
*/
@SuppressWarnings("unchecked")
public T register(String name, T metric) {
return register(MetricName.build(name), metric);
}
/**
* Given a {@link Metric}, registers it under the given name.
*
* @param name the name of the metric
* @param metric the metric
* @param the type of the metric
* @return {@code metric}
* @throws IllegalArgumentException if the name is already registered
*/
@SuppressWarnings("unchecked")
public T register(MetricName name, T metric) {
if (metric instanceof MetricSet) {
registerAll(name, (MetricSet) metric);
} else {
final Metric existing = metrics.putIfAbsent(name, metric);
if (existing == null) {
onMetricAdded(name, metric);
} else {
throw new IllegalArgumentException("A metric named " + name + " already exists");
}
}
return metric;
}
/**
* Given a metric set, registers them.
*
* @param metrics a set of metrics
* @throws IllegalArgumentException if any of the names are already registered
*/
public void registerAll(MetricSet metrics) {
registerAll(null, metrics);
}
/**
* @param name the name of the metric
* @return a new or pre-existing {@link CountMetric}
* @see #counter(MetricName)
*/
public CountMetric counter(String name) {
return counter(MetricName.build(name));
}
/**
* Return the {@link CountMetric} registered under this name; or create and register
* a new {@link CountMetric} if none is registered.
*
* @param name the name of the metric
* @return a new or pre-existing {@link CountMetric}
*/
public CountMetric counter(MetricName name) {
return getOrAdd(name, MetricBuilder.COUNTERS);
}
/**
* @param name the name of the metric
* @return a new or pre-existing {@link Histogram}
* @see #histogram(MetricName)
*/
public Histogram histogram(String name) {
return histogram(MetricName.build(name));
}
/**
* Return the {@link Histogram} registered under this name; or create and register
* a new {@link Histogram} if none is registered.
*
* @param name the name of the metric
* @return a new or pre-existing {@link Histogram}
*/
public Histogram histogram(MetricName name) {
return getOrAdd(name, MetricBuilder.HISTOGRAMS);
}
/**
* @param name the name of the metric
* @return a new or pre-existing {@link Meter}
* @see #meter(MetricName)
*/
public Meter meter(String name) {
return meter(MetricName.build(name));
}
/**
* Return the {@link Meter} registered under this name; or create and register
* a new {@link Meter} if none is registered.
*
* @param name the name of the metric
* @return a new or pre-existing {@link Meter}
*/
public Meter meter(MetricName name) {
return getOrAdd(name, MetricBuilder.METERS);
}
/**
* @param name the name of the metric
* @return a new or pre-existing {@link Sampler}
* @see #timer(MetricName)
*/
public Sampler timer(String name) {
return timer(MetricName.build(name));
}
/**
* Return the {@link Sampler} registered under this name; or create and register
* a new {@link Sampler} if none is registered.
*
* @param name the name of the metric
* @return a new or pre-existing {@link Sampler}
*/
public Sampler timer(MetricName name) {
return getOrAdd(name, MetricBuilder.TIMERS);
}
/**
* Removes the metric with the given name.
*
* @param name the name of the metric
* @return whether or not the metric was removed
*/
public boolean remove(MetricName name) {
final Metric metric = metrics.remove(name);
if (metric != null) {
onMetricRemoved(name, metric);
return true;
}
return false;
}
/**
* Removes all metrics which match the given filter.
*
* @param filter a filter
*/
public void removeMatching(MetricFilter filter) {
for (Map.Entry entry : metrics.entrySet()) {
if (filter.matches(entry.getKey(), entry.getValue())) {
remove(entry.getKey());
}
}
}
/**
* Adds a {@link Listener} to a collection of listeners that will be notified on
* metric creation. Listeners will be notified in the order in which they are added.
* The listener will be notified of all existing metrics when it first registers.
*
* @param listener the listener that will be notified
*/
public void addListener(Listener listener) {
listeners.add(listener);
for (Map.Entry entry : metrics.entrySet()) {
notifyListenerOfAddedMetric(listener, entry.getValue(), entry.getKey());
}
}
/**
* Removes a {@link Listener} from this registry's collection of listeners.
*
* @param listener the listener that will be removed
*/
public void removeListener(Listener listener) {
listeners.remove(listener);
}
/**
* Returns a set of the names of all the metrics in the registry.
*
* @return the names of all the metrics
*/
public SortedSet getNames() {
return Collections.unmodifiableSortedSet(new TreeSet<>(metrics.keySet()));
}
/**
* Returns a map of all the gauges in the registry and their names.
*
* @return all the gauges in the registry
*/
@SuppressWarnings("rawtypes")
public SortedMap getGauges() {
return getGauges(MetricFilter.ALL);
}
/**
* Returns a map of all the gauges in the registry and their names which match the given filter.
*
* @param filter the metric filter to match
* @return all the gauges in the registry
*/
@SuppressWarnings("rawtypes")
public SortedMap getGauges(MetricFilter filter) {
return getMetrics(Gauge.class, filter);
}
/**
* Returns a map of all the counters in the registry and their names.
*
* @return all the counters in the registry
*/
public SortedMap getCounters() {
return getCounters(MetricFilter.ALL);
}
/**
* Returns a map of all the counters in the registry and their names which match the given
* filter.
*
* @param filter the metric filter to match
* @return all the counters in the registry
*/
public SortedMap getCounters(MetricFilter filter) {
return getMetrics(CountMetric.class, filter);
}
/**
* Returns a map of all the histograms in the registry and their names.
*
* @return all the histograms in the registry
*/
public SortedMap getHistograms() {
return getHistograms(MetricFilter.ALL);
}
/**
* Returns a map of all the histograms in the registry and their names which match the given
* filter.
*
* @param filter the metric filter to match
* @return all the histograms in the registry
*/
public SortedMap getHistograms(MetricFilter filter) {
return getMetrics(Histogram.class, filter);
}
/**
* Returns a map of all the meters in the registry and their names.
*
* @return all the meters in the registry
*/
public SortedMap getMeters() {
return getMeters(MetricFilter.ALL);
}
/**
* Returns a map of all the meters in the registry and their names which match the given filter.
*
* @param filter the metric filter to match
* @return all the meters in the registry
*/
public SortedMap getMeters(MetricFilter filter) {
return getMetrics(Meter.class, filter);
}
/**
* Returns a map of all the timers in the registry and their names.
*
* @return all the timers in the registry
*/
public SortedMap getTimers() {
return getTimers(MetricFilter.ALL);
}
/**
* Returns a map of all the timers in the registry and their names which match the given filter.
*
* @param filter the metric filter to match
* @return all the timers in the registry
*/
public SortedMap getTimers(MetricFilter filter) {
return getMetrics(Sampler.class, filter);
}
@SuppressWarnings("unchecked")
private T getOrAdd(MetricName name, MetricBuilder builder) {
final Metric metric = metrics.get(name);
if (builder.isInstance(metric)) {
return (T) metric;
} else if (metric == null) {
try {
return register(name, builder.newMetric());
} catch (IllegalArgumentException e) {
logger.log(Level.WARNING, e.getMessage(), e);
final Metric added = metrics.get(name);
if (builder.isInstance(added)) {
return (T) added;
}
}
}
throw new IllegalArgumentException(name + " is already used for a different type of metric");
}
@SuppressWarnings("unchecked")
private SortedMap getMetrics(Class klass, MetricFilter filter) {
final TreeMap timers = new TreeMap<>();
for (Map.Entry entry : metrics.entrySet()) {
if (klass.isInstance(entry.getValue()) && filter.matches(entry.getKey(),
entry.getValue())) {
timers.put(entry.getKey(), (T) entry.getValue());
}
}
return Collections.unmodifiableSortedMap(timers);
}
private void onMetricAdded(MetricName name, Metric metric) {
for (Listener listener : listeners) {
notifyListenerOfAddedMetric(listener, metric, name);
}
}
private void notifyListenerOfAddedMetric(Listener listener, Metric metric, MetricName name) {
if (metric instanceof Gauge) {
listener.onGaugeAdded(name, (Gauge>) metric);
} else if (metric instanceof CountMetric) {
listener.onCounterAdded(name, (CountMetric) metric);
} else if (metric instanceof Histogram) {
listener.onHistogramAdded(name, (Histogram) metric);
} else if (metric instanceof Meter) {
listener.onMeterAdded(name, (Meter) metric);
} else if (metric instanceof Sampler) {
listener.onTimerAdded(name, (Sampler) metric);
} else {
throw new IllegalArgumentException("Unknown metric type: " + metric.getClass());
}
}
private void onMetricRemoved(MetricName name, Metric metric) {
for (Listener listener : listeners) {
notifyListenerOfRemovedMetric(name, metric, listener);
}
}
private void notifyListenerOfRemovedMetric(MetricName name, Metric metric, Listener listener) {
if (metric instanceof Gauge) {
listener.onGaugeRemoved(name);
} else if (metric instanceof CountMetric) {
listener.onCounterRemoved(name);
} else if (metric instanceof Histogram) {
listener.onHistogramRemoved(name);
} else if (metric instanceof Meter) {
listener.onMeterRemoved(name);
} else if (metric instanceof Sampler) {
listener.onTimerRemoved(name);
} else {
throw new IllegalArgumentException("Unknown metric type: " + metric.getClass());
}
}
private void registerAll(MetricName prefixName, MetricSet metrics) {
MetricName prefix = prefixName == null ? MetricName.EMPTY : prefixName;
for (Map.Entry entry : metrics.getMetrics().entrySet()) {
if (entry.getValue() instanceof MetricSet) {
registerAll(MetricName.join(prefix, entry.getKey()), (MetricSet) entry.getValue());
} else {
register(MetricName.join(prefix, entry.getKey()), entry.getValue());
}
}
}
@Override
public Map getMetrics() {
return Collections.unmodifiableMap(metrics);
}
/**
* Listeners for events from the registry. Listeners must be thread-safe.
*/
public interface Listener {
/**
* Called when a {@link Gauge} is added to the registry.
*
* @param name the gauge's name
* @param gauge the gauge
*/
void onGaugeAdded(MetricName name, Gauge> gauge);
/**
* Called when a {@link Gauge} is removed from the registry.
*
* @param name the gauge's name
*/
void onGaugeRemoved(MetricName name);
/**
* Called when a {@link CountMetric} is added to the registry.
*
* @param name the counter's name
* @param counter the counter
*/
void onCounterAdded(MetricName name, CountMetric counter);
/**
* Called when a {@link CountMetric} is removed from the registry.
*
* @param name the counter's name
*/
void onCounterRemoved(MetricName name);
/**
* Called when a {@link Histogram} is added to the registry.
*
* @param name the histogram's name
* @param histogram the histogram
*/
void onHistogramAdded(MetricName name, Histogram histogram);
/**
* Called when a {@link Histogram} is removed from the registry.
*
* @param name the histogram's name
*/
void onHistogramRemoved(MetricName name);
/**
* Called when a {@link Meter} is added to the registry.
*
* @param name the meter's name
* @param meter the meter
*/
void onMeterAdded(MetricName name, Meter meter);
/**
* Called when a {@link Meter} is removed from the registry.
*
* @param name the meter's name
*/
void onMeterRemoved(MetricName name);
/**
* Called when a {@link Sampler} is added to the registry.
*
* @param name the sampler's name
* @param sampler the sampler
*/
void onTimerAdded(MetricName name, Sampler sampler);
/**
* Called when a {@link Sampler} is removed from the registry.
*
* @param name the timer's name
*/
void onTimerRemoved(MetricName name);
}
/**
* A no-op implementation of {@link Listener}.
*/
abstract class Base implements Listener {
@Override
public void onGaugeAdded(MetricName name, Gauge> gauge) {
}
@Override
public void onGaugeRemoved(MetricName name) {
}
@Override
public void onCounterAdded(MetricName name, CountMetric counter) {
}
@Override
public void onCounterRemoved(MetricName name) {
}
@Override
public void onHistogramAdded(MetricName name, Histogram histogram) {
}
@Override
public void onHistogramRemoved(MetricName name) {
}
@Override
public void onMeterAdded(MetricName name, Meter meter) {
}
@Override
public void onMeterRemoved(MetricName name) {
}
@Override
public void onTimerAdded(MetricName name, Sampler sampler) {
}
@Override
public void onTimerRemoved(MetricName name) {
}
}
/**
* A quick and easy way of capturing the notion of default metrics.
*/
private interface MetricBuilder {
MetricBuilder COUNTERS = new MetricBuilder() {
@Override
public CountMetric newMetric() {
return new CountMetric();
}
@Override
public boolean isInstance(Metric metric) {
return CountMetric.class.isInstance(metric);
}
};
MetricBuilder HISTOGRAMS = new MetricBuilder() {
@Override
public Histogram newMetric() {
return new Histogram(new ExponentiallyDecayingReservoir());
}
@Override
public boolean isInstance(Metric metric) {
return Histogram.class.isInstance(metric);
}
};
MetricBuilder METERS = new MetricBuilder() {
@Override
public Meter newMetric() {
return new Meter(Executors.newSingleThreadScheduledExecutor());
}
@Override
public boolean isInstance(Metric metric) {
return Meter.class.isInstance(metric);
}
};
MetricBuilder TIMERS = new MetricBuilder() {
@Override
public Sampler newMetric() {
return new Sampler(Executors.newSingleThreadScheduledExecutor());
}
@Override
public boolean isInstance(Metric metric) {
return Sampler.class.isInstance(metric);
}
};
T newMetric();
boolean isInstance(Metric metric);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy