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

fish.payara.microprofile.metrics.impl.MetricRegistryImpl Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 *    Copyright (c) [2018-2020] Payara Foundation and/or its affiliates. All rights reserved.
 *
 *     The contents of this file are subject to the terms of either the GNU
 *     General Public License Version 2 only ("GPL") or the Common Development
 *     and Distribution License("CDDL") (collectively, the "License").  You
 *     may not use this file except in compliance with the License.  You can
 *     obtain a copy of the License at
 *     https://github.com/payara/Payara/blob/master/LICENSE.txt
 *     See the License for the specific
 *     language governing permissions and limitations under the License.
 *
 *     When distributing the software, include this License Header Notice in each
 *     file and include the License file at glassfish/legal/LICENSE.txt.
 *
 *     GPL Classpath Exception:
 *     The Payara Foundation designates this particular file as subject to the "Classpath"
 *     exception as provided by the Payara Foundation in the GPL Version 2 section of the License
 *     file that accompanied this code.
 *
 *     Modifications:
 *     If applicable, add the following below the License Header, with the fields
 *     enclosed by brackets [] replaced by your own identifying information:
 *     "Portions Copyright [year] [name of copyright owner]"
 *
 *     Contributor(s):
 *     If you wish your version of this file to be governed by only the CDDL or
 *     only the GPL Version 2, indicate your decision by adding "[Contributor]
 *     elects to include this software in this distribution under the [CDDL or GPL
 *     Version 2] license."  If you don't indicate a single choice of license, a
 *     recipient has the option to distribute your version of this file under
 *     either the CDDL, the GPL Version 2 or to extend the choice of license to
 *     its licensees as provided above.  However, if you add GPL Version 2 code
 *     and therefore, elected the GPL Version 2 license, then the option applies
 *     only if the new code is made subject to such option by the copyright
 *     holder.
 */
package fish.payara.microprofile.metrics.impl;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
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.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.enterprise.inject.Vetoed;
import org.eclipse.microprofile.metrics.ConcurrentGauge;
import org.eclipse.microprofile.metrics.Counter;
import org.eclipse.microprofile.metrics.Gauge;
import org.eclipse.microprofile.metrics.Histogram;
import org.eclipse.microprofile.metrics.Metadata;
import org.eclipse.microprofile.metrics.Meter;
import org.eclipse.microprofile.metrics.Metric;
import org.eclipse.microprofile.metrics.MetricFilter;

import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;
import static java.util.stream.Collectors.toMap;
import static org.eclipse.microprofile.metrics.MetricFilter.ALL;
import org.eclipse.microprofile.metrics.MetricID;
import org.eclipse.microprofile.metrics.MetricRegistry;
import org.eclipse.microprofile.metrics.MetricType;
import org.eclipse.microprofile.metrics.SimpleTimer;

import static org.eclipse.microprofile.metrics.MetricType.COUNTER;
import static org.eclipse.microprofile.metrics.MetricType.GAUGE;
import static org.eclipse.microprofile.metrics.MetricType.HISTOGRAM;
import static org.eclipse.microprofile.metrics.MetricType.METERED;
import static org.eclipse.microprofile.metrics.MetricType.SIMPLE_TIMER;
import static org.eclipse.microprofile.metrics.MetricType.TIMER;
import org.eclipse.microprofile.metrics.Tag;
import org.eclipse.microprofile.metrics.Timer;

/**
 * The MetricRegistry stores the metrics and metadata information
 */
@Vetoed
public class MetricRegistryImpl implements MetricRegistry {

    private static final Logger LOGGER = Logger.getLogger(MetricRegistryImpl.class.getName());

    static final class MetricFamily {
        final Metadata metadata;
        final ConcurrentMap metrics = new ConcurrentHashMap<>();

        MetricFamily(Metadata metadata) {
            this.metadata = metadata;
        }

        boolean remove(MetricID metricID) {
            return metrics.remove(metricID) != null;
        }

        T get(MetricID metricID) {
            return metrics.get(metricID);
        }
    }

    private final Type type;
    private final ConcurrentMap> metricsFamiliesByName = new ConcurrentHashMap<>();
    private final Clock clock;
    private final List listeners = new ArrayList<>();

    public MetricRegistryImpl(Type type) {
        this(type, Clock.defaultClock());
    }

    public MetricRegistryImpl(Type type, Clock clock) {
        this.type = type;
        this.clock = clock;
    }

    @Override
    public Type getType() {
        return type;
    }

    public MetricRegistryImpl addListener(MetricRegistrationListener listener) {
        listeners.add(listener);
        return this;
    }

    @Override
    public Counter counter(String name) {
        return findMetricOrCreate(name, COUNTER, new Tag[0]);
    }

    @Override
    public Counter counter(Metadata metadata) {
        return findMetricOrCreate(metadata, COUNTER);
    }

    @Override
    public Counter counter(String name, Tag... tags) {
        return findMetricOrCreate(name, COUNTER, tags);
    }

    @Override
    public Counter counter(Metadata metadata, Tag... tags) {
        return findMetricOrCreate(metadata, COUNTER, tags);
    }

    @Override
    public Counter counter(MetricID metricID) {
        return findMetricOrCreate(metricID.getName(), COUNTER, metricID.getTagsAsArray());
    }

    @Override
    public  Gauge gauge(Metadata metadata, T object, Function func, Tag... tags) {
        return gauge(metadata, () -> func.apply(object), tags);
    }

    @Override
    public  Gauge gauge(MetricID metricID, Supplier supplier) {
        return gauge(metricID.getName(), supplier, metricID.getTagsAsArray());
    }

    @Override
    public  Gauge gauge(MetricID metricID, T object, Function func) {
        return gauge(metricID, () -> func.apply(object));
    }

    @Override
    public  Gauge gauge(Metadata metadata, Supplier supplier, Tag... tags) {
        return findMetricOrCreate(metadata, GAUGE, createGauge(supplier), tags);
    }

    @Override
    public  Gauge gauge(String name, Supplier supplier, Tag... tags) {
        return findMetricOrCreate(name, GAUGE, createGauge(supplier), tags);
    }

    /**
     * This is a non-standard feature that the {@link Supplier} passed to a {@code gauge}-method can implement
     * {@link Gauge} as well in which case the passed instance is not wrapped in another lambda to get to {@link Gauge}
     * interface.
     */
    @SuppressWarnings("unchecked")
    private static  Gauge createGauge(Supplier supplier) {
        return supplier instanceof Gauge ? (Gauge) supplier : () -> supplier.get();
    }

    @Override
    public  Gauge gauge(String name, T object, Function func, Tag... tags) {
        return gauge(name, () -> func.apply(object), tags);
    }

    @Override
    public ConcurrentGauge concurrentGauge(String name) {
        return findMetricOrCreate(name, MetricType.CONCURRENT_GAUGE, new Tag[0]);
    }

    @Override
    public ConcurrentGauge concurrentGauge(String name, Tag... tags) {
        return findMetricOrCreate(name, MetricType.CONCURRENT_GAUGE, tags);
    }

    @Override
    public ConcurrentGauge concurrentGauge(Metadata metadata) {
        return findMetricOrCreate(metadata, MetricType.CONCURRENT_GAUGE);
    }

    @Override
    public ConcurrentGauge concurrentGauge(Metadata metadata, Tag... tags) {
        return findMetricOrCreate(metadata, MetricType.CONCURRENT_GAUGE, tags);
    }

    @Override
    public ConcurrentGauge concurrentGauge(MetricID metricID) {
        return findMetricOrCreate(metricID.getName(), MetricType.CONCURRENT_GAUGE, metricID.getTagsAsArray());
    }

    @Override
    public Histogram histogram(String name, Tag... tags) {
        return findMetricOrCreate(name, MetricType.HISTOGRAM, tags);
    }

    @Override
    public Histogram histogram(Metadata metadata, Tag... tags) {
        return findMetricOrCreate(metadata, MetricType.HISTOGRAM, tags);
    }

    @Override
    public Histogram histogram(String name) {
        return findMetricOrCreate(name, HISTOGRAM, new Tag[0]);
    }

    @Override
    public Histogram histogram(Metadata metadata) {
         return findMetricOrCreate(metadata, HISTOGRAM);
    }

    @Override
    public Histogram histogram(MetricID metricID) {
        return findMetricOrCreate(metricID.getName(), HISTOGRAM, metricID.getTagsAsArray());
    }

    @Override
    public Meter meter(String name, Tag... tags) {
        return findMetricOrCreate(name, METERED, tags);
    }

    @Override
    public Meter meter(Metadata metadata, Tag... tags) {
        return findMetricOrCreate(metadata, METERED, tags);
    }

    @Override
    public Meter meter(String name) {
        return findMetricOrCreate(name, METERED, new Tag[0]);
    }

    @Override
    public Meter meter(Metadata metadata) {
         return findMetricOrCreate(metadata, METERED);
    }

    @Override
    public Meter meter(MetricID metricID) {
        return findMetricOrCreate(metricID.getName(), METERED, metricID.getTagsAsArray());
    }

    @Override
    public SimpleTimer simpleTimer(String name, Tag... tags) {
        return findMetricOrCreate(name, SIMPLE_TIMER, tags);
    }

    @Override
    public SimpleTimer simpleTimer(Metadata metadata, Tag... tags) {
        return findMetricOrCreate(metadata, MetricType.SIMPLE_TIMER, tags);
    }

    @Override
    public SimpleTimer simpleTimer(String name) {
        return findMetricOrCreate(name, SIMPLE_TIMER, new Tag[0]);
    }

    @Override
    public SimpleTimer simpleTimer(Metadata metadata) {
        return findMetricOrCreate(metadata, MetricType.SIMPLE_TIMER, new Tag[0]);
    }

    @Override
    public SimpleTimer simpleTimer(MetricID metricID) {
        return findMetricOrCreate(metricID.getName(), SIMPLE_TIMER, metricID.getTagsAsArray());
    }

    @Override
    public Timer timer(String name, Tag... tags) {
        return findMetricOrCreate(name, TIMER, tags);
    }

    @Override
    public Timer timer(Metadata metadata, Tag... tags) {
        return findMetricOrCreate(metadata, TIMER, tags);
    }

    @Override
    public Timer timer(String name) {
        return findMetricOrCreate(name, TIMER, new Tag[0]);
    }

    @Override
    public Timer timer(Metadata metadata) {
        return findMetricOrCreate(metadata, TIMER);
    }

    @Override
    public Timer timer(MetricID metricID) {
        return findMetricOrCreate(metricID.getName(), TIMER, metricID.getTagsAsArray());
    }

    @Override
    public ConcurrentGauge getConcurrentGauge(MetricID metricID) {
        return getMetric(metricID, ConcurrentGauge.class);
    }

    @Override
    public Counter getCounter(MetricID metricID) {
        return getMetric(metricID, Counter.class);
    }

    @Override
    public Gauge getGauge(MetricID metricID) {
        return getMetric(metricID, Gauge.class);
    }

    @Override
    public Histogram getHistogram(MetricID metricID) {
        return getMetric(metricID, Histogram.class);
    }

    @Override
    public Meter getMeter(MetricID metricID) {
        return getMetric(metricID, Meter.class);
    }

    @Override
    public SimpleTimer getSimpleTimer(MetricID metricID) {
        return getMetric(metricID, SimpleTimer.class);
    }

    @Override
    public Timer getTimer(MetricID metricID) {
        return getMetric(metricID, Timer.class);
    }

    @Override
    public Metric getMetric(MetricID metricID) {
        MetricFamily family = metricsFamiliesByName.get(metricID.getName());
        return family == null ? null : family.get(metricID);
    }

    @Override
    public SortedSet getMetricIDs() {
        TreeSet ids = new TreeSet<>();
        for (MetricFamily e : metricsFamiliesByName.values()) {
            ids.addAll(e.metrics.keySet());
        }
        return ids;
    }

    @Override
    public SortedSet getNames() {
        return new TreeSet<>(metricsFamiliesByName.keySet());
    }

    @Override
    public SortedMap getGauges() {
        return getGauges(ALL);
    }

    @Override
    public SortedMap getGauges(MetricFilter filter) {
        return findMetrics(Gauge.class, filter);
    }

    @Override
    public SortedMap getConcurrentGauges() {
        return getConcurrentGauges(ALL);
    }

    @Override
    public SortedMap getConcurrentGauges(MetricFilter filter) {
        return findMetrics(ConcurrentGauge.class, filter);
    }

    @Override
    public SortedMap getCounters() {
        return getCounters(ALL);
    }

    @Override
    public SortedMap getCounters(MetricFilter filter) {
        return findMetrics(Counter.class, filter);
    }

    @Override
    public SortedMap getHistograms() {
        return getHistograms(ALL);
    }

    @Override
    public SortedMap getHistograms(MetricFilter filter) {
        return findMetrics(Histogram.class, filter);
    }

    @Override
    public SortedMap getMeters() {
        return getMeters(ALL);
    }

    @Override
    public SortedMap getMeters(MetricFilter filter) {
        return findMetrics(Meter.class, filter);
    }

    @Override
    public SortedMap getSimpleTimers() {
        return getSimpleTimers(ALL);
    }

    @Override
    public SortedMap getSimpleTimers(MetricFilter filter) {
        return findMetrics(SimpleTimer.class, filter);
    }

    @Override
    public SortedMap getTimers() {
        return getTimers(ALL);
    }

    @Override
    public SortedMap getTimers(MetricFilter filter) {
        return findMetrics(Timer.class, filter);
    }

    @Override
    public Map getMetrics() {
        return findMetrics((id, metric) -> true);
    }

    @Override
    public Map getMetadata() {
        return metricsFamiliesByName.entrySet().stream().collect(toMap(Entry::getKey,
                e -> e.getValue().metadata));
    }

    @Override
    public boolean remove(String name) {
        return metricsFamiliesByName.remove(name) != null;
    }

    @Override
    public boolean remove(MetricID metricID) {
        AtomicBoolean removed = new AtomicBoolean();
        metricsFamiliesByName.computeIfPresent(metricID.getName(), (name, family) -> {
            if (family.remove(metricID)) {
                removed.set(true);
            }
            return family.metrics.isEmpty() ? null : family;
        });
        return removed.get();
    }

    @Override
    public void removeMatching(MetricFilter filter) {
        if (filter == MetricFilter.ALL) {
            metricsFamiliesByName.clear();
        }
        Iterator> familyIter = metricsFamiliesByName.values().iterator();
        while (familyIter.hasNext()) {
            MetricFamily family = familyIter.next();
            Iterator> metricIter = family.metrics.entrySet().iterator();
            while (metricIter.hasNext()) {
                Entry entry = metricIter.next();
                if (filter.matches(entry.getKey(), entry.getValue())) {
                    remove(entry.getKey()); // OBS! it is important to not use the iterator.remove() so that the family is removed "atomically" when empty
                }
            }
        }
    }

    private  SortedMap findMetrics(Class metricClass, MetricFilter filter) {
        return findMetrics((id, metric) -> metricClass.isInstance(metric) && filter.matches(id, metric));
    }
    @SuppressWarnings("unchecked")
    private  SortedMap findMetrics(MetricFilter filter) {
        SortedMap matches = new TreeMap<>();
        for (MetricFamily family : metricsFamiliesByName.values()) {
            for (Entry entry : family.metrics.entrySet()) {
                if (filter.matches(entry.getKey(), entry.getValue())) {
                    matches.put(entry.getKey(), (T) entry.getValue());
                }
            }
        }
        return matches;
    }

    private  T findMetricOrCreate(String name, MetricType metricType, Tag... tags) {
        return findMetricOrCreate(name, metricType, null, tags);
    }

    private  T findMetricOrCreate(String name, MetricType metricType, T metric, Tag... tags) {
        checkNameIsNotNullOrEmpty(name);
        Metadata metadata = Metadata.builder()
                .withName(name)
                .withType(metricType)
                .withDisplayName("")
                .build();
        return findMetricOrCreate(metadata, true, metric, tags);
    }

    private  T findMetricOrCreate(Metadata metadata, MetricType metricType, Tag... tags) {
        return findMetricOrCreate(metadata, metricType, null, tags);
    }

    private  T findMetricOrCreate(Metadata metadata, MetricType metricType, T metric, Tag... tags) {
        return findMetricOrCreate(withType(metadata, metricType), false, metric, tags);
    }

    @SuppressWarnings("unchecked")
    private  T findMetricOrCreate(Metadata metadata, boolean useExistingMetadata, T metric, Tag... tags) {
        MetricID metricID = new MetricID(metadata.getName(), tags);
        MetricFamily family = metricsFamiliesByName.get(metricID.getName());
        if (family == null) {
            return register(metadata, useExistingMetadata, metric, tags);
        }
        Metric existing = family.get(metricID);
        if (existing == null) {
            checkSameType(metricID.getName(), metadata, family.metadata);
            return register(metadata, useExistingMetadata, metric, tags);
        }
        if (useExistingMetadata && metadata.getType() != family.metadata.getType()
                || !useExistingMetadata && !metadata.equals(family.metadata)) {
            throw new IllegalArgumentException(
                    String.format("Tried to lookup a metric with conflicting metadata, looup is %s, existing is %s",
                            metadata.toString(), family.metadata.toString()));
        }
        return (T) existing;
    }

    @Override
    public  T register(String name, T metric) throws IllegalArgumentException {
        checkNameIsNotNullOrEmpty(name);
        return register(Metadata.builder().withName(name).build(), true, metric);
    }

    @Override
    public  T register(Metadata metadata, T metric) throws IllegalArgumentException {
        return register(metadata, false, metric);
    }

    @Override
    public  T register(Metadata metadata, T metric, Tag... tags) throws IllegalArgumentException {
        return register(metadata, false, metric, tags);
    }

    @SuppressWarnings("unchecked")
    private  T register(Metadata metadata, boolean useExistingMetadata, T metric, Tag... tags) {
        if (metadata.getTypeRaw() == MetricType.INVALID) {
            metadata = withType(metadata, MetricType.from(metric.getClass()));
        }
        String name = metadata.getName();
        checkNameIsNotNullOrEmpty(name);
        if (useExistingMetadata) {
            Metadata existingMetadata = getMetadata(name);
            if (existingMetadata != null) {
                checkSameType(name, metadata, existingMetadata);
                metadata = existingMetadata;
            }
        }
        final Metadata newMetadata = metadata;
        final T newMetric = metric != null ? metric : (T) createMetricInstance(newMetadata);
        MetricFamily family = (MetricFamily) metricsFamiliesByName.computeIfAbsent(name,
                key -> new MetricFamily<>(newMetadata));
        MetricID metricID = new MetricID(name, tags);
        if (family.metadata != newMetadata) {
            checkReusableMetadata(name, newMetadata, family.metadata);
        }
        T current = family.metrics.computeIfAbsent(metricID, key -> newMetric);
        notifyRegistrationListeners(metricID);
        return current;
    }

    private void notifyRegistrationListeners(MetricID metricID) {
        for (MetricRegistrationListener l : listeners) {
            try {
                l.onRegistration(metricID, this);
            } catch (RuntimeException ex) {
                LOGGER.log(Level.WARNING, "Registration listener threw exception:", ex);
            }
        }
    }

    private static void checkNameIsNotNullOrEmpty(String name) {
        if (name == null || name.trim().isEmpty()) {
            throw new IllegalArgumentException("Metric name must not be null or empty");
        }
    }

    private static void checkReusableMetadata(String name, Metadata newMetadata, Metadata existingMetadata) {
        checkSameType(name, newMetadata, existingMetadata);

        if (!existingMetadata.equals(newMetadata)) {
            throw new IllegalArgumentException(String.format(
                  "Metadata ['%s'] already registered, does not match provided ['%s']",
                  existingMetadata.toString(), newMetadata.toString()
          ));
        }
    }

    private static void checkSameType(String name, Metadata newMetadata, Metadata existingMetadata) {
        //Only metrics of the same type can be reused under the same name
        if (existingMetadata.getTypeRaw() != newMetadata.getTypeRaw()) {
            throw new IllegalArgumentException(String.format(
                    "Metric ['%s'] type['%s'] does not match with existing type['%s']",
                    name, newMetadata.getType(), existingMetadata.getType()
            ));
        }
    }

    private Metric createMetricInstance(Metadata metadata) {
        String name = metadata.getName();
        switch (metadata.getTypeRaw()) {
        case COUNTER:
            return new CounterImpl();
        case CONCURRENT_GAUGE:
            return new ConcurrentGaugeImpl(clock);
        case GAUGE:
            throw new IllegalArgumentException(String.format("Unsupported operation for Gauge ['%s']", name));
        case METERED:
            return new MeterImpl();
        case HISTOGRAM:
            return new HistogramImpl();
        case TIMER:
            return new TimerImpl(clock);
        case SIMPLE_TIMER:
            return new SimpleTimerImpl(clock);
        case INVALID:
        default:
            throw new IllegalStateException("Invalid metric type : " + metadata.getTypeRaw());
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public  T getMetric(MetricID metricID, Class ofType) {
        MetricFamily family = metricsFamiliesByName.get(metricID.getName());
        if (family == null) {
            return null;
        }
        Metric metric = family.get(metricID);
        if (metric != null && !ofType.isAssignableFrom(metric.getClass())) {
            throw new IllegalArgumentException("Invalid metric type : " + ofType);
        }
        return (T) metric;
    }

    @SuppressWarnings("unchecked")
    @Override
    public  SortedMap getMetrics(Class ofType, MetricFilter filter) {
        return (SortedMap) findMetrics(
                (metricID, metric) -> filter.matches(metricID, metric) && ofType.isAssignableFrom(metric.getClass()));
    }

    @Override
    public SortedMap getMetrics(MetricFilter filter) {
        return findMetrics(filter);
    }

    @Override
    public Metadata getMetadata(String name) {
        MetricFamily family = metricsFamiliesByName.get(name);
        return family == null ? null : family.metadata;
    }

    /*
     * Non-API Methods (Extra Methods)
     */

    public Set getMetricsIDs(String name) {
        MetricFamily family = metricsFamiliesByName.get(name);
        return family == null ? emptySet() : unmodifiableSet(family.metrics.keySet());
    }

    public Map getMetrics(String name) {
        MetricFamily family = metricsFamiliesByName.get(name);
        return family == null
                ? emptyMap()
                : unmodifiableMap(family.metrics);
    }

    @Override
    public String toString() {
        StringBuilder str = new StringBuilder();
        for (Entry> family : metricsFamiliesByName.entrySet()) {
            str.append(family.getKey()).append(": ").append(family.getValue().metadata).append('\n');
            for (Entry entry : family.getValue().metrics.entrySet()) {
                str.append('\t').append(entry.getKey()).append(": ").append(toString(entry.getValue())).append('\n');
            }
        }
        return str.toString();
    }

    private static String toString(Metric metric) {
        if (isLambda(metric) && metric instanceof Gauge) {
            return "Gauge["+((Gauge)metric).getValue()+"]";
        }
        return metric.toString();
    }

    public static boolean isLambda(Object obj) {
        return obj.getClass().toString().contains("$$Lambda$");
    }

    /**
     * This is a workaround to prevent setting display name field as side effect of using
     * {@link Metadata#builder(Metadata)}. It does not prevent the issue in case the type needs setting but it can if it
     * does not need to be set. There is no API way to make the resulting {@link Metadata} identical in case the type is
     * set.
     */
    private static Metadata withType(Metadata metadata, MetricType type) {
        if (type == metadata.getTypeRaw()) {
            return metadata; // already set
        }
        return Metadata.builder(metadata).withType(type).build();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy