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

org.opendaylight.serviceutils.metrics.internal.AbstractMetricProvider Maven / Gradle / Ivy

/*
 * Copyright (c) 2017 - 2018 Red Hat, Inc. and others. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */
package org.opendaylight.serviceutils.metrics.internal;

import static com.codahale.metrics.Slf4jReporter.LoggingLevel.INFO;
import static java.lang.management.ManagementFactory.getThreadMXBean;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Slf4jReporter;
import com.codahale.metrics.jmx.JmxReporter;
import com.codahale.metrics.jvm.BufferPoolMetricSet;
import com.codahale.metrics.jvm.CachedThreadStatesGaugeSet;
import com.codahale.metrics.jvm.ClassLoadingGaugeSet;
import com.codahale.metrics.jvm.FileDescriptorRatioGauge;
import com.codahale.metrics.jvm.GarbageCollectorMetricSet;
import com.codahale.metrics.jvm.MemoryUsageGaugeSet;
import com.codahale.metrics.jvm.ThreadDeadlockDetector;
import com.google.common.annotations.VisibleForTesting;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.serviceutils.metrics.Counter;
import org.opendaylight.serviceutils.metrics.Labeled;
import org.opendaylight.serviceutils.metrics.Meter;
import org.opendaylight.serviceutils.metrics.MetricDescriptor;
import org.opendaylight.serviceutils.metrics.MetricProvider;
import org.opendaylight.serviceutils.metrics.Timer;
import org.opendaylight.serviceutils.metrics.function.CheckedCallable;
import org.opendaylight.serviceutils.metrics.function.CheckedRunnable;
import org.opendaylight.yangtools.concepts.AbstractRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implementation of {@link MetricProvider} based on Coda Hale's Dropwizard Metrics.
 *
 * @author Michael Vorburger.ch
 */
abstract class AbstractMetricProvider implements MetricProvider {
    private static final Logger LOG = LoggerFactory.getLogger(AbstractMetricProvider.class);

    private final Map meters = new ConcurrentHashMap<>();
    private final Map counters = new ConcurrentHashMap<>();
    private final Map timers = new ConcurrentHashMap<>();
    private final MetricRegistry registry = new MetricRegistry();

    private @Nullable JmxReporter jmxReporter;
    private @Nullable Slf4jReporter slf4jReporter;

    private volatile @Nullable MetricsFileReporter fileReporter;
    private volatile @Nullable ThreadsWatcher threadsWatcher;

    public final void updateConfiguration(Configuration configuration) {
        if (threadsWatcher != null) {
            threadsWatcher.close();
        }
        if (configuration.threadsWatcherIntervalMS() > 0 && (threadsWatcher == null
                || configuration.threadsWatcherIntervalMS() != threadsWatcher.getInterval().toMillis()
                || configuration.maxThreads() != threadsWatcher.getMaxThreads()
                || configuration.maxThreadsMaxLogIntervalSecs()
                        != threadsWatcher.getMaxThreadsMaxLogInterval().getSeconds()
                || configuration.deadlockedThreadsMaxLogIntervalSecs()
                        != threadsWatcher.getDeadlockedThreadsMaxLogInterval().getSeconds())) {

            ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
            if (threadMXBean != null) {
                threadsWatcher = new ThreadsWatcher(threadMXBean, configuration.maxThreads(),
                    Duration.ofMillis(configuration.threadsWatcherIntervalMS()),
                    Duration.ofSeconds(configuration.maxThreadsMaxLogIntervalSecs()),
                    Duration.ofSeconds(configuration.deadlockedThreadsMaxLogIntervalSecs()));
                threadsWatcher.start();
            } else {
                LOG.warn("Platform does not support ThreadMXBean, not starting ThreadsWatcher");
            }
        }

        if (fileReporter != null) {
            fileReporter.close();
        }
        int fileReporterInterval = fileReporter != null ? (int)fileReporter.getInterval().getSeconds() : 0;
        if (fileReporterInterval != configuration.fileReporterIntervalSecs()) {
            if (configuration.fileReporterIntervalSecs() > 0) {
                fileReporter = new MetricsFileReporter(registry,
                        Duration.ofSeconds(configuration.fileReporterIntervalSecs()));
                fileReporter.startReporter();
            }
        }
        LOG.info("Updated: {}", configuration);
    }

    final void start() {
        setUpJvmMetrics(registry);

        jmxReporter = setUpJmxReporter(registry);

        slf4jReporter = setUpSlf4jReporter(registry);

        // TODO really get this to work in Karaf, through PAX Logging.. (it's currently NOK)
        // instrumentLog4jV2(registry);
    }

    final void stop() {
        if (jmxReporter != null) {
            jmxReporter.close();
            jmxReporter = null;
        }
        if (fileReporter != null) {
            fileReporter.close();
            fileReporter = null;
        }
        if (slf4jReporter != null) {
            slf4jReporter.close();
            slf4jReporter = null;
        }
        if (threadsWatcher != null) {
            threadsWatcher.close();
        }
    }

    @VisibleForTesting
    public final MetricRegistry getRegistry() {
        return registry;
    }

    private static void setUpJvmMetrics(MetricRegistry registry) {
        FileDescriptorRatioGauge fileDescriptorRatioGauge = new FileDescriptorRatioGauge();

        registry.registerAll(new GarbageCollectorMetricSet());
        registry.registerAll(new BufferPoolMetricSet(ManagementFactory.getPlatformMBeanServer()));
        registry.registerAll(new CachedThreadStatesGaugeSet(getThreadMXBean(), new ThreadDeadlockDetector(),
            13, SECONDS));
        registry.registerAll(new MemoryUsageGaugeSet());
        registry.registerAll(new ClassLoadingGaugeSet());
        registry.gauge("odl.infrautils.FileDescriptorRatio", () -> fileDescriptorRatioGauge);
    }

    private static JmxReporter setUpJmxReporter(MetricRegistry registry) {
        JmxReporter reporter = JmxReporter.forRegistry(registry)
                .createsObjectNamesWith(new CustomObjectNameFactory()).build();
        reporter.start();
        LOG.info("JmxReporter started, ODL application's metrics are now available via JMX");
        return reporter;
    }

    private static Slf4jReporter setUpSlf4jReporter(MetricRegistry registry) {
        Slf4jReporter slf4jReporter = Slf4jReporter.forRegistry(registry)
                .convertDurationsTo(MILLISECONDS)
                .convertRatesTo(SECONDS)
                .outputTo(LOG)
                .prefixedWith("JVM")
                .withLoggingLevel(INFO)
                .shutdownExecutorOnStop(true)
                .build();
        // NB: We do intentionally *NOT* start() the Slf4jReporter to log all metrics regularly;
        // as that will spam the log, and we have our own file based reporting instead.
        // We do log system metrics once at boot up:
        LOG.info("One time system JVM metrics FYI; "
                + "to watch continously, monitor via JMX or enable periodic file dump option");
        slf4jReporter.report();
        return slf4jReporter;
    }

    // http://metrics.dropwizard.io/3.1.0/manual/log4j/
//    private static void instrumentLog4jV2(MetricRegistry registry) {
//        // TODO Confirm that Level ALL is a good idea?
//        Level level = ALL;
//
//        InstrumentedAppender appender = new InstrumentedAppender(registry,
//                null /* null Filter fine, because we don't use filters */,
//                null /* null PatternLayout, because the layout isn't used in InstrumentedAppender */, false);
//        appender.start();
//
//        LoggerContext context = (LoggerContext) LogManager.getContext(false);
//        Configuration config = context.getConfiguration();
//        config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME).addAppender(appender, level,
//                null /* null Filter fine, because we don't use filters */);
//        context.updateLoggers(config);
//    }

    private org.opendaylight.serviceutils.metrics.Meter newOrExistingMeter(Object anchor, String id) {
        return meters.computeIfAbsent(id, newId -> {
            LOG.debug("New Meter metric: {}", id);
            return new MeterImpl(newId);
        });
    }

    @Override
    public final org.opendaylight.serviceutils.metrics.Meter newMeter(Object anchor, String id) {
        requireNonNull(anchor, "anchor == null");
        checkForExistingID(id);
        return newOrExistingMeter(anchor, id);
    }

    @Override
    public final Meter newMeter(MetricDescriptor descriptor) {
        return newMeter(descriptor.anchor(), makeCodahaleID(descriptor));
    }

    @Override
    public final Labeled newMeter(MetricDescriptor descriptor, String labelName) {
        return labelValue -> newOrExistingMeter(descriptor.anchor(),
                makeCodahaleID(descriptor) + "{" + labelName + "=" + labelValue + "}");
    }

    @Override
    public final Labeled> newMeter(MetricDescriptor descriptor,
            String firstLabelName, String secondLabelName) {
        return firstLabelValue -> secondLabelValue -> newOrExistingMeter(descriptor.anchor(), makeCodahaleID(descriptor)
                + "{" + firstLabelName + "=" + firstLabelValue + "," + secondLabelName + "=" + secondLabelValue + "}");
    }

    @Override
    public final Labeled>> newMeter(MetricDescriptor descriptor,
            String firstLabelName, String secondLabelName, String thirdLabelName) {
        return firstLabelValue -> secondLabelValue -> thirdLabelValue ->
        newOrExistingMeter(descriptor.anchor(), makeCodahaleID(descriptor) + "{"
                + firstLabelName + "=" + firstLabelValue + "," + secondLabelName + "=" + secondLabelValue  + ","
                + thirdLabelName + "=" + thirdLabelValue + "}");
    }

    @Override
    public final Labeled>>> newMeter(MetricDescriptor descriptor,
            String firstLabelName, String secondLabelName, String thirdLabelName, String fourthLabelName) {
        return firstLabelValue -> secondLabelValue -> thirdLabelValue -> fourthLabelValue ->
                newOrExistingMeter(descriptor.anchor(), makeCodahaleID(descriptor) + "{"
                        + firstLabelName + "=" + firstLabelValue + "," + secondLabelName + "=" + secondLabelValue  + ","
                        + thirdLabelName + "=" + thirdLabelValue + ","
                        + fourthLabelName + "=" + fourthLabelValue + "}");
    }

    @Override
    public final Labeled>>>> newMeter(MetricDescriptor descriptor,
            String firstLabelName, String secondLabelName, String thirdLabelName,
            String fourthLabelName, String fifthLabelName) {
        return firstLabelValue -> secondLabelValue -> thirdLabelValue -> fourthLabelValue -> fifthLabelValue ->
                newOrExistingMeter(descriptor.anchor(), makeCodahaleID(descriptor) + "{"
                        + firstLabelName + "=" + firstLabelValue + "," + secondLabelName + "=" + secondLabelValue  + ","
                        + thirdLabelName + "=" + thirdLabelValue + ","
                        + fourthLabelName + "=" + fourthLabelValue + ","
                        + fifthLabelName + "=" + fifthLabelValue + "}");
    }

    private org.opendaylight.serviceutils.metrics.Counter newOrExistingCounter(Object anchor, String id) {
        return counters.computeIfAbsent(id, newId -> {
            LOG.debug("New Counter metric: {}", id);
            return new CounterImpl(newId);
        });
    }

    @Override
    public final org.opendaylight.serviceutils.metrics.Counter newCounter(Object anchor, String id) {
        requireNonNull(anchor, "anchor == null");
        checkForExistingID(id);
        return newOrExistingCounter(anchor, id);
    }

    @Override
    public final Counter newCounter(MetricDescriptor descriptor) {
        return newCounter(descriptor.anchor(), makeCodahaleID(descriptor));
    }

    @Override
    public final Labeled newCounter(MetricDescriptor descriptor, String labelName) {
        return labelValue -> newOrExistingCounter(descriptor.anchor(),
                makeCodahaleID(descriptor) + "{" + labelName + "=" + labelValue + "}");
    }

    @Override
    public final Labeled> newCounter(MetricDescriptor descriptor,
            String firstLabelName, String secondLabelName) {
        return firstLabelValue -> secondLabelValue -> newOrExistingCounter(descriptor.anchor(),
                makeCodahaleID(descriptor) + "{" + firstLabelName + "=" + firstLabelValue + ","
                    + secondLabelName + "=" + secondLabelValue + "}");
    }

    @Override
    public final Labeled>> newCounter(MetricDescriptor descriptor, String firstLabelName,
            String secondLabelName, String thirdLabelName) {
        return firstLabelValue -> secondLabelValue -> thirdLabelValue ->
                newOrExistingCounter(descriptor.anchor(), makeCodahaleID(descriptor) + "{"
                        + firstLabelName + "=" + firstLabelValue + "," + secondLabelName + "=" + secondLabelValue  + ","
                        + thirdLabelName + "=" + thirdLabelValue + "}");
    }

    @Override
    public final Labeled>>> newCounter(MetricDescriptor descriptor,
            String firstLabelName, String secondLabelName, String thirdLabelName, String fourthLabelName) {
        return firstLabelValue -> secondLabelValue -> thirdLabelValue -> fourthLabelValue ->
                newOrExistingCounter(descriptor.anchor(), makeCodahaleID(descriptor) + "{"
                        + firstLabelName + "=" + firstLabelValue + "," + secondLabelName + "=" + secondLabelValue  + ","
                        + thirdLabelName + "=" + thirdLabelValue + ","
                        + fourthLabelName + "=" + fourthLabelValue + "}");
    }

    @Override
    public final Labeled>>>> newCounter(MetricDescriptor descriptor,
            String firstLabelName, String secondLabelName, String thirdLabelName, String fourthLabelName,
            String fifthLabelName) {
        return firstLabelValue -> secondLabelValue -> thirdLabelValue -> fourthLabelValue -> fifthLabelValue ->
                newOrExistingCounter(descriptor.anchor(), makeCodahaleID(descriptor) + "{"
                        + firstLabelName + "=" + firstLabelValue + "," + secondLabelName + "=" + secondLabelValue  + ","
                        + thirdLabelName + "=" + thirdLabelValue + "," + fourthLabelName + "=" + fourthLabelValue + ","
                        + fifthLabelName + "=" + fifthLabelValue + "}");
    }

    private org.opendaylight.serviceutils.metrics.Timer newOrExistingTimer(Object anchor, String id) {
        return timers.computeIfAbsent(id, newId -> {
            LOG.debug("New Timer metric: {}", id);
            return new TimerImpl(newId);
        });
    }

    @Override
    public final org.opendaylight.serviceutils.metrics.Timer newTimer(Object anchor, String id) {
        requireNonNull(anchor, "anchor == null");
        checkForExistingID(id);
        return newOrExistingTimer(anchor, id);
    }

    @Override
    public final Timer newTimer(MetricDescriptor descriptor) {
        return newOrExistingTimer(descriptor.anchor(), makeCodahaleID(descriptor));
    }

    @Override
    public final Labeled newTimer(MetricDescriptor descriptor, String labelName) {
        return labelValue -> newOrExistingTimer(descriptor.anchor(),
                makeCodahaleID(descriptor) + "{" + labelName + "=" + labelValue + "}");
    }

    @Override
    public final Labeled> newTimer(MetricDescriptor descriptor,
                                                String firstLabelName, String secondLabelName) {
        return firstLabelValue -> secondLabelValue -> newOrExistingTimer(descriptor.anchor(),
                makeCodahaleID(descriptor) + "{" + firstLabelName + "=" + firstLabelValue + ","
                        + secondLabelName + "=" + secondLabelValue + "}");
    }

    private static String makeCodahaleID(MetricDescriptor descriptor) {
        // We're ignoring descriptor.description() because Codahale Dropwizard Metrics
        // doesn't have it but other future metrics API implementations (e.g. Prometheus.io), or a
        // possible future metrics:list kind of CLI here, will use it.
        return MetricRegistry.name(descriptor.project(), descriptor.module(), descriptor.id());
    }

    private void checkForExistingID(String id) {
        requireNonNull(id, "id == null");
        if (registry.getNames().contains(id)) {
            throw new IllegalArgumentException("Metric ID already used: " + id);
        }
    }

    private abstract class CloseableMetricImpl extends AbstractRegistration {
        protected final String id;

        CloseableMetricImpl(String id) {
            this.id = id;
        }

        final void checkIfClosed() {
            if (isClosed()) {
                throw new IllegalStateException("Metric closed: " + id);
            }
        }

        @Override
        protected void removeRegistration() {
            if (!registry.remove(id)) {
                LOG.warn("Metric remove did not actualy remove: {}", id);
            }
        }
    }

    private final class MeterImpl extends CloseableMetricImpl implements org.opendaylight.serviceutils.metrics.Meter {

        private final com.codahale.metrics.Meter meter;

        MeterImpl(String id) {
            super(id);
            this.meter = registry.meter(id);
        }

        @Override
        public void mark() {
            checkIfClosed();
            meter.mark();
        }

        @Override
        public void mark(long howMany) {
            checkIfClosed();
            meter.mark(howMany);
        }

        @Override
        public long get() {
            return meter.getCount();
        }

        @Override
        protected void removeRegistration() {
            super.removeRegistration();
            meters.remove(id);
        }
    }

    private final class CounterImpl extends CloseableMetricImpl
            implements org.opendaylight.serviceutils.metrics.Counter {

        private final com.codahale.metrics.Counter counter;

        CounterImpl(String id) {
            super(id);
            this.counter = registry.counter(id);
        }

        @Override
        public void increment() {
            checkIfClosed();
            counter.inc();
        }

        @Override
        public void increment(long howMany) {
            checkIfClosed();
            counter.inc(howMany);
        }

        @Override
        public void decrement() {
            checkIfClosed();
            counter.dec();
        }

        @Override
        public void decrement(long howMany) {
            checkIfClosed();
            counter.dec(howMany);
        }

        @Override
        public long get() {
            return counter.getCount();
        }

        @Override
        protected void removeRegistration() {
            super.removeRegistration();
            counters.remove(id);
        }
    }

    private class TimerImpl extends CloseableMetricImpl implements org.opendaylight.serviceutils.metrics.Timer {

        private final com.codahale.metrics.Timer timer;

        TimerImpl(String id) {
            super(id);
            this.timer = registry.timer(id);
        }

        @Override
        @SuppressWarnings({ "checkstyle:IllegalCatch", "unchecked" })
        public  T time(CheckedCallable event) throws E {
            checkIfClosed();
            try {
                return timer.time(event::call);
            } catch (Exception e) {
                throw (E) e;
            }
        }

        @Override
        @SuppressWarnings({ "checkstyle:IllegalCatch", "checkstyle:AvoidHidingCauseException", "unchecked" })
        @SuppressFBWarnings("BC_UNCONFIRMED_CAST_OF_RETURN_VALUE") // getCause() will be Exception not Throwable
        public  void time(CheckedRunnable event) throws E {
            checkIfClosed();
            try {
                timer.time(() -> {
                    try {
                        event.run();
                    } catch (Exception exception) {
                        throw new InternalRuntimeException(exception);
                    }
                });
            } catch (InternalRuntimeException e) {
                throw (E) e.getCause();
            }
        }
    }

    private static class InternalRuntimeException extends RuntimeException {
        private static final long serialVersionUID = 1L;

        InternalRuntimeException(Exception exception) {
            super(exception);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy