All Downloads are FREE. Search and download functionalities are using the official Maven repository.

build.tmp.org.xbib.metrics.common.MetricRegistry Maven / Gradle / Ivy

There is a newer version: 2.1.0
Show newest version
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