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

io.helidon.microprofile.faulttolerance.FaultToleranceMetrics Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2018, 2023 Oracle and/or its affiliates.
 *
 * 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 io.helidon.microprofile.faulttolerance;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;

import io.helidon.common.LazyValue;

import jakarta.enterprise.inject.spi.CDI;
import jakarta.enterprise.util.AnnotationLiteral;
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.Metric;
import org.eclipse.microprofile.metrics.MetricID;
import org.eclipse.microprofile.metrics.MetricRegistry;
import org.eclipse.microprofile.metrics.MetricUnits;
import org.eclipse.microprofile.metrics.Tag;
import org.eclipse.microprofile.metrics.annotation.RegistryType;

import static io.helidon.microprofile.faulttolerance.FaultToleranceExtension.getRealClass;

/**
 * Utility class to register and fetch FT metrics.
 */
class FaultToleranceMetrics {

    static final String METRIC_NAME_TEMPLATE = "ft.%s.%s.%s";

    private static final ReentrantLock LOCK = new ReentrantLock();
    private static LazyValue metricRegistry = metricRegistryLazyValue();

    private FaultToleranceMetrics() {
    }

    static boolean enabled() {
        return getMetricRegistry() != null;
    }

    static MetricRegistry getMetricRegistry() {
        return metricRegistry.get();
    }

    static void close() {
        // Facilitates reuse in successive tests in the same JVM.
        metricRegistry = metricRegistryLazyValue();
    }

    private static LazyValue metricRegistryLazyValue() {
        return LazyValue.create(
                () -> CDI.current().select(MetricRegistry.class, new BaseRegistryTypeLiteral()).get());
    }

    /**
     * Annotation literal to inject base registry.
     */
    static class BaseRegistryTypeLiteral extends AnnotationLiteral implements RegistryType {

        @Override
        public MetricRegistry.Type type() {
            return MetricRegistry.Type.BASE;
        }
    }

    /**
     * Base class for Fault Tolerance metrics. Shares common logic for registration
     * and lookup of metrics.
     */
    abstract static class FaultToleranceMetric {

        abstract String name();

        abstract String description();

        abstract Class metricType();

        abstract String unit();

        protected Counter getCounter(Tag... tags) {
            MetricID metricID = new MetricID(name(), tags);
            return (Counter) getMetricRegistry().getMetrics().get(metricID);
        }

        protected Counter registerCounter(Tag... tags) {
            Counter counter = getCounter(tags);
            if (counter == null) {
                Metadata metadata = Metadata.builder()
                        .withName(name())
                        .withDescription(description())
                        .withUnit(unit())
                        .build();
                try {
                    counter = getMetricRegistry().counter(metadata, tags);
                } catch (IllegalArgumentException e) {
                    // Looks like we lost registration race
                    counter = getCounter(tags);
                    Objects.requireNonNull(counter);
                }
            }
            return counter;
        }

        protected Histogram getHistogram(Tag... tags) {
            MetricID metricID = new MetricID(name(), tags);
            return (Histogram) getMetricRegistry().getMetrics().get(metricID);
        }

        protected Histogram registerHistogram(Tag... tags) {
            Histogram histogram = getHistogram(tags);
            if (histogram == null) {
                Metadata metadata = Metadata.builder()
                        .withName(name())
                        .withDescription(description())
                        .withUnit(unit())
                        .build();
                try {
                    histogram = getMetricRegistry().histogram(metadata, tags);
                } catch (IllegalArgumentException e) {
                    // Looks like we lost the registration race
                    histogram = getHistogram(tags);
                    Objects.requireNonNull(histogram);
                }
            }
            return histogram;
        }

        @SuppressWarnings("unchecked")
        protected  Gauge getGauge(Tag... tags) {
            MetricID metricID = new MetricID(name(), tags);
            return (Gauge) getMetricRegistry().getMetrics().get(metricID);
        }

        @SuppressWarnings("unchecked")
        static  T getMetric(Method method, String name) {
            MetricID metricID = new MetricID(String.format(METRIC_NAME_TEMPLATE,
                    method.getDeclaringClass().getName(),
                    method.getName(), name));
            return (T) getMetricRegistry().getMetrics().get(metricID);
        }

        static Counter getCounter(Method method, String name) {
            return getMetric(method, name);
        }

        static Histogram getHistogram(Method method, String name) {
            return getMetric(method, name);
        }

        @SuppressWarnings("unchecked")
        static  Gauge getGauge(Method method, String name) {
            return getMetric(method, name);
        }

        static long getCounter(Object bean, String methodName, String name,
                               Class... params) throws Exception {
            Method method = findMethod(getRealClass(bean), methodName, params);
            return getCounter(method, name).getCount();
        }

        static Histogram getHistogram(Object bean, String methodName, String name,
                                      Class... params) throws Exception {
            Method method = findMethod(getRealClass(bean), methodName, params);
            return getHistogram(method, name);
        }

        static  Gauge getGauge(Object bean, String methodName, String name,
                                     Class... params) throws Exception {
            Method method = findMethod(getRealClass(bean), methodName, params);
            return getGauge(method, name);
        }

        /**
         * Attempts to find a method even if not accessible.
         *
         * @param beanClass bean class.
         * @param methodName name of method.
         * @param params param types.
         * @return method found.
         * @throws NoSuchMethodException if not found.
         */
        private static Method findMethod(Class beanClass, String methodName,
                                         Class... params) throws NoSuchMethodException {
            try {
                Method method = beanClass.getDeclaredMethod(methodName, params);
                method.setAccessible(true);
                return method;
            } catch (Exception e) {
                return beanClass.getMethod(methodName, params);
            }
        }

        @SuppressWarnings("unchecked")
        protected  Gauge registerGauge(Gauge newGauge, Tag... tags) {
            Gauge gauge = getGauge(tags);
            if (gauge == null) {
                Metadata metadata = Metadata.builder()
                        .withName(name())
                        .withDescription(description())
                        .withUnit(unit())
                        .build();
                try {
                    gauge = getMetricRegistry().gauge(metadata, newGauge::getValue, tags);
                } catch (IllegalArgumentException e) {
                    // Looks like we lost the registration race
                    gauge = getGauge(tags);
                    Objects.requireNonNull(gauge);
                }
            }
            return gauge;
        }
    }

    // -- Invocations ---------------------------------------------------------

    enum InvocationResult implements Supplier {
        VALUE_RETURNED("valueReturned"),
        EXCEPTION_THROWN("exceptionThrown");

        private final Tag metricTag;

        InvocationResult(String value) {
            metricTag = new Tag("result", value);
        }

        @Override
        public Tag get() {
            return metricTag;
        }
    }

    enum InvocationFallback implements Supplier {
        APPLIED("applied"),
        NOT_APPLIED("notApplied"),
        NOT_DEFINED("notDefined");

        private final Tag metricTag;

        InvocationFallback(String value) {
            metricTag = new Tag("fallback", value);
        }

        @Override
        public Tag get() {
            return metricTag;
        }
    }

    /**
     * Class for "ft.invocations.total" counters.
     */
    static class InvocationsTotal extends FaultToleranceMetric {

        static final InvocationsTotal INSTANCE = new InvocationsTotal();

        private InvocationsTotal() {
        }

        @Override
        String name() {
            return "ft.invocations.total";
        }

        @Override
        String description() {
            return "The number of times the method was called";
        }

        @Override
        Class metricType() {
            return Counter.class;
        }

        @Override
        String unit() {
            return MetricUnits.NONE;
        }

        static Counter get(Tag... tags) {
            return INSTANCE.registerCounter(tags);
        }

        static Counter register(Tag... tags) {
            return INSTANCE.registerCounter(tags);
        }
    }

    // -- Retries -------------------------------------------------------------

    enum RetryResult implements Supplier {
        VALUE_RETURNED("valueReturned"),
        EXCEPTION_NOT_RETRYABLE("exceptionNotRetryable"),
        MAX_RETRIES_REACHED("maxRetriesReached"),
        MAX_DURATION_REACHED("maxDurationReached");

        private final Tag metricTag;

        RetryResult(String value) {
            metricTag = new Tag("retryResult", value);
        }

        @Override
        public Tag get() {
            return metricTag;
        }
    }

    enum RetryRetried implements Supplier {
        TRUE("true"),
        FALSE("false");

        private final Tag metricTag;

        RetryRetried(String value) {
            metricTag = new Tag("retried", value);
        }

        @Override
        public Tag get() {
            return metricTag;
        }
    }

    /**
     * Class for "ft.retry.calls.total" counters.
     */
    static class RetryCallsTotal extends FaultToleranceMetric {

        static final RetryCallsTotal INSTANCE = new RetryCallsTotal();

        private RetryCallsTotal() {
        }

        @Override
        String name() {
            return "ft.retry.calls.total";
        }

        @Override
        String description() {
            return "The number of times the retry logic was run. This will always be once per method call.";
        }

        @Override
        Class metricType() {
            return Counter.class;
        }

        @Override
        String unit() {
            return MetricUnits.NONE;
        }

        static Counter get(Tag... tags) {
            return INSTANCE.registerCounter(tags);
        }

        static Counter register(Tag... tags) {
            return INSTANCE.registerCounter(tags);
        }
    }

    /**
     * Class for "ft.retry.retries.total" counters.
     */
    static class RetryRetriesTotal extends FaultToleranceMetric {

        static final RetryRetriesTotal INSTANCE = new RetryRetriesTotal();

        private RetryRetriesTotal() {
        }

        @Override
        String name() {
            return "ft.retry.retries.total";
        }

        @Override
        String description() {
            return "The number of times the method was retried";
        }

        @Override
        Class metricType() {
            return Counter.class;
        }

        @Override
        String unit() {
            return MetricUnits.NONE;
        }

        static Counter get(Tag... tags) {
            return INSTANCE.registerCounter(tags);
        }

        static Counter register(Tag... tags) {
            return INSTANCE.registerCounter(tags);
        }
    }

    // -- Timeouts ------------------------------------------------------------

    enum TimeoutTimedOut implements Supplier {
        TRUE("true"),
        FALSE("false");

        private final Tag metricTag;

        TimeoutTimedOut(String value) {
            this.metricTag = new Tag("timedOut", value);
        }

        public Tag get() {
            return metricTag;
        }
    }

    /**
     * Class for "ft.timeout.calls.total" counters.
     */
    static class TimeoutCallsTotal extends FaultToleranceMetric {

        static final TimeoutCallsTotal INSTANCE = new TimeoutCallsTotal();

        private TimeoutCallsTotal() {
        }

        @Override
        String name() {
            return "ft.timeout.calls.total";
        }

        @Override
        String description() {
            return "The number of times the timeout logic was run. This will usually be once "
                    + "per method call, but may be zero times if the circuit breaker prevents "
                    + "execution or more than once if the method is retried.";
        }

        @Override
        Class metricType() {
            return Counter.class;
        }

        @Override
        String unit() {
            return MetricUnits.NONE;
        }

        static Counter get(Tag... tags) {
            return INSTANCE.registerCounter(tags);
        }

        static Counter register(Tag... tags) {
            return INSTANCE.registerCounter(tags);
        }
    }

    /**
     * Class for "ft.timeout.executionDuration" histograms.
     */
    static class TimeoutExecutionDuration extends FaultToleranceMetric {

        static final TimeoutExecutionDuration INSTANCE = new TimeoutExecutionDuration();

        private TimeoutExecutionDuration() {
        }

        @Override
        String name() {
            return "ft.timeout.executionDuration";
        }

        @Override
        String description() {
            return "Histogram of execution times for the method";
        }

        @Override
        Class metricType() {
            return Histogram.class;
        }

        @Override
        String unit() {
            return MetricUnits.NANOSECONDS;
        }

        static Histogram get(Tag... tags) {
            return INSTANCE.registerHistogram(tags);
        }

        static Histogram register(Tag... tags) {
            return INSTANCE.registerHistogram(tags);
        }
    }

    // --- CircuitBreakers ----------------------------------------------------

    enum CircuitBreakerResult implements Supplier {
        SUCCESS("success"),
        FAILURE("failure"),
        CIRCUIT_BREAKER_OPEN("circuitBreakerOpen");

        private final Tag metricTag;

        CircuitBreakerResult(String value) {
            metricTag = new Tag("circuitBreakerResult", value);
        }

        @Override
        public Tag get() {
            return metricTag;
        }
    }

    enum CircuitBreakerState implements Supplier {
        OPEN("open"),
        CLOSED("closed"),
        HALF_OPEN("halfOpen");

        private final Tag metricTag;

        CircuitBreakerState(String value) {
            metricTag = new Tag("state", value);
        }

        @Override
        public Tag get() {
            return metricTag;
        }
    }

    /**
     * Class for "ft.circuitbreaker.calls.total" counters.
     */
    @SuppressWarnings("unchecked")
    static  Gauge registerGauge(Method method, String metricName, String description, Gauge gauge) {
        LOCK.lock();
        try {
            MetricID metricID = new MetricID(String.format(METRIC_NAME_TEMPLATE,
                    method.getDeclaringClass().getName(),
                    method.getName(),
                    metricName));
            Gauge existing = getMetricRegistry().getGauges().get(metricID);
            if (existing == null) {
                getMetricRegistry().gauge(Metadata.builder()
                        .withName(metricID.getName())
                        .withDescription(description)
                        .withUnit(MetricUnits.NANOSECONDS).build(),
                        gauge::getValue);
            }
            return existing;
        } finally {
            LOCK.unlock();
        }
    }

    static class CircuitBreakerCallsTotal extends FaultToleranceMetric {

        static final CircuitBreakerCallsTotal INSTANCE = new CircuitBreakerCallsTotal();

        private CircuitBreakerCallsTotal() {
        }

        @Override
        String name() {
            return "ft.circuitbreaker.calls.total";
        }

        @Override
        String description() {
            return "The number of times the circuit breaker logic was run. This will usually be once "
                    + "per method call, but may be more than once if the method call is retried.";
        }

        @Override
        Class metricType() {
            return Counter.class;
        }

        @Override
        String unit() {
            return MetricUnits.NONE;
        }

        static Counter get(Tag... tags) {
            return INSTANCE.registerCounter(tags);
        }

        static Counter register(Tag... tags) {
            return INSTANCE.registerCounter(tags);
        }
    }

    /**
     * Class for "ft.circuitbreaker.state.total" gauges.
     */
    static class CircuitBreakerStateTotal extends FaultToleranceMetric {

        static final CircuitBreakerStateTotal INSTANCE = new CircuitBreakerStateTotal();

        private CircuitBreakerStateTotal() {
        }

        @Override
        String name() {
            return "ft.circuitbreaker.state.total";
        }

        @Override
        String description() {
            return "Amount of time the circuit breaker has spent in each state";
        }

        @Override
        Class metricType() {
            return Gauge.class;
        }

        @Override
        String unit() {
            return MetricUnits.NANOSECONDS;
        }


        static Gauge get(Tag... tags) {
            return INSTANCE.getGauge(tags);
        }

        static Gauge register(Gauge gauge, Tag... tags) {
            return INSTANCE.registerGauge(gauge, tags);
        }
    }

    /**
     * Class for "ft.circuitbreaker.opened.total" counters.
     */
    static class CircuitBreakerOpenedTotal extends FaultToleranceMetric {

        static final CircuitBreakerOpenedTotal INSTANCE = new CircuitBreakerOpenedTotal();

        private CircuitBreakerOpenedTotal() {
        }

        @Override
        String name() {
            return "ft.circuitbreaker.opened.total";
        }

        @Override
        String description() {
            return "Number of times the circuit breaker has moved from closed state to open state";
        }

        @Override
        Class metricType() {
            return Counter.class;
        }

        @Override
        String unit() {
            return MetricUnits.NONE;
        }

        static Counter get(Tag... tags) {
            return INSTANCE.registerCounter(tags);
        }

        static Counter register(Tag... tags) {
            return INSTANCE.registerCounter(tags);
        }
    }

    // --- Bulkheads ----------------------------------------------------------

    enum BulkheadResult implements Supplier {
        ACCEPTED("accepted"),
        REJECTED("rejected");

        private final Tag metricTag;

        BulkheadResult(String value) {
            metricTag = new Tag("bulkheadResult", value);
        }

        @Override
        public Tag get() {
            return metricTag;
        }
    }

    /**
     * Class for "ft.bulkhead.calls.total" counters.
     */
    static class BulkheadCallsTotal extends FaultToleranceMetric {

        static final BulkheadCallsTotal INSTANCE = new BulkheadCallsTotal();

        private BulkheadCallsTotal() {
        }

        @Override
        String name() {
            return "ft.bulkhead.calls.total";
        }

        @Override
        String description() {
            return "The number of times the bulkhead logic was run. This will usually be once per "
                    + "method call, but may be zero times if the circuit breaker prevented execution "
                    + "or more than once if the method call is retried.";
        }

        @Override
        Class metricType() {
            return Counter.class;
        }

        @Override
        String unit() {
            return MetricUnits.NONE;
        }

        static Counter get(Tag... tags) {
            return INSTANCE.registerCounter(tags);
        }

        static Counter register(Tag... tags) {
            return INSTANCE.registerCounter(tags);
        }
    }

    /**
     * Class for "ft.bulkhead.executionsRunning" gauges.
     */
    static class BulkheadExecutionsRunning extends FaultToleranceMetric {

        static final BulkheadExecutionsRunning INSTANCE = new BulkheadExecutionsRunning();

        private BulkheadExecutionsRunning() {
        }

        @Override
        String name() {
            return "ft.bulkhead.executionsRunning";
        }

        @Override
        String description() {
            return "Number of currently running executions";
        }

        @Override
        Class metricType() {
            return Gauge.class;
        }

        @Override
        String unit() {
            return MetricUnits.NONE;
        }

        static Gauge get(Tag... tags) {
            return INSTANCE.getGauge(tags);
        }

        static Gauge register(Gauge gauge, Tag... tags) {
            return INSTANCE.registerGauge(gauge, tags);
        }
    }

    /**
     * Class for "ft.bulkhead.executionsWaiting" gauges.
     */
    static class BulkheadExecutionsWaiting extends FaultToleranceMetric {

        static final BulkheadExecutionsWaiting INSTANCE = new BulkheadExecutionsWaiting();

        private BulkheadExecutionsWaiting() {
        }

        @Override
        String name() {
            return "ft.bulkhead.executionsWaiting";
        }

        @Override
        String description() {
            return "Number of executions currently waiting in the queue";
        }

        @Override
        Class metricType() {
            return Gauge.class;
        }

        @Override
        String unit() {
            return MetricUnits.NONE;
        }

        static Gauge get(Tag... tags) {
            return INSTANCE.getGauge(tags);
        }

        static Gauge register(Gauge gauge, Tag... tags) {
            return INSTANCE.registerGauge(gauge, tags);
        }
    }

    /**
     * Class for "ft.bulkhead.runningDuration" histograms.
     */
    static class BulkheadRunningDuration extends FaultToleranceMetric {

        static final BulkheadRunningDuration INSTANCE = new BulkheadRunningDuration();

        private BulkheadRunningDuration() {
        }

        @Override
        String name() {
            return "ft.bulkhead.runningDuration";
        }

        @Override
        String description() {
            return "Histogram of the time that method executions spent running";
        }

        @Override
        Class metricType() {
            return Histogram.class;
        }

        @Override
        String unit() {
            return MetricUnits.NANOSECONDS;
        }

        static Histogram get(Tag... tags) {
            return INSTANCE.registerHistogram(tags);
        }

        static Histogram register(Tag... tags) {
            return INSTANCE.registerHistogram(tags);
        }
    }

    /**
     * Class for "ft.bulkhead.waitingDuration" histograms.
     */
    static class BulkheadWaitingDuration extends FaultToleranceMetric {

        static final BulkheadWaitingDuration INSTANCE = new BulkheadWaitingDuration();

        private BulkheadWaitingDuration() {
        }

        @Override
        String name() {
            return "ft.bulkhead.waitingDuration";
        }

        @Override
        String description() {
            return "Histogram of the time that method executions spent waiting in the queue";
        }

        @Override
        Class metricType() {
            return Histogram.class;
        }

        @Override
        String unit() {
            return MetricUnits.NANOSECONDS;
        }

        static Histogram get(Tag... tags) {
            return INSTANCE.registerHistogram(tags);
        }

        static Histogram register(Tag... tags) {
            return INSTANCE.registerHistogram(tags);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy